use super::{
infer_digest_code, parse_qb64_diger, parse_qb64_identifier, parse_qb64_prefixer,
parse_qb64_saider, parse_qb64_verfer, parse_sn, parse_weight,
};
use crate::core::matter::code::DigestCode;
use crate::core::matter::error::ValidationError;
use crate::core::primitives::{Diger, Prefixer, Seqner, Tholder, Verfer};
use crate::keri::{
ConfigTrait, DelegatedInceptionEvent, DelegatedRotationEvent, Ilk, InceptionEvent,
InteractionEvent, KeriEvent, RotationEvent, Seal,
};
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{borrow::ToOwned, format, string::String, string::ToString, vec, vec::Vec};
use serde_json::Value;
use crate::serder::error::SerderError;
use crate::serder::primitives::to_qb64_string;
use crate::serder::said::{compute_digest, said_placeholder};
use crate::serder::version::{SerKind, VERSION_STRING_LEN, VersionString};
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_event(raw: &[u8]) -> Result<KeriEvent, SerderError> {
validate_version_string(raw)?;
let val: Value = serde_json::from_slice(raw)?;
let ilk_str = get_str(&val, "t")?;
let ilk = Ilk::from_code(ilk_str).map_err(|_| SerderError::UnknownIlk(ilk_str.to_owned()))?;
match ilk {
Ilk::Icp => Ok(KeriEvent::Inception(deserialize_inception(raw)?)),
Ilk::Rot => Ok(KeriEvent::Rotation(deserialize_rotation(raw)?)),
Ilk::Ixn => Ok(KeriEvent::Interaction(deserialize_interaction(raw)?)),
Ilk::Dip => Ok(KeriEvent::DelegatedInception(
deserialize_delegated_inception(raw)?,
)),
Ilk::Drt => Ok(KeriEvent::DelegatedRotation(
deserialize_delegated_rotation(raw)?,
)),
_ => Err(SerderError::UnknownIlk(ilk_str.to_owned())),
}
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_inception(raw: &[u8]) -> Result<InceptionEvent, SerderError> {
validate_version_string(raw)?;
let val: Value = serde_json::from_slice(raw)?;
let digest_code = infer_digest_code(get_str(&val, "d")?)?;
let d_str = get_str(&val, "d")?;
let i_str = get_str(&val, "i")?;
if d_str == i_str {
verify_said_double(raw, digest_code)?;
} else {
verify_said_single(raw, digest_code)?;
}
let said = parse_qb64_diger(get_str(&val, "d")?, "d")?;
let prefix = parse_qb64_identifier(get_str(&val, "i")?, "i")?;
let sn = parse_sn(get_str(&val, "s")?)?;
let threshold = tholder_from_json(get_field(&val, "kt")?)?;
let keys = parse_qb64_verfer_array(get_field(&val, "k")?)?;
let next_threshold = tholder_from_json(get_field(&val, "nt")?)?;
let next_keys = parse_qb64_diger_array(get_field(&val, "n")?)?;
let witness_threshold = parse_witness_threshold(get_field(&val, "bt")?)?;
let witnesses = parse_qb64_prefixer_array(get_field(&val, "b")?)?;
let config = parse_config_array(get_field(&val, "c")?)?;
let anchors = parse_seal_array(get_field(&val, "a")?)?;
Ok(InceptionEvent::new(
prefix,
Seqner::new(sn),
said,
keys,
threshold,
next_keys,
next_threshold,
witnesses,
witness_threshold,
config,
anchors,
))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_rotation(raw: &[u8]) -> Result<RotationEvent, SerderError> {
validate_version_string(raw)?;
let val: Value = serde_json::from_slice(raw)?;
let digest_code = infer_digest_code(get_str(&val, "d")?)?;
verify_said_single(raw, digest_code)?;
let said = parse_qb64_diger(get_str(&val, "d")?, "d")?;
let prefix = parse_qb64_identifier(get_str(&val, "i")?, "i")?;
let sn = parse_sn(get_str(&val, "s")?)?;
let prior_event_said = parse_qb64_diger(get_str(&val, "p")?, "p")?;
let threshold = tholder_from_json(get_field(&val, "kt")?)?;
let keys = parse_qb64_verfer_array(get_field(&val, "k")?)?;
let next_threshold = tholder_from_json(get_field(&val, "nt")?)?;
let next_keys = parse_qb64_diger_array(get_field(&val, "n")?)?;
let witness_threshold = parse_witness_threshold(get_field(&val, "bt")?)?;
let witness_removals = parse_qb64_prefixer_array(get_field(&val, "br")?)?;
let witness_additions = parse_qb64_prefixer_array(get_field(&val, "ba")?)?;
let config = match val.get("c") {
Some(c_val) => parse_config_array(c_val)?,
None => vec![],
};
let anchors = parse_seal_array(get_field(&val, "a")?)?;
Ok(RotationEvent::new(
prefix,
Seqner::new(sn),
said,
prior_event_said,
keys,
threshold,
next_keys,
next_threshold,
witness_additions,
witness_removals,
witness_threshold,
config,
anchors,
))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_interaction(raw: &[u8]) -> Result<InteractionEvent, SerderError> {
validate_version_string(raw)?;
let val: Value = serde_json::from_slice(raw)?;
let digest_code = infer_digest_code(get_str(&val, "d")?)?;
verify_said_single(raw, digest_code)?;
let said = parse_qb64_diger(get_str(&val, "d")?, "d")?;
let prefix = parse_qb64_identifier(get_str(&val, "i")?, "i")?;
let sn = parse_sn(get_str(&val, "s")?)?;
let prior_event_said = parse_qb64_diger(get_str(&val, "p")?, "p")?;
let anchors = parse_seal_array(get_field(&val, "a")?)?;
Ok(InteractionEvent::new(
prefix,
Seqner::new(sn),
said,
prior_event_said,
anchors,
))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_delegated_inception(
raw: &[u8],
) -> Result<DelegatedInceptionEvent, SerderError> {
validate_version_string(raw)?;
let val: Value = serde_json::from_slice(raw)?;
let digest_code = infer_digest_code(get_str(&val, "d")?)?;
let d_str = get_str(&val, "d")?;
let i_str = get_str(&val, "i")?;
if d_str == i_str {
verify_said_double(raw, digest_code)?;
} else {
verify_said_single(raw, digest_code)?;
}
let said = parse_qb64_diger(get_str(&val, "d")?, "d")?;
let prefix = parse_qb64_identifier(get_str(&val, "i")?, "i")?;
let sn = parse_sn(get_str(&val, "s")?)?;
let threshold = tholder_from_json(get_field(&val, "kt")?)?;
let keys = parse_qb64_verfer_array(get_field(&val, "k")?)?;
let next_threshold = tholder_from_json(get_field(&val, "nt")?)?;
let next_keys = parse_qb64_diger_array(get_field(&val, "n")?)?;
let witness_threshold = parse_witness_threshold(get_field(&val, "bt")?)?;
let witnesses = parse_qb64_prefixer_array(get_field(&val, "b")?)?;
let config = parse_config_array(get_field(&val, "c")?)?;
let anchors = parse_seal_array(get_field(&val, "a")?)?;
let delegator = parse_qb64_identifier(get_str(&val, "di")?, "di")?;
Ok(DelegatedInceptionEvent::new(
InceptionEvent::new(
prefix,
Seqner::new(sn),
said,
keys,
threshold,
next_keys,
next_threshold,
witnesses,
witness_threshold,
config,
anchors,
),
delegator,
))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn deserialize_delegated_rotation(
raw: &[u8],
) -> Result<DelegatedRotationEvent, SerderError> {
let rotation = deserialize_rotation(raw)?;
Ok(DelegatedRotationEvent::new(rotation))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn validate_version_string(raw: &[u8]) -> Result<(), SerderError> {
let val: Value = serde_json::from_slice(raw)?;
let vs_str = val
.get("v")
.and_then(Value::as_str)
.ok_or(SerderError::MissingField("v"))?;
if vs_str.len() < VERSION_STRING_LEN {
return Err(SerderError::InvalidVersionString(format!(
"version string too short: {}",
vs_str.len()
)));
}
let vs = VersionString::parse(vs_str)?;
if vs.kind != SerKind::Json {
return Err(SerderError::InvalidVersionString(format!(
"expected JSON, got {}",
vs.kind.as_str()
)));
}
let expected_size =
usize::try_from(vs.size).map_err(|e| SerderError::InvalidVersionString(e.to_string()))?;
if expected_size != raw.len() {
return Err(SerderError::InvalidVersionString(format!(
"version string size {} does not match actual size {}",
expected_size,
raw.len()
)));
}
Ok(())
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn verify_said_single(raw: &[u8], code: DigestCode) -> Result<(), SerderError> {
let mut value: Value = serde_json::from_slice(raw)?;
let obj = value
.as_object_mut()
.ok_or(SerderError::MissingField("d"))?;
let original_said = obj
.get("d")
.and_then(Value::as_str)
.ok_or(SerderError::MissingField("d"))?
.to_owned();
let placeholder = said_placeholder(code)?;
obj.insert("d".to_owned(), Value::String(placeholder));
let reser = serde_json::to_string(&value)?;
let computed = compute_digest(reser.as_bytes(), code)?;
let computed_qb64 = to_qb64_string(&computed);
if original_said != computed_qb64 {
return Err(SerderError::SaidMismatch {
expected: original_said,
computed: computed_qb64,
});
}
Ok(())
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn verify_said_double(raw: &[u8], code: DigestCode) -> Result<(), SerderError> {
let mut value: Value = serde_json::from_slice(raw)?;
let obj = value
.as_object_mut()
.ok_or(SerderError::MissingField("d"))?;
let original_said = obj
.get("d")
.and_then(Value::as_str)
.ok_or(SerderError::MissingField("d"))?
.to_owned();
let placeholder = said_placeholder(code)?;
obj.insert("d".to_owned(), Value::String(placeholder.clone()));
obj.insert("i".to_owned(), Value::String(placeholder));
let reser = serde_json::to_string(&value)?;
let computed = compute_digest(reser.as_bytes(), code)?;
let computed_qb64 = to_qb64_string(&computed);
if original_said != computed_qb64 {
return Err(SerderError::SaidMismatch {
expected: original_said,
computed: computed_qb64,
});
}
Ok(())
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_qb64_prefixer_array(
val: &Value,
) -> Result<Vec<Prefixer<'static>>, SerderError> {
let arr = val.as_array().ok_or(SerderError::MissingField("b"))?;
arr.iter()
.map(|v| {
let s = v.as_str().ok_or(SerderError::MissingField("b"))?;
parse_qb64_prefixer(s, "b")
})
.collect()
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_qb64_verfer_array(val: &Value) -> Result<Vec<Verfer<'static>>, SerderError> {
let arr = val.as_array().ok_or(SerderError::MissingField("k"))?;
arr.iter()
.map(|v| {
let s = v.as_str().ok_or(SerderError::MissingField("k"))?;
parse_qb64_verfer(s, "k")
})
.collect()
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_qb64_diger_array(val: &Value) -> Result<Vec<Diger<'static>>, SerderError> {
let arr = val.as_array().ok_or(SerderError::MissingField("n"))?;
arr.iter()
.map(|v| {
let s = v.as_str().ok_or(SerderError::MissingField("n"))?;
parse_qb64_diger(s, "n")
})
.collect()
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn tholder_from_json(val: &Value) -> Result<Tholder, SerderError> {
if let Some(s) = val.as_str() {
let n = u64::from_str_radix(s, 16).map_err(|_| SerderError::InvalidPrimitive {
field: "kt",
source: ValidationError::UnknownMatterCode(format!("invalid hex threshold: {s}")),
})?;
return Ok(Tholder::Simple(n));
}
if let Some(n) = val.as_u64() {
return Ok(Tholder::Simple(n));
}
if let Some(outer) = val.as_array() {
let is_flat = outer.first().is_some_and(Value::is_string);
let clauses: Result<Vec<Vec<(u64, u64)>>, SerderError> = if is_flat {
let clause: Result<Vec<(u64, u64)>, SerderError> = outer
.iter()
.map(|frac_val| {
let frac_str = frac_val.as_str().ok_or(SerderError::MissingField("kt"))?;
parse_weight(frac_str)
})
.collect();
Ok(vec![clause?])
} else {
outer
.iter()
.map(|clause_val| {
let clause_arr = clause_val
.as_array()
.ok_or(SerderError::MissingField("kt"))?;
clause_arr
.iter()
.map(|frac_val| {
let frac_str =
frac_val.as_str().ok_or(SerderError::MissingField("kt"))?;
parse_weight(frac_str)
})
.collect()
})
.collect()
};
return Ok(Tholder::Weighted(clauses?));
}
Err(SerderError::MissingField("kt"))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_witness_threshold(val: &Value) -> Result<u32, SerderError> {
if let Some(n) = val.as_u64() {
return u32::try_from(n).map_err(|_| SerderError::InvalidPrimitive {
field: "bt",
source: ValidationError::UnknownMatterCode(format!(
"witness threshold {n} exceeds u32::MAX"
)),
});
}
let s = val.as_str().ok_or(SerderError::MissingField("bt"))?;
let n = u128::from_str_radix(s, 16).map_err(|_| SerderError::InvalidPrimitive {
field: "bt",
source: ValidationError::UnknownMatterCode(format!("invalid hex bt: {s}")),
})?;
u32::try_from(n).map_err(|_| SerderError::InvalidPrimitive {
field: "bt",
source: ValidationError::UnknownMatterCode(format!(
"witness threshold {n} exceeds u32::MAX"
)),
})
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn seal_from_json(val: &Value) -> Result<Seal, SerderError> {
let obj = val.as_object().ok_or(SerderError::MissingField("a"))?;
let has = |k: &str| obj.contains_key(k);
let n = obj.len();
if has("i") && has("s") && has("d") && n == 3 {
let i = parse_qb64_prefixer(
obj["i"].as_str().ok_or(SerderError::MissingField("i"))?,
"i",
)?;
let s_val = parse_sn(obj["s"].as_str().ok_or(SerderError::MissingField("s"))?)?;
let digest = parse_qb64_saider(
obj["d"].as_str().ok_or(SerderError::MissingField("d"))?,
"d",
)?;
Ok(Seal::Event {
i,
s: Seqner::new(s_val),
d: digest,
})
} else if has("s") && has("d") && n == 2 {
let s_val = parse_sn(obj["s"].as_str().ok_or(SerderError::MissingField("s"))?)?;
let digest = parse_qb64_saider(
obj["d"].as_str().ok_or(SerderError::MissingField("d"))?,
"d",
)?;
Ok(Seal::Source {
s: Seqner::new(s_val),
d: digest,
})
} else if has("rd") && n == 1 {
let root_digest = parse_qb64_saider(
obj["rd"].as_str().ok_or(SerderError::MissingField("rd"))?,
"rd",
)?;
Ok(Seal::Root { rd: root_digest })
} else if has("d") && n == 1 {
let digest = parse_qb64_saider(
obj["d"].as_str().ok_or(SerderError::MissingField("d"))?,
"d",
)?;
Ok(Seal::Digest { d: digest })
} else if has("i") && n == 1 {
let i = parse_qb64_prefixer(
obj["i"].as_str().ok_or(SerderError::MissingField("i"))?,
"i",
)?;
Ok(Seal::Last { i })
} else {
Err(SerderError::MissingField("a"))
}
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_seal_array(val: &Value) -> Result<Vec<Seal>, SerderError> {
let arr = val.as_array().ok_or(SerderError::MissingField("a"))?;
arr.iter().map(seal_from_json).collect()
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn parse_config_array(val: &Value) -> Result<Vec<ConfigTrait>, SerderError> {
let arr = val.as_array().ok_or(SerderError::MissingField("c"))?;
arr.iter()
.map(|v| {
let s = v.as_str().ok_or(SerderError::MissingField("c"))?;
ConfigTrait::from_code(s).map_err(|_| SerderError::UnknownIlk(s.to_owned()))
})
.collect()
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn get_str<'a>(val: &'a Value, field: &'static str) -> Result<&'a str, SerderError> {
val.get(field)
.and_then(Value::as_str)
.ok_or(SerderError::MissingField(field))
}
#[allow(
clippy::redundant_pub_crate,
reason = "pub(crate) is intentional — the enclosing module is crate-internal and `unreachable_pub` denies plain `pub`"
)]
pub(crate) fn get_field<'a>(val: &'a Value, field: &'static str) -> Result<&'a Value, SerderError> {
val.get(field).ok_or(SerderError::MissingField(field))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::matter::builder::MatterBuilder;
use crate::core::matter::code::{CesrCode, DigestCode, VerKeyCode};
use crate::core::primitives::{Prefixer, Saider};
use crate::serder::serialize::{
serialize_delegated_inception, serialize_delegated_rotation, serialize_inception,
serialize_interaction, serialize_rotation,
};
use alloc::borrow::Cow;
fn make_prefixer() -> Prefixer<'static> {
MatterBuilder::new()
.with_code(VerKeyCode::Ed25519)
.with_raw(Cow::<[u8]>::Owned(vec![0u8; 32]))
.unwrap()
.build()
.unwrap()
}
fn make_saider() -> Saider<'static> {
MatterBuilder::new()
.with_code(DigestCode::Blake3_256)
.with_raw(Cow::<[u8]>::Owned(vec![1u8; 32]))
.unwrap()
.build()
.unwrap()
}
fn make_verfer() -> Verfer<'static> {
MatterBuilder::new()
.with_code(VerKeyCode::Ed25519)
.with_raw(Cow::<[u8]>::Owned(vec![1u8; 32]))
.unwrap()
.build()
.unwrap()
}
fn qb64(m: &crate::core::matter::matter::Matter<'_, impl CesrCode>) -> String {
crate::serder::primitives::to_qb64_string(m)
}
fn probe_icp() -> InceptionEvent {
InceptionEvent::new(
make_prefixer().into(),
Seqner::new(0),
make_saider(),
vec![make_verfer()],
Tholder::Simple(1),
vec![make_saider()],
Tholder::Simple(1),
vec![make_prefixer()],
1,
vec![ConfigTrait::EstOnly],
vec![Seal::Digest { d: make_saider() }],
)
}
fn probe_rot() -> RotationEvent {
RotationEvent::new(
make_prefixer().into(),
Seqner::new(2),
make_saider(),
make_saider(),
vec![make_verfer()],
Tholder::Simple(1),
vec![make_saider()],
Tholder::Simple(1),
vec![make_prefixer()],
vec![],
1,
vec![],
vec![],
)
}
#[test]
fn oracle_roundtrips_icp() {
let ser = serialize_inception(&probe_icp()).unwrap();
let event = deserialize_inception(ser.as_bytes()).unwrap();
assert_eq!(qb64(event.said()), qb64(ser.said()));
assert!(matches!(
deserialize_event(ser.as_bytes()).unwrap(),
KeriEvent::Inception(_)
));
}
#[test]
fn oracle_roundtrips_rot() {
let ser = serialize_rotation(&probe_rot()).unwrap();
let event = deserialize_rotation(ser.as_bytes()).unwrap();
assert_eq!(qb64(event.said()), qb64(ser.said()));
assert!(matches!(
deserialize_event(ser.as_bytes()).unwrap(),
KeriEvent::Rotation(_)
));
}
#[test]
fn oracle_roundtrips_ixn() {
let ixn = InteractionEvent::new(
make_prefixer().into(),
Seqner::new(3),
make_saider(),
make_saider(),
vec![Seal::Digest { d: make_saider() }],
);
let ser = serialize_interaction(&ixn).unwrap();
let event = deserialize_interaction(ser.as_bytes()).unwrap();
assert_eq!(qb64(event.said()), qb64(ser.said()));
assert!(matches!(
deserialize_event(ser.as_bytes()).unwrap(),
KeriEvent::Interaction(_)
));
}
#[test]
fn oracle_roundtrips_dip() {
let dip = DelegatedInceptionEvent::new(probe_icp(), make_prefixer().into());
let ser = serialize_delegated_inception(&dip).unwrap();
let event = deserialize_delegated_inception(ser.as_bytes()).unwrap();
assert_eq!(qb64(event.inception().said()), qb64(ser.said()));
assert!(matches!(
deserialize_event(ser.as_bytes()).unwrap(),
KeriEvent::DelegatedInception(_)
));
}
#[test]
fn oracle_roundtrips_drt() {
let drt = DelegatedRotationEvent::new(probe_rot());
let ser = serialize_delegated_rotation(&drt).unwrap();
let event = deserialize_delegated_rotation(ser.as_bytes()).unwrap();
assert_eq!(qb64(event.rotation().said()), qb64(ser.said()));
assert!(matches!(
deserialize_event(ser.as_bytes()).unwrap(),
KeriEvent::DelegatedRotation(_)
));
}
#[test]
fn tholder_simple_from_json() {
let val = Value::String("3".to_owned());
let th = tholder_from_json(&val).unwrap();
assert_eq!(th, Tholder::Simple(3));
}
#[test]
fn tholder_weighted_from_json() {
let val = serde_json::json!([["1/2", "1/2"], ["1/3", "1/3", "1/3"]]);
let th = tholder_from_json(&val).unwrap();
assert_eq!(
th,
Tholder::Weighted(vec![vec![(1, 2), (1, 2)], vec![(1, 3), (1, 3), (1, 3)],])
);
}
#[test]
fn tholder_invalid_returns_error() {
let val = Value::Bool(true);
let result = tholder_from_json(&val);
assert!(result.is_err());
}
#[test]
fn seal_digest_from_json() {
let saider = make_saider();
let qb64_str = qb64(&saider);
let val = serde_json::json!({"d": qb64_str});
let seal = seal_from_json(&val).unwrap();
assert!(matches!(seal, Seal::Digest { .. }));
}
#[test]
fn seal_root_from_json() {
let saider = make_saider();
let qb64_str = qb64(&saider);
let val = serde_json::json!({"rd": qb64_str});
let seal = seal_from_json(&val).unwrap();
assert!(matches!(seal, Seal::Root { .. }));
}
#[test]
fn seal_source_from_json() {
let saider = make_saider();
let qb64_str = qb64(&saider);
let val = serde_json::json!({"s": "1", "d": qb64_str});
let seal = seal_from_json(&val).unwrap();
let Seal::Source { s, d } = seal else {
unreachable!()
};
assert_eq!(s.value(), 1);
assert_eq!(qb64(&d), qb64_str);
}
#[test]
fn seal_event_from_json() {
let saider = make_saider();
let prefixer = make_prefixer();
let d_str = qb64(&saider);
let i_str = qb64(&prefixer);
let val = serde_json::json!({"i": i_str, "s": "a", "d": d_str});
let seal = seal_from_json(&val).unwrap();
let Seal::Event { i, s, d } = seal else {
unreachable!()
};
assert_eq!(qb64(&i), i_str);
assert_eq!(s.value(), 10);
assert_eq!(qb64(&d), d_str);
}
#[test]
fn seal_last_from_json() {
let prefixer = make_prefixer();
let i_str = qb64(&prefixer);
let val = serde_json::json!({"i": i_str});
let seal = seal_from_json(&val).unwrap();
let Seal::Last { i } = seal else {
unreachable!()
};
assert_eq!(qb64(&i), i_str);
}
#[test]
fn tholder_from_json_integer() {
let val = serde_json::json!(2);
let tholder = tholder_from_json(&val).unwrap();
assert_eq!(tholder, Tholder::Simple(2));
}
#[test]
fn parse_witness_threshold_integer() {
let val = serde_json::json!(3);
let bt = parse_witness_threshold(&val).unwrap();
assert_eq!(bt, 3);
}
}