1use crate::schema::graph::{SchemaGraph, SchemaTypeDef, TypedSchemaValue};
30use crate::schema::metadata::{MetadataEnvelope, TypeId};
31use crate::schema::schema_type::{SchemaType, VariantCaseType};
32use crate::schema::schema_value::{
33 BinaryValuePayload, DurationValuePayload, SchemaValue, TextValuePayload, VariantValuePayload,
34};
35use crate::schema::validation::{SchemaError, validate_graph};
36use std::collections::{BTreeMap, HashMap, HashSet};
37use std::fmt::{self, Display, Formatter};
38
39#[cfg(feature = "bigdecimal")]
40mod bigdecimal;
41#[cfg(feature = "bit_vec")]
42mod bit_vec;
43#[cfg(feature = "bytes")]
44mod bytes;
45#[cfg(feature = "chrono")]
46mod chrono;
47#[cfg(feature = "mac_address")]
48mod mac_address;
49#[cfg(feature = "nonempty_collections")]
50mod nonempty_collections;
51#[cfg(feature = "num_bigint")]
52mod num_bigint;
53#[cfg(feature = "rust_decimal")]
54mod rust_decimal;
55#[cfg(feature = "serde_json_types")]
56mod serde_json_types;
57#[cfg(feature = "url")]
58mod url;
59
60mod quantity;
61
62pub use quantity::{Quantity, QuantityUnit};
63
64pub trait IntoSchema {
83 fn type_id() -> TypeId;
84 fn register_in(builder: &mut SchemaBuilder) -> SchemaType;
85 fn to_value(&self) -> SchemaValue;
86}
87
88pub trait FromSchema: Sized {
90 fn from_value(value: &SchemaValue) -> Result<Self, FromSchemaError>;
91}
92
93pub fn try_into_schema_graph<T: IntoSchema + ?Sized>() -> Result<SchemaGraph, SchemaError> {
97 let mut builder = SchemaBuilder::new();
98 let root = T::register_in(&mut builder);
99 let graph = SchemaGraph {
100 defs: builder.finish(),
101 root,
102 };
103 match validate_graph(&graph) {
104 Ok(()) => Ok(graph),
105 Err(mut errors) => Err(errors.remove(0)),
106 }
107}
108
109pub fn try_into_typed_schema_value<T: IntoSchema + ?Sized>(
118 value: &T,
119) -> Result<TypedSchemaValue, SchemaError> {
120 let graph = try_into_schema_graph::<T>()?;
121 Ok(TypedSchemaValue::new(graph, value.to_value()))
122}
123
124pub trait IntoTypedSchemaValue: IntoSchema {
128 #[allow(clippy::wrong_self_convention)]
129 fn into_typed_schema_value(&self) -> Result<TypedSchemaValue, SchemaError>;
130}
131
132impl<T: IntoSchema + ?Sized> IntoTypedSchemaValue for T {
133 fn into_typed_schema_value(&self) -> Result<TypedSchemaValue, SchemaError> {
134 try_into_typed_schema_value(self)
135 }
136}
137
138#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum MergeError {
141 ConflictingDefinitions {
146 id: TypeId,
147 first_name: Option<String>,
148 second_name: Option<String>,
149 },
150}
151
152impl Display for MergeError {
153 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
154 match self {
155 MergeError::ConflictingDefinitions {
156 id,
157 first_name,
158 second_name,
159 } => write!(
160 f,
161 "conflicting definitions for TypeId `{id}` while merging agent graphs \
162 (first name = {first_name:?}, second name = {second_name:?})"
163 ),
164 }
165 }
166}
167
168impl std::error::Error for MergeError {}
169
170pub fn merge_agent_graphs(
204 graphs: impl IntoIterator<Item = SchemaGraph>,
205) -> Result<SchemaGraph, MergeError> {
206 let mut merged: Vec<SchemaTypeDef> = Vec::new();
207
208 for graph in graphs {
209 for def in graph.defs {
210 if let Some(existing) = merged.iter().find(|d| d.id == def.id) {
211 if existing.body != def.body {
212 return Err(MergeError::ConflictingDefinitions {
213 id: def.id,
214 first_name: existing.name.clone(),
215 second_name: def.name,
216 });
217 }
218 } else {
220 merged.push(def);
221 }
222 }
223 }
224
225 Ok(SchemaGraph {
226 defs: merged,
227 root: SchemaGraph::empty().root,
228 })
229}
230
231#[derive(Default)]
243pub struct SchemaBuilder {
244 defs: Vec<SchemaTypeDef>,
245 reserved: HashSet<TypeId>,
246}
247
248impl SchemaBuilder {
249 pub fn new() -> Self {
250 Self::default()
251 }
252
253 pub fn is_registered(&self, id: &TypeId) -> bool {
255 self.reserved.contains(id) || self.defs.iter().any(|d| &d.id == id)
256 }
257
258 pub fn reserve(&mut self, id: TypeId) {
262 self.reserved.insert(id);
263 }
264
265 pub fn commit(
271 &mut self,
272 id: TypeId,
273 name: Option<String>,
274 metadata: MetadataEnvelope,
275 body: SchemaType,
276 ) {
277 self.reserved.remove(&id);
278 let body = body.with_metadata(metadata);
279 if let Some(existing) = self.defs.iter_mut().find(|d| d.id == id) {
283 *existing = SchemaTypeDef { id, name, body };
284 } else {
285 self.defs.push(SchemaTypeDef { id, name, body });
286 }
287 }
288
289 pub fn finish(self) -> Vec<SchemaTypeDef> {
291 self.defs
292 }
293
294 pub fn into_graph(self, root: SchemaType) -> SchemaGraph {
296 SchemaGraph {
297 defs: self.finish(),
298 root,
299 }
300 }
301}
302
303pub fn normalize_type_path(raw: &str) -> String {
313 raw.replace("::", ".")
314}
315
316pub fn type_id_with_args(base: &str, args: &[TypeId]) -> TypeId {
323 if args.is_empty() {
324 TypeId::new(base.to_string())
325 } else {
326 let mut s = String::with_capacity(base.len() + 16);
327 s.push_str(base);
328 s.push('<');
329 for (i, a) in args.iter().enumerate() {
330 if i > 0 {
331 s.push_str(", ");
332 }
333 s.push_str(a.as_str());
334 }
335 s.push('>');
336 TypeId::new(s)
337 }
338}
339
340pub fn default_type_id_from(raw: &str) -> TypeId {
344 TypeId::new(normalize_type_path(raw))
345}
346
347#[derive(Clone, Debug, PartialEq, Eq)]
353pub enum FromSchemaError {
354 ShapeMismatch {
356 expected: String,
357 actual: String,
358 context: String,
359 },
360 OutOfRange {
362 index: u32,
363 len: u32,
364 context: String,
365 },
366 UnknownUnionTag(String),
368 Custom(String),
370}
371
372pub type DecodeError = FromSchemaError;
374
375impl FromSchemaError {
376 pub fn shape_mismatch(
377 expected: impl Into<String>,
378 actual: impl Into<String>,
379 context: impl Into<String>,
380 ) -> Self {
381 Self::ShapeMismatch {
382 expected: expected.into(),
383 actual: actual.into(),
384 context: context.into(),
385 }
386 }
387
388 pub fn out_of_range(index: u32, len: u32, context: impl Into<String>) -> Self {
389 Self::OutOfRange {
390 index,
391 len,
392 context: context.into(),
393 }
394 }
395
396 pub fn custom(msg: impl Into<String>) -> Self {
397 Self::Custom(msg.into())
398 }
399}
400
401impl Display for FromSchemaError {
402 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
403 match self {
404 FromSchemaError::ShapeMismatch {
405 expected,
406 actual,
407 context,
408 } => write!(
409 f,
410 "shape mismatch in {context}: expected {expected}, got {actual}"
411 ),
412 FromSchemaError::OutOfRange {
413 index,
414 len,
415 context,
416 } => write!(f, "index {index} out of range (len {len}) in {context}"),
417 FromSchemaError::UnknownUnionTag(tag) => write!(f, "unknown union tag `{tag}`"),
418 FromSchemaError::Custom(msg) => f.write_str(msg),
419 }
420 }
421}
422
423impl std::error::Error for FromSchemaError {}
424
425pub fn value_kind(v: &SchemaValue) -> &'static str {
427 match v {
428 SchemaValue::Bool(_) => "bool",
429 SchemaValue::S8(_) => "s8",
430 SchemaValue::S16(_) => "s16",
431 SchemaValue::S32(_) => "s32",
432 SchemaValue::S64(_) => "s64",
433 SchemaValue::U8(_) => "u8",
434 SchemaValue::U16(_) => "u16",
435 SchemaValue::U32(_) => "u32",
436 SchemaValue::U64(_) => "u64",
437 SchemaValue::F32(_) => "f32",
438 SchemaValue::F64(_) => "f64",
439 SchemaValue::Char(_) => "char",
440 SchemaValue::String(_) => "string",
441 SchemaValue::Record { .. } => "record",
442 SchemaValue::Variant(_) => "variant",
443 SchemaValue::Enum { .. } => "enum",
444 SchemaValue::Flags { .. } => "flags",
445 SchemaValue::Tuple { .. } => "tuple",
446 SchemaValue::List { .. } => "list",
447 SchemaValue::FixedList { .. } => "fixed-list",
448 SchemaValue::Map { .. } => "map",
449 SchemaValue::Option { .. } => "option",
450 SchemaValue::Result(_) => "result",
451 SchemaValue::Text(_) => "text",
452 SchemaValue::Binary(_) => "binary",
453 SchemaValue::Path { .. } => "path",
454 SchemaValue::Url { .. } => "url",
455 SchemaValue::Uuid(_) => "uuid",
456 SchemaValue::Datetime { .. } => "datetime",
457 SchemaValue::Duration(_) => "duration",
458 SchemaValue::Quantity(_) => "quantity",
459 SchemaValue::Union(_) => "union",
460 SchemaValue::Secret(_) => "secret",
461 SchemaValue::QuotaToken(_) => "quota-token",
462 SchemaValue::Stream(_) => "stream",
463 SchemaValue::PermissionCard(_) => "permission-card",
464 }
465}
466
467pub fn text_to_value(body: String, language: Option<String>) -> SchemaValue {
473 SchemaValue::Text(TextValuePayload {
474 text: body,
475 language,
476 })
477}
478
479pub fn text_from_value(v: &SchemaValue, context: &'static str) -> Result<String, FromSchemaError> {
480 match v {
481 SchemaValue::Text(p) => Ok(p.text.clone()),
482 other => Err(FromSchemaError::shape_mismatch(
483 "text",
484 value_kind(other),
485 context,
486 )),
487 }
488}
489
490pub fn binary_to_value(bytes: Vec<u8>, mime_type: Option<String>) -> SchemaValue {
491 SchemaValue::Binary(BinaryValuePayload { bytes, mime_type })
492}
493
494pub fn binary_from_value(
495 v: &SchemaValue,
496 context: &'static str,
497) -> Result<Vec<u8>, FromSchemaError> {
498 match v {
499 SchemaValue::Binary(p) => Ok(p.bytes.clone()),
500 other => Err(FromSchemaError::shape_mismatch(
501 "binary",
502 value_kind(other),
503 context,
504 )),
505 }
506}
507
508pub fn path_to_value(path: String) -> SchemaValue {
509 SchemaValue::Path { path }
510}
511
512pub fn path_from_value(v: &SchemaValue, context: &'static str) -> Result<String, FromSchemaError> {
513 match v {
514 SchemaValue::Path { path } => Ok(path.clone()),
515 other => Err(FromSchemaError::shape_mismatch(
516 "path",
517 value_kind(other),
518 context,
519 )),
520 }
521}
522
523pub fn url_to_value(url: String) -> SchemaValue {
524 SchemaValue::Url { url }
525}
526
527pub fn url_from_value(v: &SchemaValue, context: &'static str) -> Result<String, FromSchemaError> {
528 match v {
529 SchemaValue::Url { url } => Ok(url.clone()),
530 other => Err(FromSchemaError::shape_mismatch(
531 "url",
532 value_kind(other),
533 context,
534 )),
535 }
536}
537
538macro_rules! impl_primitive {
543 ($ty:ty, $type_id:literal, $schema_ty:expr, $variant:ident) => {
544 impl IntoSchema for $ty {
545 fn type_id() -> TypeId {
546 TypeId::new($type_id)
547 }
548 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
549 $schema_ty
550 }
551 fn to_value(&self) -> SchemaValue {
552 SchemaValue::$variant(*self)
553 }
554 }
555 impl FromSchema for $ty {
556 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
557 match v {
558 SchemaValue::$variant(x) => Ok(*x),
559 other => Err(FromSchemaError::shape_mismatch(
560 stringify!($variant),
561 value_kind(other),
562 stringify!($ty),
563 )),
564 }
565 }
566 }
567 };
568}
569
570impl_primitive!(bool, "bool", SchemaType::bool(), Bool);
571impl_primitive!(i8, "i8", SchemaType::s8(), S8);
572impl_primitive!(i16, "i16", SchemaType::s16(), S16);
573impl_primitive!(i32, "i32", SchemaType::s32(), S32);
574impl_primitive!(i64, "i64", SchemaType::s64(), S64);
575impl_primitive!(u8, "u8", SchemaType::u8(), U8);
576impl_primitive!(u16, "u16", SchemaType::u16(), U16);
577impl_primitive!(u32, "u32", SchemaType::u32(), U32);
578impl_primitive!(u64, "u64", SchemaType::u64(), U64);
579impl_primitive!(f32, "f32", SchemaType::f32(), F32);
580impl_primitive!(f64, "f64", SchemaType::f64(), F64);
581impl_primitive!(char, "char", SchemaType::char(), Char);
582
583impl IntoSchema for String {
584 fn type_id() -> TypeId {
585 TypeId::new("alloc.string.String")
586 }
587 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
588 SchemaType::string()
589 }
590 fn to_value(&self) -> SchemaValue {
591 SchemaValue::String(self.clone())
592 }
593}
594
595impl FromSchema for String {
596 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
597 match v {
598 SchemaValue::String(s) => Ok(s.clone()),
599 other => Err(FromSchemaError::shape_mismatch(
600 "string",
601 value_kind(other),
602 "String",
603 )),
604 }
605 }
606}
607
608impl IntoSchema for str {
612 fn type_id() -> TypeId {
613 TypeId::new("alloc.string.String")
614 }
615 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
616 SchemaType::string()
617 }
618 fn to_value(&self) -> SchemaValue {
619 SchemaValue::String(self.to_string())
620 }
621}
622
623impl<T: IntoSchema + ?Sized> IntoSchema for &T {
627 fn type_id() -> TypeId {
628 T::type_id()
629 }
630 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
631 T::register_in(b)
632 }
633 fn to_value(&self) -> SchemaValue {
634 (**self).to_value()
635 }
636}
637
638impl<T: IntoSchema + ?Sized> IntoSchema for Box<T> {
643 fn type_id() -> TypeId {
644 T::type_id()
645 }
646 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
647 T::register_in(b)
648 }
649 fn to_value(&self) -> SchemaValue {
650 (**self).to_value()
651 }
652}
653
654impl<T: FromSchema> FromSchema for Box<T> {
655 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
656 Ok(Box::new(T::from_value(v)?))
657 }
658}
659
660impl<T: IntoSchema + ?Sized> IntoSchema for std::rc::Rc<T> {
661 fn type_id() -> TypeId {
662 T::type_id()
663 }
664 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
665 T::register_in(b)
666 }
667 fn to_value(&self) -> SchemaValue {
668 (**self).to_value()
669 }
670}
671
672impl<T: FromSchema> FromSchema for std::rc::Rc<T> {
673 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
674 Ok(std::rc::Rc::new(T::from_value(v)?))
675 }
676}
677
678impl<T: IntoSchema + ?Sized> IntoSchema for std::sync::Arc<T> {
679 fn type_id() -> TypeId {
680 T::type_id()
681 }
682 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
683 T::register_in(b)
684 }
685 fn to_value(&self) -> SchemaValue {
686 (**self).to_value()
687 }
688}
689
690impl<T: FromSchema> FromSchema for std::sync::Arc<T> {
691 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
692 Ok(std::sync::Arc::new(T::from_value(v)?))
693 }
694}
695
696impl<T: IntoSchema> IntoSchema for Option<T> {
697 fn type_id() -> TypeId {
698 type_id_with_args("core.option.Option", &[T::type_id()])
699 }
700 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
701 SchemaType::option(T::register_in(b))
702 }
703 fn to_value(&self) -> SchemaValue {
704 SchemaValue::Option {
705 inner: self.as_ref().map(|x| Box::new(x.to_value())),
706 }
707 }
708}
709
710impl<T: FromSchema> FromSchema for Option<T> {
711 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
712 match v {
713 SchemaValue::Option { inner: Some(x) } => Ok(Some(T::from_value(x)?)),
714 SchemaValue::Option { inner: None } => Ok(None),
715 other => Err(FromSchemaError::shape_mismatch(
716 "option",
717 value_kind(other),
718 "Option",
719 )),
720 }
721 }
722}
723
724impl<T: IntoSchema> IntoSchema for Vec<T> {
725 fn type_id() -> TypeId {
726 type_id_with_args("alloc.vec.Vec", &[T::type_id()])
727 }
728 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
729 SchemaType::list(T::register_in(b))
730 }
731 fn to_value(&self) -> SchemaValue {
732 SchemaValue::List {
733 elements: self.iter().map(|x| x.to_value()).collect(),
734 }
735 }
736}
737
738impl<T: FromSchema> FromSchema for Vec<T> {
739 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
740 match v {
741 SchemaValue::List { elements } => elements.iter().map(T::from_value).collect(),
742 other => Err(FromSchemaError::shape_mismatch(
743 "list",
744 value_kind(other),
745 "Vec",
746 )),
747 }
748 }
749}
750
751impl<T: IntoSchema> IntoSchema for HashSet<T> {
752 fn type_id() -> TypeId {
753 type_id_with_args("std.collections.HashSet", &[T::type_id()])
754 }
755 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
756 SchemaType::list(T::register_in(b))
757 }
758 fn to_value(&self) -> SchemaValue {
759 SchemaValue::List {
760 elements: self.iter().map(|x| x.to_value()).collect(),
761 }
762 }
763}
764
765impl<T: FromSchema + Eq + std::hash::Hash> FromSchema for HashSet<T> {
766 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
767 match v {
768 SchemaValue::List { elements } => elements.iter().map(T::from_value).collect(),
769 other => Err(FromSchemaError::shape_mismatch(
770 "list",
771 value_kind(other),
772 "HashSet",
773 )),
774 }
775 }
776}
777
778impl<K: IntoSchema, V: IntoSchema> IntoSchema for HashMap<K, V> {
779 fn type_id() -> TypeId {
780 type_id_with_args("std.collections.HashMap", &[K::type_id(), V::type_id()])
781 }
782 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
783 SchemaType::map(K::register_in(b), V::register_in(b))
784 }
785 fn to_value(&self) -> SchemaValue {
786 SchemaValue::Map {
787 entries: self
788 .iter()
789 .map(|(k, v)| (k.to_value(), v.to_value()))
790 .collect(),
791 }
792 }
793}
794
795impl<K, V> FromSchema for HashMap<K, V>
796where
797 K: FromSchema + std::hash::Hash + Eq,
798 V: FromSchema,
799{
800 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
801 match v {
802 SchemaValue::Map { entries } => entries
803 .iter()
804 .map(|(k, v)| Ok((K::from_value(k)?, V::from_value(v)?)))
805 .collect(),
806 other => Err(FromSchemaError::shape_mismatch(
807 "map",
808 value_kind(other),
809 "HashMap",
810 )),
811 }
812 }
813}
814
815impl<K: IntoSchema, V: IntoSchema> IntoSchema for BTreeMap<K, V> {
816 fn type_id() -> TypeId {
817 type_id_with_args("alloc.collections.BTreeMap", &[K::type_id(), V::type_id()])
818 }
819 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
820 SchemaType::map(K::register_in(b), V::register_in(b))
821 }
822 fn to_value(&self) -> SchemaValue {
823 SchemaValue::Map {
824 entries: self
825 .iter()
826 .map(|(k, v)| (k.to_value(), v.to_value()))
827 .collect(),
828 }
829 }
830}
831
832impl<K, V> FromSchema for BTreeMap<K, V>
833where
834 K: FromSchema + Ord,
835 V: FromSchema,
836{
837 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
838 match v {
839 SchemaValue::Map { entries } => entries
840 .iter()
841 .map(|(k, v)| Ok((K::from_value(k)?, V::from_value(v)?)))
842 .collect(),
843 other => Err(FromSchemaError::shape_mismatch(
844 "map",
845 value_kind(other),
846 "BTreeMap",
847 )),
848 }
849 }
850}
851
852macro_rules! impl_tuple {
857 ( $name:literal => $( $idx:tt : $t:ident ),+ ) => {
858 impl< $( $t : IntoSchema ),+ > IntoSchema for ( $( $t, )+ ) {
859 fn type_id() -> TypeId {
860 type_id_with_args($name, &[ $( <$t as IntoSchema>::type_id() ),+ ])
861 }
862 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
863 SchemaType::tuple(vec![ $( <$t as IntoSchema>::register_in(b) ),+ ])
864 }
865 fn to_value(&self) -> SchemaValue {
866 SchemaValue::Tuple {
867 elements: vec![ $( IntoSchema::to_value(&self.$idx) ),+ ],
868 }
869 }
870 }
871 impl< $( $t : FromSchema ),+ > FromSchema for ( $( $t, )+ ) {
872 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
873 match v {
874 SchemaValue::Tuple { elements } => {
875 let expected = [ $( stringify!($t) ),+ ].len();
876 if elements.len() != expected {
877 return Err(FromSchemaError::custom(format!(
878 "tuple length mismatch: expected {expected}, got {}",
879 elements.len()
880 )));
881 }
882 Ok(( $( <$t as FromSchema>::from_value(&elements[$idx])? , )+ ))
883 }
884 other => Err(FromSchemaError::shape_mismatch(
885 "tuple",
886 value_kind(other),
887 "tuple",
888 )),
889 }
890 }
891 }
892 };
893}
894
895impl_tuple!("core.tuple.Tuple1" => 0: T0);
896impl_tuple!("core.tuple.Tuple2" => 0: T0, 1: T1);
897impl_tuple!("core.tuple.Tuple3" => 0: T0, 1: T1, 2: T2);
898impl_tuple!("core.tuple.Tuple4" => 0: T0, 1: T1, 2: T2, 3: T3);
899impl_tuple!("core.tuple.Tuple5" => 0: T0, 1: T1, 2: T2, 3: T3, 4: T4);
900impl_tuple!("core.tuple.Tuple6" => 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5);
901impl_tuple!("core.tuple.Tuple7" => 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6);
902impl_tuple!("core.tuple.Tuple8" => 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7);
903
904impl IntoSchema for () {
905 fn type_id() -> TypeId {
906 TypeId::new("core.tuple.Unit")
907 }
908 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
909 SchemaType::tuple(vec![])
910 }
911 fn to_value(&self) -> SchemaValue {
912 SchemaValue::Tuple { elements: vec![] }
913 }
914}
915
916impl FromSchema for () {
917 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
918 match v {
919 SchemaValue::Tuple { elements } if elements.is_empty() => Ok(()),
920 other => Err(FromSchemaError::shape_mismatch(
921 "unit tuple",
922 value_kind(other),
923 "()",
924 )),
925 }
926 }
927}
928
929fn is_unit_type<T: IntoSchema>() -> bool {
936 T::type_id() == <() as IntoSchema>::type_id()
937}
938
939impl<T: IntoSchema, E: IntoSchema> IntoSchema for Result<T, E> {
940 fn type_id() -> TypeId {
941 type_id_with_args("core.result.Result", &[T::type_id(), E::type_id()])
942 }
943 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
944 let ok = if is_unit_type::<T>() {
945 None
946 } else {
947 Some(Box::new(T::register_in(b)))
948 };
949 let err = if is_unit_type::<E>() {
950 None
951 } else {
952 Some(Box::new(E::register_in(b)))
953 };
954 SchemaType::result(crate::schema::schema_type::ResultSpec { ok, err })
955 }
956 fn to_value(&self) -> SchemaValue {
957 use crate::schema::schema_value::ResultValuePayload;
958 match self {
959 Ok(x) => SchemaValue::Result(ResultValuePayload::Ok {
960 value: if is_unit_type::<T>() {
961 None
962 } else {
963 Some(Box::new(x.to_value()))
964 },
965 }),
966 Err(e) => SchemaValue::Result(ResultValuePayload::Err {
967 value: if is_unit_type::<E>() {
968 None
969 } else {
970 Some(Box::new(e.to_value()))
971 },
972 }),
973 }
974 }
975}
976
977impl<T: FromSchema, E: FromSchema> FromSchema for Result<T, E> {
978 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
979 use crate::schema::schema_value::ResultValuePayload;
980 let unit = SchemaValue::Tuple { elements: vec![] };
981 match v {
982 SchemaValue::Result(ResultValuePayload::Ok { value }) => {
983 let inner = value.as_deref().unwrap_or(&unit);
984 Ok(Ok(T::from_value(inner)?))
985 }
986 SchemaValue::Result(ResultValuePayload::Err { value }) => {
987 let inner = value.as_deref().unwrap_or(&unit);
988 Ok(Err(E::from_value(inner)?))
989 }
990 other => Err(FromSchemaError::shape_mismatch(
991 "result",
992 value_kind(other),
993 "Result",
994 )),
995 }
996 }
997}
998
999impl IntoSchema for ::chrono::DateTime<::chrono::Utc> {
1004 fn type_id() -> TypeId {
1005 TypeId::new("chrono.DateTime<chrono.Utc>")
1006 }
1007 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
1008 SchemaType::datetime()
1009 }
1010 fn to_value(&self) -> SchemaValue {
1011 SchemaValue::Datetime { value: *self }
1012 }
1013}
1014
1015impl FromSchema for ::chrono::DateTime<::chrono::Utc> {
1016 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1017 match v {
1018 SchemaValue::Datetime { value } => Ok(*value),
1019 other => Err(FromSchemaError::shape_mismatch(
1020 "datetime",
1021 value_kind(other),
1022 "DateTime<Utc>",
1023 )),
1024 }
1025 }
1026}
1027
1028impl IntoSchema for std::time::Duration {
1029 fn type_id() -> TypeId {
1030 TypeId::new("core.time.Duration")
1031 }
1032 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
1033 SchemaType::duration()
1034 }
1035 fn to_value(&self) -> SchemaValue {
1036 let nanos = self.as_nanos();
1037 let clamped: i64 = if nanos > i64::MAX as u128 {
1038 i64::MAX
1039 } else {
1040 nanos as i64
1041 };
1042 SchemaValue::Duration(DurationValuePayload {
1043 nanoseconds: clamped,
1044 })
1045 }
1046}
1047
1048impl FromSchema for std::time::Duration {
1049 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1050 match v {
1051 SchemaValue::Duration(p) => {
1052 if p.nanoseconds < 0 {
1053 Err(FromSchemaError::custom(
1054 "std::time::Duration cannot decode a negative duration",
1055 ))
1056 } else {
1057 Ok(std::time::Duration::from_nanos(p.nanoseconds as u64))
1058 }
1059 }
1060 other => Err(FromSchemaError::shape_mismatch(
1061 "duration",
1062 value_kind(other),
1063 "Duration",
1064 )),
1065 }
1066 }
1067}
1068
1069impl IntoSchema for std::path::PathBuf {
1072 fn type_id() -> TypeId {
1073 TypeId::new("std.path.PathBuf")
1074 }
1075 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
1076 use crate::schema::schema_type::{PathDirection, PathKind, PathSpec};
1077 SchemaType::path(PathSpec {
1078 direction: PathDirection::InOut,
1079 kind: PathKind::Any,
1080 allowed_mime_types: None,
1081 allowed_extensions: None,
1082 })
1083 }
1084 fn to_value(&self) -> SchemaValue {
1085 match self.to_str() {
1086 Some(s) => path_to_value(s.to_string()),
1087 None => path_to_value(self.to_string_lossy().into_owned()),
1088 }
1089 }
1090}
1091
1092impl FromSchema for std::path::PathBuf {
1093 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1094 Ok(std::path::PathBuf::from(path_from_value(v, "PathBuf")?))
1095 }
1096}
1097
1098impl IntoSchema for uuid::Uuid {
1099 fn type_id() -> TypeId {
1100 TypeId::new("uuid.Uuid")
1101 }
1102 fn register_in(_builder: &mut SchemaBuilder) -> SchemaType {
1103 SchemaType::uuid()
1104 }
1105 fn to_value(&self) -> SchemaValue {
1106 SchemaValue::Uuid(*self)
1107 }
1108}
1109
1110impl FromSchema for uuid::Uuid {
1111 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1112 match v {
1113 SchemaValue::Uuid(value) => Ok(*value),
1114 other => Err(FromSchemaError::shape_mismatch(
1115 "uuid",
1116 value_kind(other),
1117 "Uuid",
1118 )),
1119 }
1120 }
1121}
1122
1123impl IntoSchema for usize {
1126 fn type_id() -> TypeId {
1127 TypeId::new("usize")
1128 }
1129 fn register_in(_b: &mut SchemaBuilder) -> SchemaType {
1130 SchemaType::u64()
1131 }
1132 fn to_value(&self) -> SchemaValue {
1133 SchemaValue::U64(*self as u64)
1134 }
1135}
1136
1137impl FromSchema for usize {
1138 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1139 match v {
1140 SchemaValue::U64(x) => Ok(*x as usize),
1141 other => Err(FromSchemaError::shape_mismatch(
1142 "u64",
1143 value_kind(other),
1144 "usize",
1145 )),
1146 }
1147 }
1148}
1149
1150impl<T: IntoSchema> IntoSchema for std::ops::Bound<T> {
1153 fn type_id() -> TypeId {
1154 type_id_with_args("core.ops.range.Bound", &[T::type_id()])
1155 }
1156 fn register_in(b: &mut SchemaBuilder) -> SchemaType {
1157 let inner = T::register_in(b);
1158 SchemaType::variant(vec![
1159 VariantCaseType {
1160 name: "included".to_string(),
1161 payload: Some(inner.clone()),
1162 metadata: MetadataEnvelope::default(),
1163 },
1164 VariantCaseType {
1165 name: "excluded".to_string(),
1166 payload: Some(inner),
1167 metadata: MetadataEnvelope::default(),
1168 },
1169 VariantCaseType {
1170 name: "unbounded".to_string(),
1171 payload: None,
1172 metadata: MetadataEnvelope::default(),
1173 },
1174 ])
1175 }
1176 fn to_value(&self) -> SchemaValue {
1177 match self {
1178 std::ops::Bound::Included(t) => SchemaValue::Variant(VariantValuePayload {
1179 case: 0,
1180 payload: Some(Box::new(t.to_value())),
1181 }),
1182 std::ops::Bound::Excluded(t) => SchemaValue::Variant(VariantValuePayload {
1183 case: 1,
1184 payload: Some(Box::new(t.to_value())),
1185 }),
1186 std::ops::Bound::Unbounded => SchemaValue::Variant(VariantValuePayload {
1187 case: 2,
1188 payload: None,
1189 }),
1190 }
1191 }
1192}
1193
1194impl<T: FromSchema> FromSchema for std::ops::Bound<T> {
1195 fn from_value(v: &SchemaValue) -> Result<Self, FromSchemaError> {
1196 match v {
1197 SchemaValue::Variant(VariantValuePayload { case, payload }) => match case {
1198 0 => {
1199 let p = payload.as_ref().ok_or_else(|| {
1200 FromSchemaError::custom("Bound::Included requires a payload")
1201 })?;
1202 Ok(std::ops::Bound::Included(T::from_value(p)?))
1203 }
1204 1 => {
1205 let p = payload.as_ref().ok_or_else(|| {
1206 FromSchemaError::custom("Bound::Excluded requires a payload")
1207 })?;
1208 Ok(std::ops::Bound::Excluded(T::from_value(p)?))
1209 }
1210 2 => Ok(std::ops::Bound::Unbounded),
1211 other => Err(FromSchemaError::out_of_range(*other, 3, "Bound")),
1212 },
1213 other => Err(FromSchemaError::shape_mismatch(
1214 "variant",
1215 value_kind(other),
1216 "Bound",
1217 )),
1218 }
1219 }
1220}
1221
1222#[cfg(test)]
1227mod result_unit_tests {
1228 use super::*;
1229 use crate::schema::schema_value::ResultValuePayload;
1230 use test_r::test;
1231
1232 #[test]
1233 fn ok_unit_encodes_as_none() {
1234 let v: Result<(), String> = Ok(());
1235 assert_eq!(
1236 v.to_value(),
1237 SchemaValue::Result(ResultValuePayload::Ok { value: None })
1238 );
1239 }
1240
1241 #[test]
1242 fn err_unit_encodes_as_none() {
1243 let v: Result<u32, ()> = Err(());
1244 assert_eq!(
1245 v.to_value(),
1246 SchemaValue::Result(ResultValuePayload::Err { value: None })
1247 );
1248 }
1249
1250 #[test]
1251 fn non_unit_branches_still_carry_payload() {
1252 let ok: Result<u32, String> = Ok(7);
1253 assert_eq!(
1254 ok.to_value(),
1255 SchemaValue::Result(ResultValuePayload::Ok {
1256 value: Some(Box::new(7u32.to_value()))
1257 })
1258 );
1259 let err: Result<u32, String> = Err("boom".to_string());
1260 assert_eq!(
1261 err.to_value(),
1262 SchemaValue::Result(ResultValuePayload::Err {
1263 value: Some(Box::new("boom".to_string().to_value()))
1264 })
1265 );
1266 }
1267
1268 #[test]
1269 fn unit_branch_schema_is_none() {
1270 let mut b = SchemaBuilder::new();
1271 let ty = <Result<(), String> as IntoSchema>::register_in(&mut b);
1272 match ty {
1273 SchemaType::Result { spec, .. } => {
1274 assert!(spec.ok.is_none(), "ok branch should be None for unit");
1275 assert!(spec.err.is_some(), "err branch should carry string");
1276 }
1277 other => panic!("expected result schema, got {other:?}"),
1278 }
1279 }
1280
1281 #[test]
1282 fn ok_unit_roundtrips() {
1283 let v: Result<(), String> = Ok(());
1284 let decoded = <Result<(), String> as FromSchema>::from_value(&v.to_value()).unwrap();
1285 assert_eq!(decoded, Ok(()));
1286 }
1287
1288 #[test]
1289 fn err_unit_roundtrips() {
1290 let v: Result<u32, ()> = Err(());
1291 let decoded = <Result<u32, ()> as FromSchema>::from_value(&v.to_value()).unwrap();
1292 assert_eq!(decoded, Err(()));
1293 }
1294}
1295
1296#[cfg(test)]
1297mod merge_tests {
1298 use super::*;
1299 use test_r::test;
1300
1301 fn def(id: &str, body: SchemaType) -> SchemaTypeDef {
1302 SchemaTypeDef {
1303 id: TypeId::new(id),
1304 name: None,
1305 body,
1306 }
1307 }
1308
1309 #[test]
1310 fn empty_input_produces_empty_merged_graph() {
1311 let merged = merge_agent_graphs(std::iter::empty()).unwrap();
1312 assert!(merged.defs.is_empty());
1313 assert_eq!(merged.root, SchemaGraph::empty().root);
1315 }
1316
1317 #[test]
1318 fn defs_are_deduplicated_by_type_id_when_bodies_match() {
1319 let g1 = SchemaGraph {
1320 defs: vec![def("a.Foo", SchemaType::s32())],
1321 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1322 };
1323 let g2 = SchemaGraph {
1324 defs: vec![def("a.Foo", SchemaType::s32())],
1325 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1326 };
1327 let merged = merge_agent_graphs([g1, g2]).unwrap();
1328 assert_eq!(merged.defs.len(), 1);
1329 assert_eq!(merged.defs[0].id, TypeId::new("a.Foo"));
1330 }
1331
1332 #[test]
1333 fn defs_with_same_id_but_divergent_bodies_are_rejected() {
1334 let g1 = SchemaGraph {
1335 defs: vec![def("a.Foo", SchemaType::s32())],
1336 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1337 };
1338 let g2 = SchemaGraph {
1339 defs: vec![def("a.Foo", SchemaType::string())],
1340 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1341 };
1342 let err = merge_agent_graphs([g1, g2]).unwrap_err();
1343 assert!(matches!(
1344 err,
1345 MergeError::ConflictingDefinitions { ref id, .. } if id == &TypeId::new("a.Foo")
1346 ));
1347 }
1348
1349 #[test]
1350 fn first_seen_insertion_order_is_preserved() {
1351 let g1 = SchemaGraph {
1352 defs: vec![
1353 def("a.Alpha", SchemaType::bool()),
1354 def("a.Beta", SchemaType::s64()),
1355 ],
1356 root: SchemaType::ref_to(TypeId::new("a.Alpha")),
1357 };
1358 let g2 = SchemaGraph {
1359 defs: vec![
1360 def("a.Gamma", SchemaType::u32()),
1361 def("a.Alpha", SchemaType::bool()), ],
1363 root: SchemaType::ref_to(TypeId::new("a.Gamma")),
1364 };
1365 let merged = merge_agent_graphs([g1, g2]).unwrap();
1366 let ids: Vec<_> = merged
1367 .defs
1368 .iter()
1369 .map(|d| d.id.as_str().to_string())
1370 .collect();
1371 assert_eq!(ids, vec!["a.Alpha", "a.Beta", "a.Gamma"]);
1372 }
1373
1374 #[test]
1375 fn duplicate_id_with_same_body_keeps_first_seen_name() {
1376 let g1 = SchemaGraph {
1380 defs: vec![SchemaTypeDef {
1381 id: TypeId::new("a.Foo"),
1382 name: Some("FooFirst".into()),
1383 body: SchemaType::s32(),
1384 }],
1385 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1386 };
1387 let g2 = SchemaGraph {
1388 defs: vec![SchemaTypeDef {
1389 id: TypeId::new("a.Foo"),
1390 name: Some("FooSecond".into()),
1391 body: SchemaType::s32(),
1392 }],
1393 root: SchemaType::ref_to(TypeId::new("a.Foo")),
1394 };
1395 let merged = merge_agent_graphs([g1, g2]).unwrap();
1396 assert_eq!(merged.defs.len(), 1);
1397 assert_eq!(merged.defs[0].name.as_deref(), Some("FooFirst"));
1398 }
1399
1400 #[test]
1401 fn merged_graph_uses_sentinel_root() {
1402 let g = SchemaGraph::anonymous(SchemaType::s32());
1407 let merged = merge_agent_graphs([g]).unwrap();
1408 assert_eq!(merged.root, SchemaGraph::empty().root);
1409 }
1410}