use serde_json::{Map, Value};
use crate::cbor::{decode_canonical_cbor, CborValue};
use crate::poe_standard::{encode_poe_record, PoeRecord};
use crate::verifier::types::{
DecryptResult, MerkleCheck, PathSegment, SignatureCheck, UriCheck, ValidationSummary, Verdict,
VerifierIssue, VerifyReport, VerifyTxSummary, VerifyTxWitness,
};
#[must_use]
pub fn verify_report_to_dict(report: &VerifyReport) -> Value {
let mut out = Map::new();
out.insert("tx_hash".into(), Value::String(report.tx_hash.clone()));
out.insert(
"verdict".into(),
Value::String(report.verdict.as_str().into()),
);
out.insert("exit_code".into(), Value::from(report.exit_code.as_u8()));
out.insert(
"profile".into(),
Value::String(report.profile.as_str().into()),
);
out.insert("network".into(), Value::String(report.network.into()));
out.insert(
"confirmation_depth_threshold".into(),
Value::from(report.confirmation_depth_threshold),
);
out.insert("validation".into(), validation_to_value(&report.validation));
insert_list_or_omit(&mut out, "http_calls", http_calls_to_values(report));
out.insert(
"metadata_present".into(),
Value::Bool(report.metadata_present),
);
out.insert(
"num_confirmations".into(),
Value::from(report.num_confirmations),
);
if let Some(t) = report.block_time {
out.insert("block_time".into(), Value::from(t));
}
if let Some(s) = report.block_slot {
out.insert("block_slot".into(), Value::from(s));
}
if let Some(record) = &report.record {
out.insert("record".into(), record_to_value(record));
}
if let Some(checks) = &report.record_signatures {
out.insert(
"record_signatures".into(),
Value::Array(checks.iter().map(signature_check_to_value).collect()),
);
}
if let Some(results) = &report.item_decryptions {
out.insert(
"item_decryptions".into(),
Value::Array(results.iter().map(decrypt_result_to_value).collect()),
);
}
if let Some(witnesses) = &report.tx_witnesses {
out.insert(
"tx_witnesses".into(),
Value::Array(witnesses.iter().map(tx_witness_to_value).collect()),
);
}
if let Some(summary) = &report.tx_summary {
out.insert("tx_summary".into(), tx_summary_to_value(summary));
}
if let Some(labels) = &report.metadata_labels {
out.insert(
"metadata_labels".into(),
Value::Array(labels.iter().copied().map(Value::from).collect()),
);
}
if let Some(checks) = &report.uri_checks {
insert_list_or_omit(
&mut out,
"uri_checks",
checks.iter().map(uri_check_to_value).collect(),
);
}
if let Some(checks) = &report.merkle_checks {
insert_list_or_omit(
&mut out,
"merkle_checks",
checks.iter().map(merkle_check_to_value).collect(),
);
}
Value::Object(out)
}
fn insert_list_or_omit(out: &mut Map<String, Value>, key: &str, list: Vec<Value>) {
if !list.is_empty() {
out.insert(key.into(), Value::Array(list));
}
}
fn http_calls_to_values(report: &VerifyReport) -> Vec<Value> {
report
.http_calls
.iter()
.map(|c| {
let mut m = Map::new();
m.insert("url".into(), Value::String(c.url.clone()));
m.insert("method".into(), Value::String(c.method.as_str().into()));
m.insert("status".into(), Value::from(c.status));
m.insert("bytes".into(), Value::from(c.bytes));
m.insert("duration_ms".into(), Value::from(c.duration_ms));
m.insert("purpose".into(), Value::String(c.purpose.as_str().into()));
Value::Object(m)
})
.collect()
}
fn validation_to_value(v: &ValidationSummary) -> Value {
let mut m = Map::new();
m.insert("valid".into(), Value::Bool(v.valid));
if !v.issues.is_empty() {
m.insert("issues".into(), issues_to_values(&v.issues));
}
if !v.warnings.is_empty() {
m.insert("warnings".into(), issues_to_values(&v.warnings));
}
if !v.info.is_empty() {
m.insert("info".into(), issues_to_values(&v.info));
}
Value::Object(m)
}
fn issues_to_values(issues: &[VerifierIssue]) -> Value {
Value::Array(
issues
.iter()
.map(|i| {
let mut m = Map::new();
m.insert("code".into(), Value::String(i.code.code().into()));
m.insert("path".into(), Value::Array(path_to_values(&i.path)));
m.insert("message".into(), Value::String(i.message.clone()));
Value::Object(m)
})
.collect(),
)
}
fn path_to_values(path: &[PathSegment]) -> Vec<Value> {
path.iter()
.map(|seg| match seg {
PathSegment::Key(k) => Value::String(k.clone()),
PathSegment::Index(i) => Value::from(*i),
})
.collect()
}
fn signature_check_to_value(c: &SignatureCheck) -> Value {
let mut m = Map::new();
m.insert("index".into(), Value::from(c.index));
m.insert("verdict".into(), Value::String(c.verdict_str().into()));
if let Some(pub_hex) = &c.signer_pub {
m.insert("signer_pub".into(), Value::String(pub_hex.clone()));
}
if let Some(t) = c.signer_type {
m.insert("signer_type".into(), Value::String(t.as_str().into()));
}
if let Some(r) = c.reason {
m.insert("reason".into(), Value::String(r.as_str().into()));
}
Value::Object(m)
}
fn decrypt_result_to_value(d: &DecryptResult) -> Value {
let mut m = Map::new();
m.insert("item_index".into(), Value::from(d.item_index));
m.insert("verdict".into(), Value::String(d.verdict_str().into()));
if let Some(hash_ok) = d.plaintext_hash_ok {
m.insert("plaintext_hash_ok".into(), Value::Bool(hash_ok));
}
if let Some(reason) = d.reason_str() {
m.insert("reason".into(), Value::String(reason.into()));
}
Value::Object(m)
}
fn tx_witness_to_value(w: &VerifyTxWitness) -> Value {
let mut m = Map::new();
m.insert("type".into(), Value::String("vkey".into()));
m.insert("vkey".into(), Value::String(w.vkey.clone()));
m.insert("key_hash".into(), Value::String(w.key_hash.clone()));
m.insert("signature_valid".into(), Value::Bool(w.signature_valid));
Value::Object(m)
}
fn tx_summary_to_value(s: &VerifyTxSummary) -> Value {
let mut m = Map::new();
m.insert("fee_lovelace".into(), Value::String(s.fee_lovelace.clone()));
m.insert("input_count".into(), Value::from(s.input_count));
m.insert("output_count".into(), Value::from(s.output_count));
m.insert(
"outputs".into(),
Value::Array(
s.outputs
.iter()
.map(|o| {
let mut om = Map::new();
om.insert("address".into(), Value::String(o.address.clone()));
om.insert("lovelace".into(), Value::String(o.lovelace.clone()));
Value::Object(om)
})
.collect(),
),
);
m.insert(
"total_output_lovelace".into(),
Value::String(s.total_output_lovelace.clone()),
);
m.insert(
"script_witness_count".into(),
Value::from(s.script_witness_count),
);
if let Some(v) = s.invalid_before {
m.insert("invalid_before".into(), Value::from(v));
}
if let Some(v) = s.invalid_hereafter {
m.insert("invalid_hereafter".into(), Value::from(v));
}
if let Some(hashes) = &s.required_signer_key_hashes {
m.insert(
"required_signer_key_hashes".into(),
Value::Array(hashes.iter().map(|h| Value::String(h.clone())).collect()),
);
}
if let Some(v) = s.network_id {
m.insert("network_id".into(), Value::from(v));
}
Value::Object(m)
}
fn uri_check_to_value(u: &UriCheck) -> Value {
let mut m = Map::new();
m.insert("item_index".into(), Value::from(u.item_index));
m.insert("uri".into(), Value::String(u.uri.clone()));
m.insert("ok".into(), Value::Bool(u.ok));
if let Some(r) = u.reason {
m.insert("reason".into(), Value::String(r.as_str().into()));
}
Value::Object(m)
}
fn merkle_check_to_value(c: &MerkleCheck) -> Value {
let mut m = Map::new();
m.insert("merkle_index".into(), Value::from(c.merkle_index));
m.insert("alg".into(), Value::String(c.alg.clone()));
m.insert("verdict".into(), Value::String(c.verdict_str().into()));
if let Some(r) = c.reason {
m.insert("reason".into(), Value::String(r.as_str().into()));
}
Value::Object(m)
}
fn record_to_value(record: &PoeRecord) -> Value {
let Ok(bytes) = encode_poe_record(record) else {
return Value::Object(Map::new());
};
let Ok(cbor) = decode_canonical_cbor(&bytes) else {
return Value::Object(Map::new());
};
cbor_to_value(&cbor)
}
fn cbor_to_value(value: &CborValue) -> Value {
match value {
CborValue::Unsigned(n) => Value::from(*n),
CborValue::Negative(m) => {
let signed = -1_i128 - i128::from(*m);
i64::try_from(signed).map_or_else(|_| Value::String(signed.to_string()), Value::from)
}
CborValue::Bytes(b) => Value::String(crate::hex::encode(b)),
CborValue::Text(s) => Value::String(s.clone()),
CborValue::Bool(b) => Value::Bool(*b),
CborValue::Null => Value::Null,
CborValue::Array(items) => Value::Array(items.iter().map(cbor_to_value).collect()),
CborValue::Map(pairs) => {
let mut m = Map::new();
for (k, v) in pairs {
m.insert(cbor_key_to_string(k), cbor_to_value(v));
}
Value::Object(m)
}
}
}
fn cbor_key_to_string(key: &CborValue) -> String {
match key {
CborValue::Text(s) => s.clone(),
CborValue::Unsigned(n) => n.to_string(),
CborValue::Negative(m) => (-1_i128 - i128::from(*m)).to_string(),
CborValue::Bytes(b) => crate::hex::encode(b),
other => format!("{other:?}"),
}
}
#[must_use]
pub fn is_clean_pass(report: &VerifyReport) -> bool {
report.verdict == Verdict::Valid
}