1use std::borrow::Borrow;
2use std::fmt;
3use std::str::FromStr;
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use crate::keys::{KeriDecodeError, KeriPublicKey};
8
9#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
13#[error("Invalid KERI {type_name}: {reason}")]
14pub struct KeriTypeError {
15 pub type_name: &'static str,
17 pub reason: String,
19}
20
21fn validate_prefix_derivation_code(s: &str) -> Result<(), KeriTypeError> {
26 if s.is_empty() {
27 return Err(KeriTypeError {
28 type_name: "Prefix",
29 reason: "must not be empty".into(),
30 });
31 }
32 let first = s.as_bytes()[0];
33 if !first.is_ascii_uppercase() && !first.is_ascii_digit() {
34 return Err(KeriTypeError {
35 type_name: "Prefix",
36 reason: format!(
37 "must start with a CESR derivation code (uppercase letter or digit), got '{}'",
38 &s[..s.len().min(10)]
39 ),
40 });
41 }
42 Ok(())
43}
44
45fn validate_said_derivation_code(s: &str) -> Result<(), KeriTypeError> {
49 if s.is_empty() {
50 return Err(KeriTypeError {
51 type_name: "Said",
52 reason: "must not be empty".into(),
53 });
54 }
55 if !s.starts_with('E') {
56 return Err(KeriTypeError {
57 type_name: "Said",
58 reason: format!(
59 "must start with 'E' (Blake3 derivation code), got '{}'",
60 &s[..s.len().min(10)]
61 ),
62 });
63 }
64 Ok(())
65}
66
67#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
84#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
85#[repr(transparent)]
86pub struct Prefix(String);
87
88impl Prefix {
89 pub fn new(s: String) -> Result<Self, KeriTypeError> {
94 validate_prefix_derivation_code(&s)?;
95 Ok(Self(s))
96 }
97
98 pub fn new_unchecked(s: String) -> Self {
100 Self(s)
101 }
102
103 pub fn as_str(&self) -> &str {
105 &self.0
106 }
107
108 pub fn into_inner(self) -> String {
110 self.0
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.0.is_empty()
116 }
117}
118
119impl<'de> Deserialize<'de> for Prefix {
123 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
124 let s = String::deserialize(deserializer)?;
125 if s.is_empty() {
126 return Err(serde::de::Error::custom("Prefix must not be empty"));
127 }
128 Ok(Self(s))
129 }
130}
131
132impl fmt::Display for Prefix {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 f.write_str(&self.0)
135 }
136}
137
138impl AsRef<str> for Prefix {
139 fn as_ref(&self) -> &str {
140 &self.0
141 }
142}
143
144impl Borrow<str> for Prefix {
145 fn borrow(&self) -> &str {
146 &self.0
147 }
148}
149
150impl From<Prefix> for String {
151 fn from(p: Prefix) -> String {
152 p.0
153 }
154}
155
156impl PartialEq<str> for Prefix {
157 fn eq(&self, other: &str) -> bool {
158 self.0 == other
159 }
160}
161
162impl PartialEq<&str> for Prefix {
163 fn eq(&self, other: &&str) -> bool {
164 self.0 == *other
165 }
166}
167
168impl PartialEq<Prefix> for str {
169 fn eq(&self, other: &Prefix) -> bool {
170 self == other.0
171 }
172}
173
174impl PartialEq<Prefix> for &str {
175 fn eq(&self, other: &Prefix) -> bool {
176 *self == other.0
177 }
178}
179
180#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199#[repr(transparent)]
200pub struct Said(String);
201
202impl Said {
203 pub fn new(s: String) -> Result<Self, KeriTypeError> {
207 validate_said_derivation_code(&s)?;
208 Ok(Self(s))
209 }
210
211 pub fn new_unchecked(s: String) -> Self {
213 Self(s)
214 }
215
216 pub fn as_str(&self) -> &str {
218 &self.0
219 }
220
221 pub fn into_inner(self) -> String {
223 self.0
224 }
225
226 pub fn is_empty(&self) -> bool {
228 self.0.is_empty()
229 }
230}
231
232impl<'de> Deserialize<'de> for Said {
236 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
237 let s = String::deserialize(deserializer)?;
238 if s.is_empty() {
239 return Err(serde::de::Error::custom("Said must not be empty"));
240 }
241 Ok(Self(s))
242 }
243}
244
245impl fmt::Display for Said {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.write_str(&self.0)
248 }
249}
250
251impl AsRef<str> for Said {
252 fn as_ref(&self) -> &str {
253 &self.0
254 }
255}
256
257impl Borrow<str> for Said {
258 fn borrow(&self) -> &str {
259 &self.0
260 }
261}
262
263impl From<Said> for String {
264 fn from(s: Said) -> String {
265 s.0
266 }
267}
268
269impl PartialEq<str> for Said {
270 fn eq(&self, other: &str) -> bool {
271 self.0 == other
272 }
273}
274
275impl PartialEq<&str> for Said {
276 fn eq(&self, other: &&str) -> bool {
277 self.0 == *other
278 }
279}
280
281impl PartialEq<Said> for str {
282 fn eq(&self, other: &Said) -> bool {
283 self == other.0
284 }
285}
286
287impl PartialEq<Said> for &str {
288 fn eq(&self, other: &Said) -> bool {
289 *self == other.0
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Hash)]
308pub struct Fraction {
309 pub numerator: u64,
311 pub denominator: u64,
313}
314
315#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
317pub enum FractionError {
318 #[error("missing '/' separator in fraction: {0:?}")]
320 MissingSeparator(String),
321 #[error("invalid integer in fraction: {0}")]
323 InvalidInt(String),
324 #[error("fraction denominator must not be zero")]
326 ZeroDenominator,
327}
328
329impl Fraction {
330 pub fn sum_meets_one(fractions: &[&Fraction]) -> bool {
343 if fractions.is_empty() {
344 return false;
345 }
346 let mut num: u128 = 0;
349 let mut den: u128 = 1;
350 for f in fractions {
351 num = num * (f.denominator as u128) + (f.numerator as u128) * den;
354 den *= f.denominator as u128;
355 }
356 num >= den
358 }
359}
360
361impl FromStr for Fraction {
362 type Err = FractionError;
363
364 fn from_str(s: &str) -> Result<Self, Self::Err> {
365 let (num_str, den_str) = s
366 .split_once('/')
367 .ok_or_else(|| FractionError::MissingSeparator(s.to_string()))?;
368 let numerator: u64 = num_str
369 .parse()
370 .map_err(|_| FractionError::InvalidInt(num_str.to_string()))?;
371 let denominator: u64 = den_str
372 .parse()
373 .map_err(|_| FractionError::InvalidInt(den_str.to_string()))?;
374 if denominator == 0 {
375 return Err(FractionError::ZeroDenominator);
376 }
377 Ok(Self {
378 numerator,
379 denominator,
380 })
381 }
382}
383
384impl fmt::Display for Fraction {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 write!(f, "{}/{}", self.numerator, self.denominator)
387 }
388}
389
390impl Serialize for Fraction {
391 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
392 serializer.serialize_str(&self.to_string())
393 }
394}
395
396impl<'de> Deserialize<'de> for Fraction {
397 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
398 let s = String::deserialize(deserializer)?;
399 s.parse().map_err(serde::de::Error::custom)
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Eq)]
418pub enum Threshold {
419 Simple(u64),
421 Weighted(Vec<Vec<Fraction>>),
424}
425
426impl Threshold {
427 pub fn simple_value(&self) -> Option<u64> {
429 match self {
430 Threshold::Simple(v) => Some(*v),
431 Threshold::Weighted(_) => None,
432 }
433 }
434
435 pub fn validate_satisfiable(&self, count: usize) -> Result<(), KeriTypeError> {
453 match self {
454 Threshold::Simple(0) => Ok(()),
455 Threshold::Simple(n) if *n as usize > count => Err(KeriTypeError {
456 type_name: "Threshold",
457 reason: format!("simple threshold {n} exceeds list length {count}"),
458 }),
459 Threshold::Simple(_) => Ok(()),
460 Threshold::Weighted(clauses) => {
461 for clause in clauses {
462 if clause.len() != count {
463 return Err(KeriTypeError {
464 type_name: "Threshold",
465 reason: format!(
466 "weighted clause length {} != list length {count}",
467 clause.len()
468 ),
469 });
470 }
471 }
472 Ok(())
473 }
474 }
475 }
476
477 pub fn is_satisfied(&self, verified_indices: &[u32], key_count: usize) -> bool {
495 let mut unique: std::collections::HashSet<u32> = std::collections::HashSet::new();
497 for &idx in verified_indices {
498 if (idx as usize) < key_count {
499 unique.insert(idx);
500 }
501 }
502
503 match self {
504 Threshold::Simple(required) => unique.len() as u64 >= *required,
505 Threshold::Weighted(clauses) => {
506 for clause in clauses {
508 let verified_fractions: Vec<&Fraction> = clause
509 .iter()
510 .enumerate()
511 .filter(|(i, _)| unique.contains(&(*i as u32)))
512 .map(|(_, f)| f)
513 .collect();
514 if !Fraction::sum_meets_one(&verified_fractions) {
515 return false;
516 }
517 }
518 true
519 }
520 }
521 }
522}
523
524impl Serialize for Threshold {
525 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
526 match self {
527 Threshold::Simple(v) => serializer.serialize_str(&format!("{v:x}")),
528 Threshold::Weighted(clauses) => clauses.serialize(serializer),
529 }
530 }
531}
532
533impl<'de> Deserialize<'de> for Threshold {
534 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
535 let value = serde_json::Value::deserialize(deserializer)?;
536 match value {
537 serde_json::Value::String(s) => {
538 let v = u64::from_str_radix(&s, 16).map_err(|_| {
539 serde::de::Error::custom(format!("invalid hex threshold: {s:?}"))
540 })?;
541 Ok(Threshold::Simple(v))
542 }
543 serde_json::Value::Array(arr) => {
544 let clauses: Vec<Vec<Fraction>> = arr
545 .into_iter()
546 .map(|clause| match clause {
547 serde_json::Value::Array(weights) => weights
548 .into_iter()
549 .map(|w| match w {
550 serde_json::Value::String(s) => {
551 s.parse().map_err(serde::de::Error::custom)
552 }
553 _ => Err(serde::de::Error::custom("weight must be a string")),
554 })
555 .collect::<Result<Vec<_>, _>>(),
556 _ => Err(serde::de::Error::custom("clause must be an array")),
557 })
558 .collect::<Result<Vec<_>, _>>()?;
559 Ok(Threshold::Weighted(clauses))
560 }
561 _ => Err(serde::de::Error::custom(
562 "threshold must be a hex string or array of clause arrays",
563 )),
564 }
565 }
566}
567
568impl Default for Threshold {
569 fn default() -> Self {
570 Threshold::Simple(0)
571 }
572}
573
574#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
588#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
589#[repr(transparent)]
590pub struct CesrKey(String);
591
592impl CesrKey {
593 pub fn new_unchecked(s: String) -> Self {
595 Self(s)
596 }
597
598 pub fn parse(&self) -> Result<KeriPublicKey, KeriDecodeError> {
603 KeriPublicKey::parse(&self.0)
604 }
605
606 pub fn as_str(&self) -> &str {
608 &self.0
609 }
610
611 pub fn into_inner(self) -> String {
613 self.0
614 }
615}
616
617impl fmt::Display for CesrKey {
618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619 f.write_str(&self.0)
620 }
621}
622
623impl AsRef<str> for CesrKey {
624 fn as_ref(&self) -> &str {
625 &self.0
626 }
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
643#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
644pub enum ConfigTrait {
645 #[serde(rename = "EO")]
647 EstablishmentOnly,
648 #[serde(rename = "DND")]
650 DoNotDelegate,
651 #[serde(rename = "RB")]
653 RegistrarBackers,
654 #[serde(rename = "NRB")]
656 NoRegistrarBackers,
657}
658
659#[derive(Debug, Clone, PartialEq, Eq)]
672pub struct VersionString {
673 pub kind: String,
675 pub size: u32,
677}
678
679impl VersionString {
680 pub fn json(size: u32) -> Self {
682 Self {
683 kind: "JSON".to_string(),
684 size,
685 }
686 }
687
688 pub fn placeholder() -> Self {
690 Self {
691 kind: "JSON".to_string(),
692 size: 0,
693 }
694 }
695}
696
697impl Default for VersionString {
698 fn default() -> Self {
699 Self::placeholder()
700 }
701}
702
703impl fmt::Display for VersionString {
704 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705 write!(f, "KERI10{}{:06x}_", self.kind, self.size)
706 }
707}
708
709impl Serialize for VersionString {
710 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
711 serializer.serialize_str(&self.to_string())
712 }
713}
714
715impl<'de> Deserialize<'de> for VersionString {
716 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
717 let s = String::deserialize(deserializer)?;
718 if s.len() >= 17 && s.is_ascii() && s.ends_with('_') {
722 let size_hex = &s[10..16];
723 let size = u32::from_str_radix(size_hex, 16).map_err(|_| {
724 serde::de::Error::custom(format!("invalid version string size: {size_hex:?}"))
725 })?;
726 let kind = s[6..10].to_string();
727 Ok(Self { kind, size })
728 } else {
729 Err(serde::de::Error::custom(format!(
733 "invalid KERI version string: {s:?}"
734 )))
735 }
736 }
737}
738
739#[cfg(feature = "schema")]
742mod schema_impls {
743 use super::*;
744
745 impl schemars::JsonSchema for Fraction {
746 fn schema_name() -> String {
747 "Fraction".to_string()
748 }
749 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
750 schemars::schema::SchemaObject {
751 instance_type: Some(schemars::schema::InstanceType::String.into()),
752 ..Default::default()
753 }
754 .into()
755 }
756 }
757
758 impl schemars::JsonSchema for Threshold {
759 fn schema_name() -> String {
760 "Threshold".to_string()
761 }
762 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
763 schemars::schema::Schema::Bool(true)
765 }
766 }
767
768 impl schemars::JsonSchema for crate::events::Seal {
769 fn schema_name() -> String {
770 "Seal".to_string()
771 }
772 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
773 schemars::schema::Schema::Bool(true)
775 }
776 }
777
778 impl schemars::JsonSchema for VersionString {
779 fn schema_name() -> String {
780 "VersionString".to_string()
781 }
782 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
783 schemars::schema::SchemaObject {
784 instance_type: Some(schemars::schema::InstanceType::String.into()),
785 ..Default::default()
786 }
787 .into()
788 }
789 }
790}
791
792#[cfg(test)]
793#[allow(clippy::unwrap_used)]
794mod tests {
795 use super::*;
796
797 #[test]
800 fn fraction_parse_valid() {
801 let f: Fraction = "1/3".parse().unwrap();
802 assert_eq!(f.numerator, 1);
803 assert_eq!(f.denominator, 3);
804 }
805
806 #[test]
807 fn fraction_parse_rejects_zero_denominator() {
808 let err = "1/0".parse::<Fraction>().unwrap_err();
809 assert_eq!(err, FractionError::ZeroDenominator);
810 }
811
812 #[test]
813 fn fraction_parse_rejects_missing_separator() {
814 assert!("42".parse::<Fraction>().is_err());
815 }
816
817 #[test]
818 fn fraction_serde_roundtrip() {
819 let f = Fraction {
820 numerator: 1,
821 denominator: 2,
822 };
823 let json = serde_json::to_string(&f).unwrap();
824 assert_eq!(json, "\"1/2\"");
825 let parsed: Fraction = serde_json::from_str(&json).unwrap();
826 assert_eq!(parsed, f);
827 }
828
829 #[test]
830 fn fraction_display() {
831 let f = Fraction {
832 numerator: 3,
833 denominator: 4,
834 };
835 assert_eq!(f.to_string(), "3/4");
836 }
837
838 #[test]
841 fn threshold_simple_from_hex() {
842 let t: Threshold = serde_json::from_str("\"a\"").unwrap();
843 assert_eq!(t, Threshold::Simple(10));
844 assert_eq!(t.simple_value(), Some(10));
845 }
846
847 #[test]
848 fn threshold_simple_serialize_as_hex() {
849 let t = Threshold::Simple(16);
850 let json = serde_json::to_string(&t).unwrap();
851 assert_eq!(json, "\"10\""); }
853
854 #[test]
855 fn threshold_simple_roundtrip() {
856 let t = Threshold::Simple(2);
857 let json = serde_json::to_string(&t).unwrap();
858 let parsed: Threshold = serde_json::from_str(&json).unwrap();
859 assert_eq!(parsed, t);
860 }
861
862 #[test]
863 fn threshold_weighted_roundtrip() {
864 let json = r#"[["1/2","1/2"],["1/3","1/3","1/3"]]"#;
865 let t: Threshold = serde_json::from_str(json).unwrap();
866 assert!(t.simple_value().is_none());
867 if let Threshold::Weighted(clauses) = &t {
868 assert_eq!(clauses.len(), 2);
869 assert_eq!(clauses[0].len(), 2);
870 assert_eq!(clauses[1].len(), 3);
871 assert_eq!(clauses[0][0].numerator, 1);
872 assert_eq!(clauses[0][0].denominator, 2);
873 } else {
874 panic!("expected Weighted");
875 }
876 let reserialized = serde_json::to_string(&t).unwrap();
877 let reparsed: Threshold = serde_json::from_str(&reserialized).unwrap();
878 assert_eq!(reparsed, t);
879 }
880
881 #[test]
882 fn threshold_rejects_invalid_hex() {
883 let result = serde_json::from_str::<Threshold>("\"xyz\"");
884 assert!(result.is_err());
885 }
886
887 #[test]
890 fn fraction_sum_one_third_times_three() {
891 let f: Fraction = "1/3".parse().unwrap();
892 assert!(Fraction::sum_meets_one(&[&f, &f, &f]));
893 }
894
895 #[test]
896 fn fraction_sum_two_thirds_not_enough() {
897 let f: Fraction = "1/3".parse().unwrap();
898 assert!(!Fraction::sum_meets_one(&[&f, &f]));
899 }
900
901 #[test]
902 fn fraction_sum_halves() {
903 let f: Fraction = "1/2".parse().unwrap();
904 assert!(Fraction::sum_meets_one(&[&f, &f]));
905 assert!(!Fraction::sum_meets_one(&[&f]));
906 }
907
908 #[test]
909 fn fraction_sum_empty_is_false() {
910 assert!(!Fraction::sum_meets_one(&[]));
911 }
912
913 #[test]
916 fn threshold_simple_satisfied() {
917 let t = Threshold::Simple(2);
918 assert!(t.is_satisfied(&[0, 1], 3));
919 assert!(t.is_satisfied(&[0, 1, 2], 3));
920 assert!(!t.is_satisfied(&[0], 3));
921 }
922
923 #[test]
924 fn threshold_simple_zero_always_satisfied() {
925 let t = Threshold::Simple(0);
926 assert!(t.is_satisfied(&[], 3));
927 }
928
929 #[test]
930 fn threshold_simple_deduplicates_indices() {
931 let t = Threshold::Simple(2);
932 assert!(!t.is_satisfied(&[0, 0], 3));
934 }
935
936 #[test]
937 fn threshold_simple_rejects_out_of_range() {
938 let t = Threshold::Simple(1);
939 assert!(!t.is_satisfied(&[5], 3));
941 }
942
943 #[test]
944 fn threshold_weighted_two_of_three() {
945 let t: Threshold = serde_json::from_str(r#"[["1/2","1/2","1/2"]]"#).unwrap();
947 assert!(t.is_satisfied(&[0, 1], 3));
948 assert!(t.is_satisfied(&[1, 2], 3));
949 assert!(!t.is_satisfied(&[0], 3));
950 }
951
952 #[test]
953 fn threshold_weighted_with_reserves() {
954 let t: Threshold = serde_json::from_str(r#"[["1/2","1/2","1/2","1/4","1/4"]]"#).unwrap();
956 assert!(t.is_satisfied(&[0, 1], 5)); assert!(t.is_satisfied(&[0, 3, 4], 5)); assert!(!t.is_satisfied(&[3, 4], 5)); }
960
961 #[test]
962 fn threshold_weighted_multi_clause_and() {
963 let t: Threshold = serde_json::from_str(r#"[["1/2","1/2"],["1/3","1/3","1/3"]]"#).unwrap();
965 assert!(!t.is_satisfied(&[0, 1], 3));
968 assert!(t.is_satisfied(&[0, 1, 2], 3));
970 }
971
972 #[test]
975 fn cesr_key_roundtrip() {
976 let key_str = "DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
977 let key = CesrKey::new_unchecked(key_str.to_string());
978 let json = serde_json::to_string(&key).unwrap();
979 let parsed: CesrKey = serde_json::from_str(&json).unwrap();
980 assert_eq!(parsed.as_str(), key_str);
981 }
982
983 #[test]
984 fn cesr_key_parse_valid() {
985 let key =
986 CesrKey::new_unchecked("DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string());
987 assert!(key.parse().is_ok());
988 }
989
990 #[test]
991 fn cesr_key_parse_invalid() {
992 let key = CesrKey::new_unchecked("not-a-valid-key".to_string());
993 assert!(key.parse().is_err());
994 }
995
996 #[test]
999 fn config_trait_serde_roundtrip() {
1000 let traits = vec![ConfigTrait::EstablishmentOnly, ConfigTrait::DoNotDelegate];
1001 let json = serde_json::to_string(&traits).unwrap();
1002 assert_eq!(json, r#"["EO","DND"]"#);
1003 let parsed: Vec<ConfigTrait> = serde_json::from_str(&json).unwrap();
1004 assert_eq!(parsed, traits);
1005 }
1006
1007 #[test]
1008 fn config_trait_all_variants_roundtrip() {
1009 let all = vec![
1010 ConfigTrait::EstablishmentOnly,
1011 ConfigTrait::DoNotDelegate,
1012 ConfigTrait::RegistrarBackers,
1013 ConfigTrait::NoRegistrarBackers,
1014 ];
1015 let json = serde_json::to_string(&all).unwrap();
1016 assert_eq!(json, r#"["EO","DND","RB","NRB"]"#);
1017 let parsed: Vec<ConfigTrait> = serde_json::from_str(&json).unwrap();
1018 assert_eq!(parsed, all);
1019 }
1020
1021 #[test]
1024 fn version_string_display() {
1025 let vs = VersionString::json(256);
1026 assert_eq!(vs.to_string(), "KERI10JSON000100_");
1027 }
1028
1029 #[test]
1030 fn version_string_placeholder() {
1031 let vs = VersionString::placeholder();
1032 assert_eq!(vs.to_string(), "KERI10JSON000000_");
1033 assert_eq!(vs.size, 0);
1034 }
1035
1036 #[test]
1037 fn version_string_parse_full() {
1038 let vs: VersionString = serde_json::from_str("\"KERI10JSON000100_\"").unwrap();
1039 assert_eq!(vs.kind, "JSON");
1040 assert_eq!(vs.size, 256);
1041 }
1042
1043 #[test]
1044 fn version_string_rejects_legacy_short() {
1045 assert!(serde_json::from_str::<VersionString>("\"KERI10JSON\"").is_err());
1049 assert!(serde_json::from_str::<VersionString>("\"KERI10JSON0001\"").is_err());
1050 }
1051
1052 #[test]
1053 fn version_string_roundtrip() {
1054 let vs = VersionString::json(1024);
1055 let json = serde_json::to_string(&vs).unwrap();
1056 let parsed: VersionString = serde_json::from_str(&json).unwrap();
1057 assert_eq!(parsed, vs);
1058 }
1059
1060 #[test]
1061 fn version_string_rejects_invalid() {
1062 assert!(serde_json::from_str::<VersionString>("\"INVALID\"").is_err());
1063 }
1064
1065 #[test]
1066 fn version_string_rejects_multibyte_without_panicking() {
1067 assert!(serde_json::from_str::<VersionString>("\"KERI1ЬSON00012b_\"").is_err());
1071 assert!(serde_json::from_str::<VersionString>("\"KERI10JSON00Ь1b_\"").is_err());
1073 }
1074}