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