1use std::collections::BTreeMap;
13
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16
17pub const CURRENT_SCHEMA_VERSION: &str = "0.5.0";
29
30pub const SUPPORTED_VERSIONS: &[&str] = &[CURRENT_SCHEMA_VERSION];
36
37#[allow(clippy::trivially_copy_pass_by_ref)]
41fn is_false(b: &bool) -> bool {
42 !*b
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
53#[schemars(description = "Top-level WeaveFFI API definition.")]
54pub struct Api {
55 pub version: String,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub package: Option<Package>,
64 pub modules: Vec<Module>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
72 #[schemars(with = "Option<BTreeMap<String, serde_json::Value>>")]
73 pub generators: Option<BTreeMap<String, toml::Value>>,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
85#[schemars(description = "Package identity for the generated consumer artifacts.")]
86pub struct Package {
87 pub name: String,
90 pub version: String,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub description: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub license: Option<String>,
100 #[serde(default, skip_serializing_if = "Vec::is_empty")]
104 pub authors: Vec<String>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub homepage: Option<String>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub repository: Option<String>,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
122#[schemars(
123 description = "A WeaveFFI module: a named group of functions, types, callbacks, listeners, and errors."
124)]
125pub struct Module {
126 pub name: String,
129 #[serde(default, skip_serializing_if = "Vec::is_empty")]
131 pub functions: Vec<Function>,
132 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub interfaces: Vec<InterfaceDef>,
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
138 pub structs: Vec<StructDef>,
139 #[serde(default, skip_serializing_if = "Vec::is_empty")]
141 pub enums: Vec<EnumDef>,
142 #[serde(default, skip_serializing_if = "Vec::is_empty")]
144 pub callbacks: Vec<CallbackDef>,
145 #[serde(default, skip_serializing_if = "Vec::is_empty")]
147 pub listeners: Vec<ListenerDef>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub errors: Option<ErrorDomain>,
152 #[serde(default, skip_serializing_if = "Vec::is_empty")]
154 pub modules: Vec<Module>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
166pub struct Function {
167 pub name: String,
170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub params: Vec<Param>,
173 #[serde(rename = "return", default, skip_serializing_if = "Option::is_none")]
176 pub returns: Option<TypeRef>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub doc: Option<String>,
181 #[serde(default, skip_serializing_if = "is_false")]
187 pub throws: bool,
188 #[serde(default, rename = "async", skip_serializing_if = "is_false")]
191 pub r#async: bool,
192 #[serde(default, skip_serializing_if = "is_false")]
195 pub cancellable: bool,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub deprecated: Option<String>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
203 pub since: Option<String>,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
220#[schemars(
221 description = "An interface: an opaque object type with constructors, methods, and statics."
222)]
223pub struct InterfaceDef {
224 pub name: String,
226 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub doc: Option<String>,
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
233 pub constructors: Vec<Function>,
234 #[serde(default, skip_serializing_if = "Vec::is_empty")]
237 pub methods: Vec<Function>,
238 #[serde(default, skip_serializing_if = "Vec::is_empty")]
240 pub statics: Vec<Function>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
245pub struct Param {
246 pub name: String,
248 #[serde(rename = "type")]
250 pub ty: TypeRef,
251 #[serde(default, skip_serializing_if = "is_false")]
254 pub mutable: bool,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub doc: Option<String>,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
268pub struct CallbackDef {
269 pub name: String,
272 pub params: Vec<Param>,
274 #[serde(default, skip_serializing_if = "Option::is_none")]
277 pub doc: Option<String>,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
287pub struct ListenerDef {
288 pub name: String,
291 pub event_callback: String,
294 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub doc: Option<String>,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Hash)]
310pub enum TypeRef {
311 I8,
313 I16,
315 I32,
317 I64,
319 U8,
321 U16,
323 U32,
325 U64,
327 F32,
329 F64,
331 Bool,
333 StringUtf8,
335 Bytes,
337 Handle,
341 TypedHandle(String),
344 Struct(String),
355 Enum(String),
357 Interface(String),
366 BorrowedStr,
369 BorrowedBytes,
372 Optional(Box<TypeRef>),
374 List(Box<TypeRef>),
376 Map(Box<TypeRef>, Box<TypeRef>),
379 Iterator(Box<TypeRef>),
382}
383
384pub fn parse_type_ref(s: &str) -> Result<TypeRef, String> {
400 let s = s.trim();
401 if s.is_empty() {
402 return Err("empty type reference".to_string());
403 }
404 if s.starts_with('[') && s.ends_with(']') {
405 let inner = &s[1..s.len() - 1];
406 return parse_type_ref(inner).map(|t| TypeRef::List(Box::new(t)));
407 }
408 if s.starts_with('{') && s.ends_with('}') {
409 let inner = &s[1..s.len() - 1];
410 let colon = inner
411 .find(':')
412 .ok_or_else(|| "map type missing ':' separator".to_string())?;
413 let key = parse_type_ref(&inner[..colon])?;
414 let val = parse_type_ref(&inner[colon + 1..])?;
415 return Ok(TypeRef::Map(Box::new(key), Box::new(val)));
416 }
417 if let Some(inner) = s.strip_suffix('?') {
418 return parse_type_ref(inner).map(|t| TypeRef::Optional(Box::new(t)));
419 }
420 if let Some(inner) = s
421 .strip_prefix("handle<")
422 .and_then(|rest| rest.strip_suffix('>'))
423 {
424 return Ok(TypeRef::TypedHandle(inner.into()));
425 }
426 if let Some(inner) = s
427 .strip_prefix("iter<")
428 .and_then(|rest| rest.strip_suffix('>'))
429 {
430 return parse_type_ref(inner).map(|t| TypeRef::Iterator(Box::new(t)));
431 }
432 match s {
433 "i8" => Ok(TypeRef::I8),
434 "i16" => Ok(TypeRef::I16),
435 "i32" => Ok(TypeRef::I32),
436 "i64" => Ok(TypeRef::I64),
437 "u8" => Ok(TypeRef::U8),
438 "u16" => Ok(TypeRef::U16),
439 "u32" => Ok(TypeRef::U32),
440 "u64" => Ok(TypeRef::U64),
441 "f32" => Ok(TypeRef::F32),
442 "f64" => Ok(TypeRef::F64),
443 "bool" => Ok(TypeRef::Bool),
444 "string" => Ok(TypeRef::StringUtf8),
445 "bytes" => Ok(TypeRef::Bytes),
446 "handle" => Ok(TypeRef::Handle),
447 "&str" => Ok(TypeRef::BorrowedStr),
448 "&[u8]" => Ok(TypeRef::BorrowedBytes),
449 name => Ok(TypeRef::Struct(name.to_string())),
450 }
451}
452
453fn type_ref_to_string(ty: &TypeRef) -> String {
454 match ty {
455 TypeRef::I8 => "i8".to_string(),
456 TypeRef::I16 => "i16".to_string(),
457 TypeRef::I32 => "i32".to_string(),
458 TypeRef::I64 => "i64".to_string(),
459 TypeRef::U8 => "u8".to_string(),
460 TypeRef::U16 => "u16".to_string(),
461 TypeRef::U32 => "u32".to_string(),
462 TypeRef::U64 => "u64".to_string(),
463 TypeRef::F32 => "f32".to_string(),
464 TypeRef::F64 => "f64".to_string(),
465 TypeRef::Bool => "bool".to_string(),
466 TypeRef::StringUtf8 => "string".to_string(),
467 TypeRef::Bytes => "bytes".to_string(),
468 TypeRef::BorrowedStr => "&str".to_string(),
469 TypeRef::BorrowedBytes => "&[u8]".to_string(),
470 TypeRef::Handle => "handle".to_string(),
471 TypeRef::TypedHandle(name) => format!("handle<{name}>"),
472 TypeRef::Struct(name) | TypeRef::Enum(name) | TypeRef::Interface(name) => name.clone(),
473 TypeRef::Optional(inner) => format!("{}?", type_ref_to_string(inner)),
474 TypeRef::List(inner) => format!("[{}]", type_ref_to_string(inner)),
475 TypeRef::Map(k, v) => format!("{{{}:{}}}", type_ref_to_string(k), type_ref_to_string(v)),
476 TypeRef::Iterator(inner) => format!("iter<{}>", type_ref_to_string(inner)),
477 }
478}
479
480impl Serialize for TypeRef {
481 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
482 where
483 S: serde::Serializer,
484 {
485 serializer.serialize_str(&type_ref_to_string(self))
486 }
487}
488
489impl<'de> Deserialize<'de> for TypeRef {
490 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
491 where
492 D: serde::Deserializer<'de>,
493 {
494 let s = String::deserialize(deserializer)?;
495 parse_type_ref(&s).map_err(serde::de::Error::custom)
496 }
497}
498
499impl JsonSchema for TypeRef {
504 fn schema_name() -> String {
505 "TypeRef".to_string()
506 }
507
508 fn schema_id() -> std::borrow::Cow<'static, str> {
509 std::borrow::Cow::Borrowed(concat!(module_path!(), "::TypeRef"))
510 }
511
512 fn json_schema(_generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
513 let mut schema = schemars::schema::SchemaObject {
514 instance_type: Some(schemars::schema::InstanceType::String.into()),
515 ..Default::default()
516 };
517 let meta = schema.metadata();
518 meta.title = Some("TypeRef".to_string());
519 meta.description = Some(
520 "Reference to a type. Encoded as a string with custom syntax: \
521 primitives (`i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, \
522 `f32`, `f64`, `bool`, `string`, `bytes`, `handle`), \
523 borrowed types (`&str`, `&[u8]`), typed handles (`handle<{name}>`), \
524 iterators (`iter<{T}>`), lists (`[{T}]`), maps (`{{K:V}}`), \
525 optionals (`{T}?`), or any user-defined struct/enum/interface name."
526 .to_string(),
527 );
528 schema.into()
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
541#[schemars(
542 description = "An enum type. C-style when every variant is a bare discriminant; an algebraic sum type when any variant declares fields."
543)]
544pub struct EnumDef {
545 pub name: String,
547 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub doc: Option<String>,
551 pub variants: Vec<EnumVariant>,
554}
555
556impl EnumDef {
557 pub fn is_rich(&self) -> bool {
563 self.variants.iter().any(|v| !v.fields.is_empty())
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
570pub struct EnumVariant {
571 pub name: String,
573 pub value: i32,
576 #[serde(default, skip_serializing_if = "Option::is_none")]
579 pub doc: Option<String>,
580 #[serde(default, skip_serializing_if = "Vec::is_empty")]
585 pub fields: Vec<StructField>,
586}
587
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
591#[schemars(description = "A struct (record) type with named fields.")]
592pub struct StructDef {
593 pub name: String,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
598 pub doc: Option<String>,
599 pub fields: Vec<StructField>,
602 #[serde(default, skip_serializing_if = "is_false")]
605 pub builder: bool,
606}
607
608#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
611pub struct StructField {
612 pub name: String,
614 #[serde(rename = "type")]
616 pub ty: TypeRef,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
620 pub doc: Option<String>,
621 #[serde(default, skip_serializing_if = "Option::is_none")]
626 #[schemars(with = "Option<serde_json::Value>")]
627 pub default: Option<serde_yaml::Value>,
628}
629
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
633pub struct ErrorDomain {
634 pub name: String,
637 pub codes: Vec<ErrorCode>,
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
643pub struct ErrorCode {
644 pub name: String,
647 pub code: i32,
649 pub message: String,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub doc: Option<String>,
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn struct_def_round_trip_yaml() {
663 let yaml = r#"
664version: "0.5.0"
665modules:
666 - name: geometry
667 functions: []
668 structs:
669 - name: Point
670 doc: "A 2D point"
671 fields:
672 - name: x
673 type: f64
674 - name: "y"
675 type: f64
676 doc: "Y coordinate"
677"#;
678 let api: Api = serde_yaml::from_str(yaml).unwrap();
679 let m = &api.modules[0];
680 assert_eq!(m.structs.len(), 1);
681 let s = &m.structs[0];
682 assert_eq!(s.name, "Point");
683 assert_eq!(s.doc.as_deref(), Some("A 2D point"));
684 assert_eq!(s.fields.len(), 2);
685 assert_eq!(s.fields[0].name, "x");
686 assert_eq!(s.fields[0].ty, TypeRef::F64);
687 assert_eq!(s.fields[0].doc, None);
688 assert_eq!(s.fields[1].name, "y");
689 assert_eq!(s.fields[1].doc.as_deref(), Some("Y coordinate"));
690 }
691
692 #[test]
693 fn struct_def_round_trip_json() {
694 let json = r#"{
695 "version": "0.5.0",
696 "modules": [{
697 "name": "geo",
698 "functions": [],
699 "structs": [{
700 "name": "Rect",
701 "fields": [
702 {"name": "width", "type": "i32"},
703 {"name": "height", "type": "i32"}
704 ]
705 }]
706 }]
707 }"#;
708 let api: Api = serde_json::from_str(json).unwrap();
709 let s = &api.modules[0].structs[0];
710 assert_eq!(s.name, "Rect");
711 assert_eq!(s.doc, None);
712 assert_eq!(s.fields[0].ty, TypeRef::I32);
713 }
714
715 #[test]
716 fn structs_default_to_empty() {
717 let yaml = r#"
718version: "0.5.0"
719modules:
720 - name: math
721 functions: []
722"#;
723 let api: Api = serde_yaml::from_str(yaml).unwrap();
724 assert!(api.modules[0].structs.is_empty());
725 }
726
727 #[test]
728 fn package_block_round_trips_yaml() {
729 let yaml = r#"
730version: "0.5.0"
731package:
732 name: kvstore
733 version: 1.2.0
734 description: "An embedded key/value store"
735 license: MIT
736 authors:
737 - "Ada Lovelace <ada@example.com>"
738 homepage: "https://example.com/kvstore"
739 repository: "https://github.com/example/kvstore"
740modules:
741 - name: kv
742 functions: []
743"#;
744 let api: Api = serde_yaml::from_str(yaml).unwrap();
745 let pkg = api.package.as_ref().expect("package should parse");
746 assert_eq!(pkg.name, "kvstore");
747 assert_eq!(pkg.version, "1.2.0");
748 assert_eq!(
749 pkg.description.as_deref(),
750 Some("An embedded key/value store")
751 );
752 assert_eq!(pkg.license.as_deref(), Some("MIT"));
753 assert_eq!(pkg.authors, vec!["Ada Lovelace <ada@example.com>"]);
754 assert_eq!(pkg.homepage.as_deref(), Some("https://example.com/kvstore"));
755 assert_eq!(
756 pkg.repository.as_deref(),
757 Some("https://github.com/example/kvstore")
758 );
759
760 let out = serde_yaml::to_string(&api).unwrap();
762 assert!(out.contains("name: kvstore"));
763 assert!(out.contains("version: 1.2.0"));
764 }
765
766 #[test]
767 fn package_is_optional() {
768 let yaml = r#"
769version: "0.5.0"
770modules:
771 - name: math
772 functions: []
773"#;
774 let api: Api = serde_yaml::from_str(yaml).unwrap();
775 assert!(api.package.is_none());
776 let out = serde_yaml::to_string(&api).unwrap();
778 assert!(!out.contains("package:"));
779 }
780
781 #[test]
782 fn package_minimal_requires_name_and_version() {
783 let yaml = r#"
784version: "0.5.0"
785package:
786 name: tiny
787 version: 0.0.1
788modules: []
789"#;
790 let api: Api = serde_yaml::from_str(yaml).unwrap();
791 let pkg = api.package.as_ref().unwrap();
792 assert_eq!(pkg.name, "tiny");
793 assert_eq!(pkg.version, "0.0.1");
794 assert!(pkg.description.is_none());
795 assert!(pkg.authors.is_empty());
796 }
797
798 #[test]
799 fn typeref_struct_variant_serializes() {
800 let ty = TypeRef::Struct("Point".to_string());
801 let json = serde_json::to_string(&ty).unwrap();
802 assert_eq!(json, r#""Point""#);
803 let back: TypeRef = serde_json::from_str(&json).unwrap();
804 assert_eq!(back, ty);
805 }
806
807 #[test]
808 fn struct_field_with_struct_type() {
809 let field = StructField {
810 name: "origin".to_string(),
811 ty: TypeRef::Struct("Point".to_string()),
812 doc: None,
813 default: None,
814 };
815 let json = serde_json::to_string(&field).unwrap();
816 let back: StructField = serde_json::from_str(&json).unwrap();
817 assert_eq!(back, field);
818 }
819
820 #[test]
821 fn typeref_is_not_copy() {
822 let a = TypeRef::Struct("Foo".to_string());
823 let b = a.clone();
824 assert_eq!(a, b);
825 }
826
827 #[test]
828 fn enum_def_round_trip_yaml() {
829 let yaml = r#"
830version: "0.5.0"
831modules:
832 - name: graphics
833 functions: []
834 enums:
835 - name: Color
836 doc: "Primary colors"
837 variants:
838 - name: Red
839 value: 0
840 - name: Green
841 value: 1
842 doc: "The color green"
843 - name: Blue
844 value: 2
845"#;
846 let api: Api = serde_yaml::from_str(yaml).unwrap();
847 let m = &api.modules[0];
848 assert_eq!(m.enums.len(), 1);
849 let e = &m.enums[0];
850 assert_eq!(e.name, "Color");
851 assert_eq!(e.doc.as_deref(), Some("Primary colors"));
852 assert_eq!(e.variants.len(), 3);
853 assert_eq!(e.variants[0].name, "Red");
854 assert_eq!(e.variants[0].value, 0);
855 assert_eq!(e.variants[0].doc, None);
856 assert_eq!(e.variants[1].name, "Green");
857 assert_eq!(e.variants[1].value, 1);
858 assert_eq!(e.variants[1].doc.as_deref(), Some("The color green"));
859 assert_eq!(e.variants[2].name, "Blue");
860 assert_eq!(e.variants[2].value, 2);
861 }
862
863 #[test]
864 fn enum_def_round_trip_json() {
865 let json = r#"{
866 "version": "0.5.0",
867 "modules": [{
868 "name": "status",
869 "functions": [],
870 "enums": [{
871 "name": "Status",
872 "variants": [
873 {"name": "Ok", "value": 0},
874 {"name": "Error", "value": 1}
875 ]
876 }]
877 }]
878 }"#;
879 let api: Api = serde_json::from_str(json).unwrap();
880 let e = &api.modules[0].enums[0];
881 assert_eq!(e.name, "Status");
882 assert_eq!(e.doc, None);
883 assert_eq!(e.variants.len(), 2);
884 assert_eq!(e.variants[1].value, 1);
885 }
886
887 #[test]
888 fn enums_default_to_empty() {
889 let yaml = r#"
890version: "0.5.0"
891modules:
892 - name: math
893 functions: []
894"#;
895 let api: Api = serde_yaml::from_str(yaml).unwrap();
896 assert!(api.modules[0].enums.is_empty());
897 }
898
899 #[test]
900 fn typeref_enum_variant_serializes_as_name() {
901 let ty = TypeRef::Enum("Color".to_string());
902 let json = serde_json::to_string(&ty).unwrap();
903 assert_eq!(json, r#""Color""#);
904 }
905
906 #[test]
907 fn enum_def_clone_and_eq() {
908 let e = EnumDef {
909 name: "Direction".to_string(),
910 doc: Some("Cardinal directions".to_string()),
911 variants: vec![
912 EnumVariant {
913 name: "North".to_string(),
914 value: 0,
915 doc: None,
916 fields: vec![],
917 },
918 EnumVariant {
919 name: "South".to_string(),
920 value: 1,
921 doc: None,
922 fields: vec![],
923 },
924 ],
925 };
926 assert_eq!(e, e.clone());
927 assert!(!e.is_rich());
928 }
929
930 #[test]
931 fn enum_def_is_rich_when_a_variant_has_fields() {
932 let e = EnumDef {
933 name: "Shape".to_string(),
934 doc: None,
935 variants: vec![
936 EnumVariant {
937 name: "Circle".to_string(),
938 value: 0,
939 doc: None,
940 fields: vec![StructField {
941 name: "radius".to_string(),
942 ty: TypeRef::F64,
943 doc: None,
944 default: None,
945 }],
946 },
947 EnumVariant {
948 name: "Empty".to_string(),
949 value: 1,
950 doc: None,
951 fields: vec![],
952 },
953 ],
954 };
955 assert!(e.is_rich());
956 }
957
958 #[test]
959 fn struct_def_clone_and_eq() {
960 let s = StructDef {
961 name: "Color".to_string(),
962 doc: Some("RGB color".to_string()),
963 fields: vec![
964 StructField {
965 name: "r".to_string(),
966 ty: TypeRef::U32,
967 doc: None,
968 default: None,
969 },
970 StructField {
971 name: "g".to_string(),
972 ty: TypeRef::U32,
973 doc: None,
974 default: None,
975 },
976 StructField {
977 name: "b".to_string(),
978 ty: TypeRef::U32,
979 doc: None,
980 default: None,
981 },
982 ],
983 builder: false,
984 };
985 assert_eq!(s, s.clone());
986 }
987
988 #[test]
989 fn parse_type_ref_primitives() {
990 assert_eq!(parse_type_ref("i32"), Ok(TypeRef::I32));
991 assert_eq!(parse_type_ref("u32"), Ok(TypeRef::U32));
992 assert_eq!(parse_type_ref("i64"), Ok(TypeRef::I64));
993 assert_eq!(parse_type_ref("f64"), Ok(TypeRef::F64));
994 assert_eq!(parse_type_ref("bool"), Ok(TypeRef::Bool));
995 assert_eq!(parse_type_ref("string"), Ok(TypeRef::StringUtf8));
996 assert_eq!(parse_type_ref("bytes"), Ok(TypeRef::Bytes));
997 assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
998 }
999
1000 #[test]
1001 fn parse_type_ref_struct() {
1002 assert_eq!(
1003 parse_type_ref("Contact"),
1004 Ok(TypeRef::Struct("Contact".into()))
1005 );
1006 assert_eq!(
1007 parse_type_ref("MyWidget"),
1008 Ok(TypeRef::Struct("MyWidget".into()))
1009 );
1010 }
1011
1012 #[test]
1013 fn parse_type_ref_optional() {
1014 assert_eq!(
1015 parse_type_ref("string?"),
1016 Ok(TypeRef::Optional(Box::new(TypeRef::StringUtf8)))
1017 );
1018 assert_eq!(
1019 parse_type_ref("i32?"),
1020 Ok(TypeRef::Optional(Box::new(TypeRef::I32)))
1021 );
1022 assert_eq!(
1023 parse_type_ref("Contact?"),
1024 Ok(TypeRef::Optional(Box::new(TypeRef::Struct(
1025 "Contact".into()
1026 ))))
1027 );
1028 }
1029
1030 #[test]
1031 fn parse_type_ref_list() {
1032 assert_eq!(
1033 parse_type_ref("[i32]"),
1034 Ok(TypeRef::List(Box::new(TypeRef::I32)))
1035 );
1036 assert_eq!(
1037 parse_type_ref("[string]"),
1038 Ok(TypeRef::List(Box::new(TypeRef::StringUtf8)))
1039 );
1040 assert_eq!(
1041 parse_type_ref("[Contact]"),
1042 Ok(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
1043 );
1044 }
1045
1046 #[test]
1047 fn parse_type_ref_nested() {
1048 assert_eq!(
1049 parse_type_ref("[i32?]"),
1050 Ok(TypeRef::List(Box::new(TypeRef::Optional(Box::new(
1051 TypeRef::I32
1052 )))))
1053 );
1054 assert_eq!(
1055 parse_type_ref("[Contact]?"),
1056 Ok(TypeRef::Optional(Box::new(TypeRef::List(Box::new(
1057 TypeRef::Struct("Contact".into())
1058 )))))
1059 );
1060 }
1061
1062 #[test]
1063 fn parse_type_ref_empty_is_error() {
1064 assert!(parse_type_ref("").is_err());
1065 assert!(parse_type_ref(" ").is_err());
1066 }
1067
1068 #[test]
1069 fn typeref_primitive_round_trips() {
1070 for ty in [
1071 TypeRef::I32,
1072 TypeRef::U32,
1073 TypeRef::I64,
1074 TypeRef::F64,
1075 TypeRef::Bool,
1076 TypeRef::StringUtf8,
1077 TypeRef::Bytes,
1078 TypeRef::Handle,
1079 ] {
1080 let json = serde_json::to_string(&ty).unwrap();
1081 let back: TypeRef = serde_json::from_str(&json).unwrap();
1082 assert_eq!(back, ty);
1083 }
1084 }
1085
1086 #[test]
1087 fn typeref_optional_round_trip() {
1088 let ty = TypeRef::Optional(Box::new(TypeRef::StringUtf8));
1089 let json = serde_json::to_string(&ty).unwrap();
1090 assert_eq!(json, r#""string?""#);
1091 let back: TypeRef = serde_json::from_str(&json).unwrap();
1092 assert_eq!(back, ty);
1093 }
1094
1095 #[test]
1096 fn typeref_list_round_trip() {
1097 let ty = TypeRef::List(Box::new(TypeRef::I32));
1098 let json = serde_json::to_string(&ty).unwrap();
1099 assert_eq!(json, r#""[i32]""#);
1100 let back: TypeRef = serde_json::from_str(&json).unwrap();
1101 assert_eq!(back, ty);
1102 }
1103
1104 #[test]
1105 fn typeref_optional_struct_round_trip() {
1106 let ty = TypeRef::Optional(Box::new(TypeRef::Struct("Contact".into())));
1107 let json = serde_json::to_string(&ty).unwrap();
1108 assert_eq!(json, r#""Contact?""#);
1109 let back: TypeRef = serde_json::from_str(&json).unwrap();
1110 assert_eq!(back, ty);
1111 }
1112
1113 #[test]
1114 fn typeref_list_struct_round_trip() {
1115 let ty = TypeRef::List(Box::new(TypeRef::Struct("Contact".into())));
1116 let json = serde_json::to_string(&ty).unwrap();
1117 assert_eq!(json, r#""[Contact]""#);
1118 let back: TypeRef = serde_json::from_str(&json).unwrap();
1119 assert_eq!(back, ty);
1120 }
1121
1122 #[test]
1123 fn typeref_optional_yaml_deser() {
1124 let yaml = r#"
1125version: "0.5.0"
1126modules:
1127 - name: contacts
1128 functions:
1129 - name: find
1130 params:
1131 - name: id
1132 type: i32
1133 return: "Contact?"
1134"#;
1135 let api: Api = serde_yaml::from_str(yaml).unwrap();
1136 let f = &api.modules[0].functions[0];
1137 assert_eq!(
1138 f.returns,
1139 Some(TypeRef::Optional(Box::new(TypeRef::Struct(
1140 "Contact".into()
1141 ))))
1142 );
1143 }
1144
1145 #[test]
1146 fn typeref_list_yaml_deser() {
1147 let yaml = r#"
1148version: "0.5.0"
1149modules:
1150 - name: contacts
1151 functions:
1152 - name: list_all
1153 params: []
1154 return: "[Contact]"
1155"#;
1156 let api: Api = serde_yaml::from_str(yaml).unwrap();
1157 let f = &api.modules[0].functions[0];
1158 assert_eq!(
1159 f.returns,
1160 Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
1161 );
1162 }
1163
1164 #[test]
1165 fn typeref_hash_works_with_box_variants() {
1166 use std::collections::HashSet;
1167 let mut set = HashSet::new();
1168 set.insert(TypeRef::I32);
1169 set.insert(TypeRef::Optional(Box::new(TypeRef::I32)));
1170 set.insert(TypeRef::List(Box::new(TypeRef::I32)));
1171 set.insert(TypeRef::Optional(Box::new(TypeRef::Struct("Foo".into()))));
1172 set.insert(TypeRef::Map(
1173 Box::new(TypeRef::StringUtf8),
1174 Box::new(TypeRef::I32),
1175 ));
1176 assert_eq!(set.len(), 5);
1177 }
1178
1179 #[test]
1180 fn parse_type_ref_map_primitives() {
1181 assert_eq!(
1182 parse_type_ref("{string:i32}"),
1183 Ok(TypeRef::Map(
1184 Box::new(TypeRef::StringUtf8),
1185 Box::new(TypeRef::I32)
1186 ))
1187 );
1188 }
1189
1190 #[test]
1191 fn parse_type_ref_map_struct_value() {
1192 assert_eq!(
1193 parse_type_ref("{string:Contact}"),
1194 Ok(TypeRef::Map(
1195 Box::new(TypeRef::StringUtf8),
1196 Box::new(TypeRef::Struct("Contact".into()))
1197 ))
1198 );
1199 }
1200
1201 #[test]
1202 fn parse_type_ref_map_nested_value() {
1203 assert_eq!(
1204 parse_type_ref("{string:[i32]}"),
1205 Ok(TypeRef::Map(
1206 Box::new(TypeRef::StringUtf8),
1207 Box::new(TypeRef::List(Box::new(TypeRef::I32)))
1208 ))
1209 );
1210 }
1211
1212 #[test]
1213 fn parse_type_ref_map_missing_colon() {
1214 assert!(parse_type_ref("{string}").is_err());
1215 }
1216
1217 #[test]
1218 fn typeref_map_round_trip() {
1219 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
1220 let json = serde_json::to_string(&ty).unwrap();
1221 assert_eq!(json, r#""{string:i32}""#);
1222 let back: TypeRef = serde_json::from_str(&json).unwrap();
1223 assert_eq!(back, ty);
1224 }
1225
1226 #[test]
1227 fn typeref_map_struct_round_trip() {
1228 let ty = TypeRef::Map(
1229 Box::new(TypeRef::StringUtf8),
1230 Box::new(TypeRef::Struct("Contact".into())),
1231 );
1232 let json = serde_json::to_string(&ty).unwrap();
1233 assert_eq!(json, r#""{string:Contact}""#);
1234 let back: TypeRef = serde_json::from_str(&json).unwrap();
1235 assert_eq!(back, ty);
1236 }
1237
1238 #[test]
1239 fn typeref_map_yaml_deser() {
1240 let yaml = r#"
1241version: "0.5.0"
1242modules:
1243 - name: contacts
1244 functions:
1245 - name: get_metadata
1246 params: []
1247 return: "{string:i32}"
1248"#;
1249 let api: Api = serde_yaml::from_str(yaml).unwrap();
1250 let f = &api.modules[0].functions[0];
1251 assert_eq!(
1252 f.returns,
1253 Some(TypeRef::Map(
1254 Box::new(TypeRef::StringUtf8),
1255 Box::new(TypeRef::I32)
1256 ))
1257 );
1258 }
1259
1260 #[test]
1261 fn typeref_optional_map_round_trip() {
1262 let ty = TypeRef::Optional(Box::new(TypeRef::Map(
1263 Box::new(TypeRef::StringUtf8),
1264 Box::new(TypeRef::I32),
1265 )));
1266 let json = serde_json::to_string(&ty).unwrap();
1267 assert_eq!(json, r#""{string:i32}?""#);
1268 let back: TypeRef = serde_json::from_str(&json).unwrap();
1269 assert_eq!(back, ty);
1270 }
1271
1272 #[test]
1273 fn parse_map_string_to_i32() {
1274 assert_eq!(
1275 parse_type_ref("{string:i32}"),
1276 Ok(TypeRef::Map(
1277 Box::new(TypeRef::StringUtf8),
1278 Box::new(TypeRef::I32),
1279 ))
1280 );
1281 }
1282
1283 #[test]
1284 fn parse_map_string_to_struct() {
1285 assert_eq!(
1286 parse_type_ref("{string:Contact}"),
1287 Ok(TypeRef::Map(
1288 Box::new(TypeRef::StringUtf8),
1289 Box::new(TypeRef::Struct("Contact".into())),
1290 ))
1291 );
1292 }
1293
1294 #[test]
1295 fn parse_map_roundtrip() {
1296 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
1297 let json = serde_json::to_string(&ty).unwrap();
1298 let back: TypeRef = serde_json::from_str(&json).unwrap();
1299 assert_eq!(back, ty);
1300 }
1301
1302 #[test]
1303 fn parse_optional_map() {
1304 assert_eq!(
1305 parse_type_ref("{string:i32}?"),
1306 Ok(TypeRef::Optional(Box::new(TypeRef::Map(
1307 Box::new(TypeRef::StringUtf8),
1308 Box::new(TypeRef::I32),
1309 ))))
1310 );
1311 }
1312
1313 #[test]
1314 fn parse_map_of_lists() {
1315 assert_eq!(
1316 parse_type_ref("{string:[i32]}"),
1317 Ok(TypeRef::Map(
1318 Box::new(TypeRef::StringUtf8),
1319 Box::new(TypeRef::List(Box::new(TypeRef::I32))),
1320 ))
1321 );
1322 }
1323
1324 #[test]
1325 fn parse_type_ref_iterator() {
1326 assert_eq!(
1327 parse_type_ref("iter<i32>"),
1328 Ok(TypeRef::Iterator(Box::new(TypeRef::I32)))
1329 );
1330 assert_eq!(
1331 parse_type_ref("iter<string>"),
1332 Ok(TypeRef::Iterator(Box::new(TypeRef::StringUtf8)))
1333 );
1334 assert_eq!(
1335 parse_type_ref("iter<Contact>"),
1336 Ok(TypeRef::Iterator(Box::new(TypeRef::Struct(
1337 "Contact".into()
1338 ))))
1339 );
1340 }
1341
1342 #[test]
1343 fn typeref_iterator_round_trip() {
1344 let ty = TypeRef::Iterator(Box::new(TypeRef::I32));
1345 let json = serde_json::to_string(&ty).unwrap();
1346 assert_eq!(json, r#""iter<i32>""#);
1347 let back: TypeRef = serde_json::from_str(&json).unwrap();
1348 assert_eq!(back, ty);
1349 }
1350
1351 #[test]
1352 fn typeref_iterator_struct_round_trip() {
1353 let ty = TypeRef::Iterator(Box::new(TypeRef::Struct("Contact".into())));
1354 let json = serde_json::to_string(&ty).unwrap();
1355 assert_eq!(json, r#""iter<Contact>""#);
1356 let back: TypeRef = serde_json::from_str(&json).unwrap();
1357 assert_eq!(back, ty);
1358 }
1359
1360 #[test]
1361 fn parse_type_ref_borrowed() {
1362 assert_eq!(parse_type_ref("&str"), Ok(TypeRef::BorrowedStr));
1363 assert_eq!(parse_type_ref("&[u8]"), Ok(TypeRef::BorrowedBytes));
1364 }
1365
1366 #[test]
1367 fn typeref_borrowed_round_trip() {
1368 for ty in [TypeRef::BorrowedStr, TypeRef::BorrowedBytes] {
1369 let json = serde_json::to_string(&ty).unwrap();
1370 let back: TypeRef = serde_json::from_str(&json).unwrap();
1371 assert_eq!(back, ty);
1372 }
1373 }
1374
1375 #[test]
1376 fn typeref_borrowed_str_serializes_as_ampersand_str() {
1377 let json = serde_json::to_string(&TypeRef::BorrowedStr).unwrap();
1378 assert_eq!(json, r#""&str""#);
1379 }
1380
1381 #[test]
1382 fn typeref_borrowed_bytes_serializes_as_ampersand_u8() {
1383 let json = serde_json::to_string(&TypeRef::BorrowedBytes).unwrap();
1384 assert_eq!(json, r#""&[u8]""#);
1385 }
1386
1387 #[test]
1388 fn typeref_borrowed_yaml_deser() {
1389 let yaml = r#"
1390version: "0.5.0"
1391modules:
1392 - name: io
1393 functions:
1394 - name: write
1395 params:
1396 - name: data
1397 type: "&str"
1398 - name: raw
1399 type: "&[u8]"
1400"#;
1401 let api: Api = serde_yaml::from_str(yaml).unwrap();
1402 let f = &api.modules[0].functions[0];
1403 assert_eq!(f.params[0].ty, TypeRef::BorrowedStr);
1404 assert_eq!(f.params[1].ty, TypeRef::BorrowedBytes);
1405 }
1406
1407 #[test]
1408 fn parse_typed_handle() {
1409 assert_eq!(
1410 parse_type_ref("handle<Session>"),
1411 Ok(TypeRef::TypedHandle("Session".into()))
1412 );
1413 assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
1414 }
1415
1416 #[test]
1417 fn generators_field_parses_from_yaml() {
1418 let yaml = r#"
1419version: "0.5.0"
1420modules:
1421 - name: math
1422 functions: []
1423generators:
1424 swift:
1425 module_name: MySwiftModule
1426 android:
1427 package: com.example.app
1428"#;
1429 let api: Api = serde_yaml::from_str(yaml).unwrap();
1430 let generators = api.generators.as_ref().unwrap();
1431 let swift = generators["swift"].as_table().unwrap();
1432 assert_eq!(swift["module_name"].as_str(), Some("MySwiftModule"));
1433 let android = generators["android"].as_table().unwrap();
1434 assert_eq!(android["package"].as_str(), Some("com.example.app"));
1435 }
1436
1437 #[test]
1438 fn generators_defaults_to_none() {
1439 let yaml = r#"
1440version: "0.5.0"
1441modules:
1442 - name: math
1443 functions: []
1444"#;
1445 let api: Api = serde_yaml::from_str(yaml).unwrap();
1446 assert!(api.generators.is_none());
1447 }
1448
1449 #[test]
1450 fn parse_typed_handle_roundtrip() {
1451 let ty = TypeRef::TypedHandle("Connection".into());
1452 let json = serde_json::to_string(&ty).unwrap();
1453 assert_eq!(json, r#""handle<Connection>""#);
1454 let back: TypeRef = serde_json::from_str(&json).unwrap();
1455 assert_eq!(back, ty);
1456 }
1457
1458 #[test]
1459 fn callback_def_round_trip_yaml() {
1460 let yaml = r#"
1461version: "0.5.0"
1462modules:
1463 - name: events
1464 functions: []
1465 callbacks:
1466 - name: on_data
1467 params:
1468 - name: payload
1469 type: string
1470 doc: "Fired when data arrives"
1471"#;
1472 let api: Api = serde_yaml::from_str(yaml).unwrap();
1473 let m = &api.modules[0];
1474 assert_eq!(m.callbacks.len(), 1);
1475 let cb = &m.callbacks[0];
1476 assert_eq!(cb.name, "on_data");
1477 assert_eq!(cb.params.len(), 1);
1478 assert_eq!(cb.params[0].name, "payload");
1479 assert_eq!(cb.params[0].ty, TypeRef::StringUtf8);
1480 assert_eq!(cb.doc.as_deref(), Some("Fired when data arrives"));
1481 }
1482
1483 #[test]
1484 fn listener_def_round_trip_yaml() {
1485 let yaml = r#"
1486version: "0.5.0"
1487modules:
1488 - name: events
1489 functions: []
1490 callbacks:
1491 - name: on_data
1492 params: []
1493 listeners:
1494 - name: data_stream
1495 event_callback: on_data
1496 doc: "Subscribe to data events"
1497"#;
1498 let api: Api = serde_yaml::from_str(yaml).unwrap();
1499 let m = &api.modules[0];
1500 assert_eq!(m.listeners.len(), 1);
1501 let l = &m.listeners[0];
1502 assert_eq!(l.name, "data_stream");
1503 assert_eq!(l.event_callback, "on_data");
1504 assert_eq!(l.doc.as_deref(), Some("Subscribe to data events"));
1505 }
1506
1507 #[test]
1508 fn callbacks_and_listeners_default_to_empty() {
1509 let yaml = r#"
1510version: "0.5.0"
1511modules:
1512 - name: math
1513 functions: []
1514"#;
1515 let api: Api = serde_yaml::from_str(yaml).unwrap();
1516 assert!(api.modules[0].callbacks.is_empty());
1517 assert!(api.modules[0].listeners.is_empty());
1518 }
1519
1520 #[test]
1521 fn callback_def_json_round_trip() {
1522 let cb = CallbackDef {
1523 name: "on_event".to_string(),
1524 params: vec![Param {
1525 name: "data".to_string(),
1526 ty: TypeRef::I32,
1527 mutable: false,
1528 doc: None,
1529 }],
1530 doc: Some("event callback".to_string()),
1531 };
1532 let json = serde_json::to_string(&cb).unwrap();
1533 let back: CallbackDef = serde_json::from_str(&json).unwrap();
1534 assert_eq!(back, cb);
1535 }
1536
1537 #[test]
1538 fn listener_def_json_round_trip() {
1539 let l = ListenerDef {
1540 name: "watcher".to_string(),
1541 event_callback: "on_change".to_string(),
1542 doc: None,
1543 };
1544 let json = serde_json::to_string(&l).unwrap();
1545 let back: ListenerDef = serde_json::from_str(&json).unwrap();
1546 assert_eq!(back, l);
1547 }
1548
1549 #[test]
1550 fn builder_defaults_to_false() {
1551 let yaml = r#"
1552version: "0.5.0"
1553modules:
1554 - name: contacts
1555 functions: []
1556 structs:
1557 - name: Contact
1558 fields:
1559 - name: name
1560 type: string
1561"#;
1562 let api: Api = serde_yaml::from_str(yaml).unwrap();
1563 assert!(!api.modules[0].structs[0].builder);
1564 }
1565
1566 #[test]
1567 fn builder_true_round_trip() {
1568 let yaml = r#"
1569version: "0.5.0"
1570modules:
1571 - name: contacts
1572 functions: []
1573 structs:
1574 - name: Contact
1575 fields:
1576 - name: name
1577 type: string
1578 builder: true
1579"#;
1580 let api: Api = serde_yaml::from_str(yaml).unwrap();
1581 assert!(api.modules[0].structs[0].builder);
1582
1583 let json = serde_json::to_string(&api).unwrap();
1584 let back: Api = serde_json::from_str(&json).unwrap();
1585 assert!(back.modules[0].structs[0].builder);
1586 }
1587
1588 #[test]
1589 fn builder_false_explicit() {
1590 let json = r#"{
1591 "version": "0.5.0",
1592 "modules": [{
1593 "name": "geo",
1594 "functions": [],
1595 "structs": [{
1596 "name": "Point",
1597 "fields": [{"name": "x", "type": "f64"}],
1598 "builder": false
1599 }]
1600 }]
1601 }"#;
1602 let api: Api = serde_json::from_str(json).unwrap();
1603 assert!(!api.modules[0].structs[0].builder);
1604 }
1605
1606 #[test]
1607 fn param_mutable_defaults_to_false() {
1608 let yaml = r#"
1609version: "0.5.0"
1610modules:
1611 - name: io
1612 functions:
1613 - name: write
1614 params:
1615 - name: data
1616 type: string
1617"#;
1618 let api: Api = serde_yaml::from_str(yaml).unwrap();
1619 assert!(!api.modules[0].functions[0].params[0].mutable);
1620 }
1621
1622 #[test]
1623 fn param_mutable_true_round_trip() {
1624 let yaml = r#"
1625version: "0.5.0"
1626modules:
1627 - name: io
1628 functions:
1629 - name: fill_buffer
1630 params:
1631 - name: buf
1632 type: bytes
1633 mutable: true
1634"#;
1635 let api: Api = serde_yaml::from_str(yaml).unwrap();
1636 assert!(api.modules[0].functions[0].params[0].mutable);
1637
1638 let json = serde_json::to_string(&api).unwrap();
1639 let back: Api = serde_json::from_str(&json).unwrap();
1640 assert!(back.modules[0].functions[0].params[0].mutable);
1641 }
1642
1643 #[test]
1644 fn param_mutable_false_explicit() {
1645 let json = r#"{
1646 "version": "0.5.0",
1647 "modules": [{
1648 "name": "io",
1649 "functions": [{
1650 "name": "read",
1651 "params": [{"name": "buf", "type": "bytes", "mutable": false}]
1652 }]
1653 }]
1654 }"#;
1655 let api: Api = serde_json::from_str(json).unwrap();
1656 assert!(!api.modules[0].functions[0].params[0].mutable);
1657 }
1658
1659 #[test]
1660 fn deprecated_and_since_default_to_none() {
1661 let yaml = r#"
1662version: "0.5.0"
1663modules:
1664 - name: math
1665 functions:
1666 - name: add
1667 params: []
1668"#;
1669 let api: Api = serde_yaml::from_str(yaml).unwrap();
1670 let f = &api.modules[0].functions[0];
1671 assert_eq!(f.deprecated, None);
1672 assert_eq!(f.since, None);
1673 }
1674
1675 #[test]
1676 fn deprecated_and_since_round_trip() {
1677 let yaml = r#"
1678version: "0.5.0"
1679modules:
1680 - name: math
1681 functions:
1682 - name: add_old
1683 params: []
1684 deprecated: "Use add_v2 instead"
1685 since: "0.1.0"
1686"#;
1687 let api: Api = serde_yaml::from_str(yaml).unwrap();
1688 let f = &api.modules[0].functions[0];
1689 assert_eq!(f.deprecated.as_deref(), Some("Use add_v2 instead"));
1690 assert_eq!(f.since.as_deref(), Some("0.1.0"));
1691
1692 let json = serde_json::to_string(&api).unwrap();
1693 let back: Api = serde_json::from_str(&json).unwrap();
1694 let f2 = &back.modules[0].functions[0];
1695 assert_eq!(f2.deprecated.as_deref(), Some("Use add_v2 instead"));
1696 assert_eq!(f2.since.as_deref(), Some("0.1.0"));
1697 }
1698
1699 #[test]
1700 fn struct_field_default_value_round_trip() {
1701 let yaml = r#"
1702version: "0.5.0"
1703modules:
1704 - name: contacts
1705 functions: []
1706 structs:
1707 - name: Contact
1708 fields:
1709 - name: name
1710 type: string
1711 - name: age
1712 type: i32
1713 default: 0
1714"#;
1715 let api: Api = serde_yaml::from_str(yaml).unwrap();
1716 let fields = &api.modules[0].structs[0].fields;
1717 assert!(fields[0].default.is_none());
1718 assert_eq!(
1719 fields[1].default,
1720 Some(serde_yaml::Value::Number(serde_yaml::Number::from(0)))
1721 );
1722 }
1723
1724 #[test]
1725 fn serialization_omits_defaulted_fields() {
1726 let api = Api {
1730 version: "0.5.0".into(),
1731 modules: vec![Module {
1732 name: "calc".into(),
1733 functions: vec![Function {
1734 name: "add".into(),
1735 params: vec![Param {
1736 name: "a".into(),
1737 ty: TypeRef::I32,
1738 mutable: false,
1739 doc: None,
1740 }],
1741 returns: Some(TypeRef::I32),
1742 doc: None,
1743 throws: false,
1744 r#async: false,
1745 cancellable: false,
1746 deprecated: None,
1747 since: None,
1748 }],
1749 interfaces: vec![],
1750 structs: vec![],
1751 enums: vec![],
1752 callbacks: vec![],
1753 listeners: vec![],
1754 errors: None,
1755 modules: vec![],
1756 }],
1757 generators: None,
1758 package: None,
1759 };
1760 let yaml = serde_yaml::to_string(&api).unwrap();
1761 for needle in [
1762 "generators",
1763 "interfaces",
1764 "structs",
1765 "enums",
1766 "callbacks",
1767 "listeners",
1768 "errors",
1769 "modules:\n", "doc",
1771 "throws",
1772 "async",
1773 "cancellable",
1774 "deprecated",
1775 "since",
1776 "mutable",
1777 "null",
1778 "[]",
1779 "false",
1780 ] {
1781 if needle == "modules:\n" {
1785 continue;
1786 }
1787 assert!(
1788 !yaml.contains(needle),
1789 "default field `{needle}` leaked into canonical YAML:\n{yaml}"
1790 );
1791 }
1792 let back: Api = serde_yaml::from_str(&yaml).unwrap();
1794 assert_eq!(back, api);
1795 }
1796
1797 #[test]
1798 fn parse_type_ref_does_not_yield_callback() {
1799 assert_eq!(
1800 parse_type_ref("callback"),
1801 Ok(TypeRef::Struct("callback".into()))
1802 );
1803 }
1804
1805 #[test]
1806 fn api_json_schema_derives() {
1807 let schema = schemars::schema_for!(Api);
1808 let json = serde_json::to_value(&schema).unwrap();
1809 assert!(json.get("$schema").is_some());
1810 assert!(json.get("properties").is_some());
1811 assert_eq!(json.get("title").and_then(|v| v.as_str()), Some("Api"));
1812 let defs = json
1813 .get("definitions")
1814 .and_then(|v| v.as_object())
1815 .expect("definitions");
1816 assert!(defs.contains_key("Module"));
1817 assert!(defs.contains_key("Function"));
1818 assert!(defs.contains_key("Param"));
1819 assert!(defs.contains_key("TypeRef"));
1820 assert!(defs.contains_key("InterfaceDef"));
1821 assert!(defs.contains_key("StructDef"));
1822 assert!(defs.contains_key("StructField"));
1823 assert!(defs.contains_key("EnumDef"));
1824 assert!(defs.contains_key("EnumVariant"));
1825 assert!(defs.contains_key("CallbackDef"));
1826 assert!(defs.contains_key("ListenerDef"));
1827 assert!(defs.contains_key("ErrorDomain"));
1828 assert!(defs.contains_key("ErrorCode"));
1829 }
1830
1831 #[test]
1832 fn interface_round_trip_yaml() {
1833 let yaml = r#"
1834version: "0.5.0"
1835modules:
1836 - name: kv
1837 interfaces:
1838 - name: Store
1839 doc: "A key/value store"
1840 constructors:
1841 - name: open
1842 params:
1843 - name: path
1844 type: string
1845 throws: true
1846 methods:
1847 - name: put
1848 params:
1849 - name: key
1850 type: string
1851 - name: value
1852 type: bytes
1853 return: bool
1854 throws: true
1855 - name: count
1856 return: i64
1857 statics:
1858 - name: default_path
1859 return: string
1860"#;
1861 let api: Api = serde_yaml::from_str(yaml).unwrap();
1862 let m = &api.modules[0];
1863 assert!(m.functions.is_empty());
1864 assert_eq!(m.interfaces.len(), 1);
1865 let i = &m.interfaces[0];
1866 assert_eq!(i.name, "Store");
1867 assert_eq!(i.doc.as_deref(), Some("A key/value store"));
1868 assert_eq!(i.constructors.len(), 1);
1869 assert!(i.constructors[0].throws);
1870 assert_eq!(i.constructors[0].returns, None);
1871 assert_eq!(i.methods.len(), 2);
1872 assert!(i.methods[0].throws);
1873 assert!(!i.methods[1].throws);
1874 assert_eq!(i.methods[1].returns, Some(TypeRef::I64));
1875 assert_eq!(i.statics.len(), 1);
1876 assert_eq!(i.statics[0].returns, Some(TypeRef::StringUtf8));
1877
1878 let out = serde_yaml::to_string(&api).unwrap();
1880 let back: Api = serde_yaml::from_str(&out).unwrap();
1881 assert_eq!(back, api);
1882 }
1883
1884 #[test]
1885 fn interfaces_default_to_empty() {
1886 let yaml = r#"
1887version: "0.5.0"
1888modules:
1889 - name: math
1890 functions: []
1891"#;
1892 let api: Api = serde_yaml::from_str(yaml).unwrap();
1893 assert!(api.modules[0].interfaces.is_empty());
1894 }
1895
1896 #[test]
1897 fn throws_defaults_to_false_and_round_trips() {
1898 let yaml = r#"
1899version: "0.5.0"
1900modules:
1901 - name: kv
1902 functions:
1903 - name: open
1904 params: []
1905 throws: true
1906 - name: count
1907 params: []
1908"#;
1909 let api: Api = serde_yaml::from_str(yaml).unwrap();
1910 assert!(api.modules[0].functions[0].throws);
1911 assert!(!api.modules[0].functions[1].throws);
1912 let json = serde_json::to_string(&api).unwrap();
1913 let back: Api = serde_json::from_str(&json).unwrap();
1914 assert!(back.modules[0].functions[0].throws);
1915 assert!(!back.modules[0].functions[1].throws);
1916 }
1917
1918 #[test]
1919 fn typeref_interface_serializes_as_name() {
1920 let ty = TypeRef::Interface("Store".to_string());
1921 let json = serde_json::to_string(&ty).unwrap();
1922 assert_eq!(json, r#""Store""#);
1923 let back: TypeRef = serde_json::from_str(&json).unwrap();
1925 assert_eq!(back, TypeRef::Struct("Store".into()));
1926 }
1927
1928 #[test]
1929 fn typeref_json_schema_is_string_with_description() {
1930 let schema = schemars::schema_for!(TypeRef);
1931 let json = serde_json::to_value(&schema).unwrap();
1932 assert_eq!(json.get("type").and_then(|v| v.as_str()), Some("string"));
1933 assert!(json
1934 .get("description")
1935 .and_then(|v| v.as_str())
1936 .is_some_and(|s| s.contains("handle<") && s.contains("iter<")));
1937 }
1938}