use anyhow::{Result, bail};
use bc_components::{ARID, Digest, URI, UUID, XID, tags::TAG_ENVELOPE};
use bc_envelope::prelude::*;
use clap::{Args, ValueEnum};
use crate::{EnvelopeArgs, EnvelopeArgsLike};
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
pub enum SubjectType {
Assertion,
Object,
Predicate,
Arid,
AridHex,
Bool,
Cbor,
Data,
Date,
Digest,
Envelope,
Known,
Number,
String,
Ur,
Uri,
Uuid,
Wrapped,
Xid,
}
#[derive(Debug, Args)]
#[group(skip)]
pub struct CommandArgs {
#[arg(name = "TYPE")]
subject_type: SubjectType,
#[arg(long)]
ur_type: Option<String>,
#[arg(long)]
ur_tag: Option<u64>,
#[command(flatten)]
envelope_args: EnvelopeArgs,
}
impl EnvelopeArgsLike for CommandArgs {
fn envelope(&self) -> Option<&str> { self.envelope_args.envelope() }
}
impl crate::Exec for CommandArgs {
fn exec(&self) -> Result<String> {
let envelope = self.read_envelope()?;
let string = match self.subject_type {
SubjectType::Assertion => extract_assertion(envelope)?,
SubjectType::Object => extract_object(envelope)?,
SubjectType::Predicate => extract_predicate(envelope)?,
SubjectType::Arid => {
envelope.extract_subject::<ARID>()?.ur_string()
}
SubjectType::AridHex => envelope.extract_subject::<ARID>()?.hex(),
SubjectType::Bool => {
envelope.extract_subject::<bool>()?.to_string()
}
SubjectType::Cbor => extract_cbor_string(envelope)?,
SubjectType::Data => hex::encode(
envelope
.subject()
.try_leaf()?
.to_cbor()
.try_into_byte_string()?,
),
SubjectType::Date => {
envelope.extract_subject::<Date>()?.to_string()
}
SubjectType::Digest => {
envelope.extract_subject::<Digest>()?.ur_string()
}
SubjectType::Envelope => envelope.subject().ur_string(),
SubjectType::Known => extract_known_value_string(envelope)?,
SubjectType::Number => {
envelope.extract_subject::<f64>()?.to_string()
}
SubjectType::String => envelope.extract_subject::<String>()?,
SubjectType::Ur => self.extract_ur(envelope)?,
SubjectType::Uri => envelope.extract_subject::<URI>()?.to_string(),
SubjectType::Uuid => {
envelope.extract_subject::<UUID>()?.to_string()
}
SubjectType::Wrapped => envelope.try_unwrap()?.ur_string(),
SubjectType::Xid => envelope.extract_subject::<XID>()?.ur_string(),
};
Ok(string)
}
}
fn extract_assertion(envelope: Envelope) -> Result<String> {
if let Some(assertion) = envelope.as_assertion() {
let pred_obj = [
assertion.clone().as_predicate().unwrap(),
assertion.as_object().unwrap(),
];
Ok(pred_obj
.into_iter()
.map(|e| e.ur_string())
.collect::<Vec<String>>()
.join("\n"))
} else {
bail!("Envelope is not an assertion.");
}
}
fn extract_object(envelope: Envelope) -> Result<String> {
if let Some(assertion) = envelope.as_assertion() {
Ok(assertion.as_object().unwrap().ur_string())
} else {
bail!("Envelope is not an assertion.");
}
}
fn extract_predicate(envelope: Envelope) -> Result<String> {
if let Some(assertion) = envelope.as_assertion() {
Ok(assertion.as_predicate().unwrap().ur_string())
} else {
bail!("Envelope is not an assertion.");
}
}
impl CommandArgs {
fn extract_ur(&self, envelope: Envelope) -> Result<String> {
Ok(if let Some(cbor) = envelope.clone().subject().as_leaf() {
if let CBORCase::Tagged(tag, untagged_cbor) = cbor.into_case() {
let known_tag =
with_tags!(|tags: &dyn dcbor::TagsStoreTrait| {
tags.tag_for_value(tag.value())
});
let mut ur_type: Option<String> = self.ur_type.clone();
if let Some(known_tag) = known_tag
&& let Some(name) = known_tag.name()
{
ur_type = Some(name.to_string());
}
if ur_type.is_none() {
bail!("UR type required");
}
bc_ur::UR::new(ur_type.unwrap(), untagged_cbor)?.to_string()
} else {
bail!(
"Can't convert to UR: CBOR in envelope subject has no tag"
);
}
} else if envelope.is_wrapped() {
if self.ur_tag.is_some() || self.ur_type.is_some() {
if self.ur_tag != Some(TAG_ENVELOPE) {
bail!("UR tag mismatch");
}
let envelope_name =
with_format_context!(|context: &FormatContext| {
context.tags().name_for_value(TAG_ENVELOPE)
});
if self.ur_type != Some(envelope_name) {
bail!("UR type mismatch");
}
}
envelope.try_unwrap()?.ur_string()
} else {
bail!("No CBOR data found in envelope subject");
})
}
}
fn extract_known_value_string(envelope: Envelope) -> Result<String> {
let _k = envelope.extract_subject::<KnownValue>()?;
Ok(envelope.subject().format())
}
fn extract_cbor_string(envelope: Envelope) -> Result<String> {
let subject = &envelope.subject();
Ok(if let Some(cbor) = subject.as_leaf() {
cbor.hex()
} else if subject.is_wrapped() {
envelope.try_unwrap()?.to_cbor().hex()
} else if let Some(known_value) = subject.as_known_value() {
known_value.to_cbor().hex()
} else {
bail!("No CBOR data found in envelope subject");
})
}