1use std::collections::BTreeMap;
12
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16pub const CURRENT_SCHEMA_VERSION: &str = "0.4.0";
28
29pub const SUPPORTED_VERSIONS: &[&str] = &[CURRENT_SCHEMA_VERSION];
35
36#[allow(clippy::trivially_copy_pass_by_ref)]
40fn is_false(b: &bool) -> bool {
41 !*b
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
52#[schemars(description = "Top-level WeaveFFI API definition.")]
53pub struct Api {
54 pub version: String,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub package: Option<Package>,
63 pub modules: Vec<Module>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
71 #[schemars(with = "Option<BTreeMap<String, serde_json::Value>>")]
72 pub generators: Option<BTreeMap<String, toml::Value>>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
84#[schemars(description = "Package identity for the generated consumer artifacts.")]
85pub struct Package {
86 pub name: String,
89 pub version: String,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub description: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub license: Option<String>,
99 #[serde(default, skip_serializing_if = "Vec::is_empty")]
103 pub authors: Vec<String>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub homepage: Option<String>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub repository: Option<String>,
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
121#[schemars(
122 description = "A WeaveFFI module: a named group of functions, types, callbacks, listeners, and errors."
123)]
124pub struct Module {
125 pub name: String,
128 pub functions: Vec<Function>,
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub structs: Vec<StructDef>,
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub enums: Vec<EnumDef>,
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
138 pub callbacks: Vec<CallbackDef>,
139 #[serde(default, skip_serializing_if = "Vec::is_empty")]
141 pub listeners: Vec<ListenerDef>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub errors: Option<ErrorDomain>,
146 #[serde(default, skip_serializing_if = "Vec::is_empty")]
148 pub modules: Vec<Module>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
158pub struct Function {
159 pub name: String,
162 pub params: Vec<Param>,
164 #[serde(rename = "return", default, skip_serializing_if = "Option::is_none")]
167 pub returns: Option<TypeRef>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub doc: Option<String>,
172 #[serde(default, rename = "async", skip_serializing_if = "is_false")]
175 pub r#async: bool,
176 #[serde(default, skip_serializing_if = "is_false")]
179 pub cancellable: bool,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub deprecated: Option<String>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub since: Option<String>,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
192pub struct Param {
193 pub name: String,
195 #[serde(rename = "type")]
197 pub ty: TypeRef,
198 #[serde(default, skip_serializing_if = "is_false")]
201 pub mutable: bool,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub doc: Option<String>,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
215pub struct CallbackDef {
216 pub name: String,
219 pub params: Vec<Param>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub doc: Option<String>,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
234pub struct ListenerDef {
235 pub name: String,
238 pub event_callback: String,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub doc: Option<String>,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Hash)]
257pub enum TypeRef {
258 I8,
260 I16,
262 I32,
264 I64,
266 U8,
268 U16,
270 U32,
272 U64,
274 F32,
276 F64,
278 Bool,
280 StringUtf8,
282 Bytes,
284 Handle,
288 TypedHandle(String),
291 Struct(String),
301 Enum(String),
303 BorrowedStr,
306 BorrowedBytes,
309 Optional(Box<TypeRef>),
311 List(Box<TypeRef>),
313 Map(Box<TypeRef>, Box<TypeRef>),
316 Iterator(Box<TypeRef>),
319}
320
321pub fn parse_type_ref(s: &str) -> Result<TypeRef, String> {
336 let s = s.trim();
337 if s.is_empty() {
338 return Err("empty type reference".to_string());
339 }
340 if s.starts_with('[') && s.ends_with(']') {
341 let inner = &s[1..s.len() - 1];
342 return parse_type_ref(inner).map(|t| TypeRef::List(Box::new(t)));
343 }
344 if s.starts_with('{') && s.ends_with('}') {
345 let inner = &s[1..s.len() - 1];
346 let colon = inner
347 .find(':')
348 .ok_or_else(|| "map type missing ':' separator".to_string())?;
349 let key = parse_type_ref(&inner[..colon])?;
350 let val = parse_type_ref(&inner[colon + 1..])?;
351 return Ok(TypeRef::Map(Box::new(key), Box::new(val)));
352 }
353 if let Some(inner) = s.strip_suffix('?') {
354 return parse_type_ref(inner).map(|t| TypeRef::Optional(Box::new(t)));
355 }
356 if let Some(inner) = s
357 .strip_prefix("handle<")
358 .and_then(|rest| rest.strip_suffix('>'))
359 {
360 return Ok(TypeRef::TypedHandle(inner.into()));
361 }
362 if let Some(inner) = s
363 .strip_prefix("iter<")
364 .and_then(|rest| rest.strip_suffix('>'))
365 {
366 return parse_type_ref(inner).map(|t| TypeRef::Iterator(Box::new(t)));
367 }
368 match s {
369 "i8" => Ok(TypeRef::I8),
370 "i16" => Ok(TypeRef::I16),
371 "i32" => Ok(TypeRef::I32),
372 "i64" => Ok(TypeRef::I64),
373 "u8" => Ok(TypeRef::U8),
374 "u16" => Ok(TypeRef::U16),
375 "u32" => Ok(TypeRef::U32),
376 "u64" => Ok(TypeRef::U64),
377 "f32" => Ok(TypeRef::F32),
378 "f64" => Ok(TypeRef::F64),
379 "bool" => Ok(TypeRef::Bool),
380 "string" => Ok(TypeRef::StringUtf8),
381 "bytes" => Ok(TypeRef::Bytes),
382 "handle" => Ok(TypeRef::Handle),
383 "&str" => Ok(TypeRef::BorrowedStr),
384 "&[u8]" => Ok(TypeRef::BorrowedBytes),
385 name => Ok(TypeRef::Struct(name.to_string())),
386 }
387}
388
389fn type_ref_to_string(ty: &TypeRef) -> String {
390 match ty {
391 TypeRef::I8 => "i8".to_string(),
392 TypeRef::I16 => "i16".to_string(),
393 TypeRef::I32 => "i32".to_string(),
394 TypeRef::I64 => "i64".to_string(),
395 TypeRef::U8 => "u8".to_string(),
396 TypeRef::U16 => "u16".to_string(),
397 TypeRef::U32 => "u32".to_string(),
398 TypeRef::U64 => "u64".to_string(),
399 TypeRef::F32 => "f32".to_string(),
400 TypeRef::F64 => "f64".to_string(),
401 TypeRef::Bool => "bool".to_string(),
402 TypeRef::StringUtf8 => "string".to_string(),
403 TypeRef::Bytes => "bytes".to_string(),
404 TypeRef::BorrowedStr => "&str".to_string(),
405 TypeRef::BorrowedBytes => "&[u8]".to_string(),
406 TypeRef::Handle => "handle".to_string(),
407 TypeRef::TypedHandle(name) => format!("handle<{name}>"),
408 TypeRef::Struct(name) | TypeRef::Enum(name) => name.clone(),
409 TypeRef::Optional(inner) => format!("{}?", type_ref_to_string(inner)),
410 TypeRef::List(inner) => format!("[{}]", type_ref_to_string(inner)),
411 TypeRef::Map(k, v) => format!("{{{}:{}}}", type_ref_to_string(k), type_ref_to_string(v)),
412 TypeRef::Iterator(inner) => format!("iter<{}>", type_ref_to_string(inner)),
413 }
414}
415
416impl Serialize for TypeRef {
417 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
418 where
419 S: serde::Serializer,
420 {
421 serializer.serialize_str(&type_ref_to_string(self))
422 }
423}
424
425impl<'de> Deserialize<'de> for TypeRef {
426 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
427 where
428 D: serde::Deserializer<'de>,
429 {
430 let s = String::deserialize(deserializer)?;
431 parse_type_ref(&s).map_err(serde::de::Error::custom)
432 }
433}
434
435impl JsonSchema for TypeRef {
440 fn schema_name() -> String {
441 "TypeRef".to_string()
442 }
443
444 fn schema_id() -> std::borrow::Cow<'static, str> {
445 std::borrow::Cow::Borrowed(concat!(module_path!(), "::TypeRef"))
446 }
447
448 fn json_schema(_generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
449 let mut schema = schemars::schema::SchemaObject {
450 instance_type: Some(schemars::schema::InstanceType::String.into()),
451 ..Default::default()
452 };
453 let meta = schema.metadata();
454 meta.title = Some("TypeRef".to_string());
455 meta.description = Some(
456 "Reference to a type. Encoded as a string with custom syntax: \
457 primitives (`i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, \
458 `f32`, `f64`, `bool`, `string`, `bytes`, `handle`), \
459 borrowed types (`&str`, `&[u8]`), typed handles (`handle<{name}>`), \
460 iterators (`iter<{T}>`), lists (`[{T}]`), maps (`{{K:V}}`), \
461 optionals (`{T}?`), or any user-defined struct/enum name."
462 .to_string(),
463 );
464 schema.into()
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
477#[schemars(
478 description = "An enum type. C-style when every variant is a bare discriminant; an algebraic sum type when any variant declares fields."
479)]
480pub struct EnumDef {
481 pub name: String,
483 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub doc: Option<String>,
487 pub variants: Vec<EnumVariant>,
490}
491
492impl EnumDef {
493 pub fn is_rich(&self) -> bool {
499 self.variants.iter().any(|v| !v.fields.is_empty())
500 }
501}
502
503#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
506pub struct EnumVariant {
507 pub name: String,
509 pub value: i32,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
515 pub doc: Option<String>,
516 #[serde(default, skip_serializing_if = "Vec::is_empty")]
521 pub fields: Vec<StructField>,
522}
523
524#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
527#[schemars(description = "A struct (record) type with named fields.")]
528pub struct StructDef {
529 pub name: String,
531 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub doc: Option<String>,
535 pub fields: Vec<StructField>,
538 #[serde(default, skip_serializing_if = "is_false")]
541 pub builder: bool,
542}
543
544#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
547pub struct StructField {
548 pub name: String,
550 #[serde(rename = "type")]
552 pub ty: TypeRef,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub doc: Option<String>,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
562 #[schemars(with = "Option<serde_json::Value>")]
563 pub default: Option<serde_yaml::Value>,
564}
565
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
569pub struct ErrorDomain {
570 pub name: String,
573 pub codes: Vec<ErrorCode>,
575}
576
577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
579pub struct ErrorCode {
580 pub name: String,
583 pub code: i32,
585 pub message: String,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
590 pub doc: Option<String>,
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn struct_def_round_trip_yaml() {
599 let yaml = r#"
600version: "0.4.0"
601modules:
602 - name: geometry
603 functions: []
604 structs:
605 - name: Point
606 doc: "A 2D point"
607 fields:
608 - name: x
609 type: f64
610 - name: "y"
611 type: f64
612 doc: "Y coordinate"
613"#;
614 let api: Api = serde_yaml::from_str(yaml).unwrap();
615 let m = &api.modules[0];
616 assert_eq!(m.structs.len(), 1);
617 let s = &m.structs[0];
618 assert_eq!(s.name, "Point");
619 assert_eq!(s.doc.as_deref(), Some("A 2D point"));
620 assert_eq!(s.fields.len(), 2);
621 assert_eq!(s.fields[0].name, "x");
622 assert_eq!(s.fields[0].ty, TypeRef::F64);
623 assert_eq!(s.fields[0].doc, None);
624 assert_eq!(s.fields[1].name, "y");
625 assert_eq!(s.fields[1].doc.as_deref(), Some("Y coordinate"));
626 }
627
628 #[test]
629 fn struct_def_round_trip_json() {
630 let json = r#"{
631 "version": "0.4.0",
632 "modules": [{
633 "name": "geo",
634 "functions": [],
635 "structs": [{
636 "name": "Rect",
637 "fields": [
638 {"name": "width", "type": "i32"},
639 {"name": "height", "type": "i32"}
640 ]
641 }]
642 }]
643 }"#;
644 let api: Api = serde_json::from_str(json).unwrap();
645 let s = &api.modules[0].structs[0];
646 assert_eq!(s.name, "Rect");
647 assert_eq!(s.doc, None);
648 assert_eq!(s.fields[0].ty, TypeRef::I32);
649 }
650
651 #[test]
652 fn structs_default_to_empty() {
653 let yaml = r#"
654version: "0.4.0"
655modules:
656 - name: math
657 functions: []
658"#;
659 let api: Api = serde_yaml::from_str(yaml).unwrap();
660 assert!(api.modules[0].structs.is_empty());
661 }
662
663 #[test]
664 fn package_block_round_trips_yaml() {
665 let yaml = r#"
666version: "0.4.0"
667package:
668 name: kvstore
669 version: 1.2.0
670 description: "An embedded key/value store"
671 license: MIT
672 authors:
673 - "Ada Lovelace <ada@example.com>"
674 homepage: "https://example.com/kvstore"
675 repository: "https://github.com/example/kvstore"
676modules:
677 - name: kv
678 functions: []
679"#;
680 let api: Api = serde_yaml::from_str(yaml).unwrap();
681 let pkg = api.package.as_ref().expect("package should parse");
682 assert_eq!(pkg.name, "kvstore");
683 assert_eq!(pkg.version, "1.2.0");
684 assert_eq!(
685 pkg.description.as_deref(),
686 Some("An embedded key/value store")
687 );
688 assert_eq!(pkg.license.as_deref(), Some("MIT"));
689 assert_eq!(pkg.authors, vec!["Ada Lovelace <ada@example.com>"]);
690 assert_eq!(pkg.homepage.as_deref(), Some("https://example.com/kvstore"));
691 assert_eq!(
692 pkg.repository.as_deref(),
693 Some("https://github.com/example/kvstore")
694 );
695
696 let out = serde_yaml::to_string(&api).unwrap();
698 assert!(out.contains("name: kvstore"));
699 assert!(out.contains("version: 1.2.0"));
700 }
701
702 #[test]
703 fn package_is_optional() {
704 let yaml = r#"
705version: "0.4.0"
706modules:
707 - name: math
708 functions: []
709"#;
710 let api: Api = serde_yaml::from_str(yaml).unwrap();
711 assert!(api.package.is_none());
712 let out = serde_yaml::to_string(&api).unwrap();
714 assert!(!out.contains("package:"));
715 }
716
717 #[test]
718 fn package_minimal_requires_name_and_version() {
719 let yaml = r#"
720version: "0.4.0"
721package:
722 name: tiny
723 version: 0.0.1
724modules: []
725"#;
726 let api: Api = serde_yaml::from_str(yaml).unwrap();
727 let pkg = api.package.as_ref().unwrap();
728 assert_eq!(pkg.name, "tiny");
729 assert_eq!(pkg.version, "0.0.1");
730 assert!(pkg.description.is_none());
731 assert!(pkg.authors.is_empty());
732 }
733
734 #[test]
735 fn typeref_struct_variant_serializes() {
736 let ty = TypeRef::Struct("Point".to_string());
737 let json = serde_json::to_string(&ty).unwrap();
738 assert_eq!(json, r#""Point""#);
739 let back: TypeRef = serde_json::from_str(&json).unwrap();
740 assert_eq!(back, ty);
741 }
742
743 #[test]
744 fn struct_field_with_struct_type() {
745 let field = StructField {
746 name: "origin".to_string(),
747 ty: TypeRef::Struct("Point".to_string()),
748 doc: None,
749 default: None,
750 };
751 let json = serde_json::to_string(&field).unwrap();
752 let back: StructField = serde_json::from_str(&json).unwrap();
753 assert_eq!(back, field);
754 }
755
756 #[test]
757 fn typeref_is_not_copy() {
758 let a = TypeRef::Struct("Foo".to_string());
759 let b = a.clone();
760 assert_eq!(a, b);
761 }
762
763 #[test]
764 fn enum_def_round_trip_yaml() {
765 let yaml = r#"
766version: "0.4.0"
767modules:
768 - name: graphics
769 functions: []
770 enums:
771 - name: Color
772 doc: "Primary colors"
773 variants:
774 - name: Red
775 value: 0
776 - name: Green
777 value: 1
778 doc: "The color green"
779 - name: Blue
780 value: 2
781"#;
782 let api: Api = serde_yaml::from_str(yaml).unwrap();
783 let m = &api.modules[0];
784 assert_eq!(m.enums.len(), 1);
785 let e = &m.enums[0];
786 assert_eq!(e.name, "Color");
787 assert_eq!(e.doc.as_deref(), Some("Primary colors"));
788 assert_eq!(e.variants.len(), 3);
789 assert_eq!(e.variants[0].name, "Red");
790 assert_eq!(e.variants[0].value, 0);
791 assert_eq!(e.variants[0].doc, None);
792 assert_eq!(e.variants[1].name, "Green");
793 assert_eq!(e.variants[1].value, 1);
794 assert_eq!(e.variants[1].doc.as_deref(), Some("The color green"));
795 assert_eq!(e.variants[2].name, "Blue");
796 assert_eq!(e.variants[2].value, 2);
797 }
798
799 #[test]
800 fn enum_def_round_trip_json() {
801 let json = r#"{
802 "version": "0.4.0",
803 "modules": [{
804 "name": "status",
805 "functions": [],
806 "enums": [{
807 "name": "Status",
808 "variants": [
809 {"name": "Ok", "value": 0},
810 {"name": "Error", "value": 1}
811 ]
812 }]
813 }]
814 }"#;
815 let api: Api = serde_json::from_str(json).unwrap();
816 let e = &api.modules[0].enums[0];
817 assert_eq!(e.name, "Status");
818 assert_eq!(e.doc, None);
819 assert_eq!(e.variants.len(), 2);
820 assert_eq!(e.variants[1].value, 1);
821 }
822
823 #[test]
824 fn enums_default_to_empty() {
825 let yaml = r#"
826version: "0.4.0"
827modules:
828 - name: math
829 functions: []
830"#;
831 let api: Api = serde_yaml::from_str(yaml).unwrap();
832 assert!(api.modules[0].enums.is_empty());
833 }
834
835 #[test]
836 fn typeref_enum_variant_serializes_as_name() {
837 let ty = TypeRef::Enum("Color".to_string());
838 let json = serde_json::to_string(&ty).unwrap();
839 assert_eq!(json, r#""Color""#);
840 }
841
842 #[test]
843 fn enum_def_clone_and_eq() {
844 let e = EnumDef {
845 name: "Direction".to_string(),
846 doc: Some("Cardinal directions".to_string()),
847 variants: vec![
848 EnumVariant {
849 name: "North".to_string(),
850 value: 0,
851 doc: None,
852 fields: vec![],
853 },
854 EnumVariant {
855 name: "South".to_string(),
856 value: 1,
857 doc: None,
858 fields: vec![],
859 },
860 ],
861 };
862 assert_eq!(e, e.clone());
863 assert!(!e.is_rich());
864 }
865
866 #[test]
867 fn enum_def_is_rich_when_a_variant_has_fields() {
868 let e = EnumDef {
869 name: "Shape".to_string(),
870 doc: None,
871 variants: vec![
872 EnumVariant {
873 name: "Circle".to_string(),
874 value: 0,
875 doc: None,
876 fields: vec![StructField {
877 name: "radius".to_string(),
878 ty: TypeRef::F64,
879 doc: None,
880 default: None,
881 }],
882 },
883 EnumVariant {
884 name: "Empty".to_string(),
885 value: 1,
886 doc: None,
887 fields: vec![],
888 },
889 ],
890 };
891 assert!(e.is_rich());
892 }
893
894 #[test]
895 fn struct_def_clone_and_eq() {
896 let s = StructDef {
897 name: "Color".to_string(),
898 doc: Some("RGB color".to_string()),
899 fields: vec![
900 StructField {
901 name: "r".to_string(),
902 ty: TypeRef::U32,
903 doc: None,
904 default: None,
905 },
906 StructField {
907 name: "g".to_string(),
908 ty: TypeRef::U32,
909 doc: None,
910 default: None,
911 },
912 StructField {
913 name: "b".to_string(),
914 ty: TypeRef::U32,
915 doc: None,
916 default: None,
917 },
918 ],
919 builder: false,
920 };
921 assert_eq!(s, s.clone());
922 }
923
924 #[test]
925 fn parse_type_ref_primitives() {
926 assert_eq!(parse_type_ref("i32"), Ok(TypeRef::I32));
927 assert_eq!(parse_type_ref("u32"), Ok(TypeRef::U32));
928 assert_eq!(parse_type_ref("i64"), Ok(TypeRef::I64));
929 assert_eq!(parse_type_ref("f64"), Ok(TypeRef::F64));
930 assert_eq!(parse_type_ref("bool"), Ok(TypeRef::Bool));
931 assert_eq!(parse_type_ref("string"), Ok(TypeRef::StringUtf8));
932 assert_eq!(parse_type_ref("bytes"), Ok(TypeRef::Bytes));
933 assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
934 }
935
936 #[test]
937 fn parse_type_ref_struct() {
938 assert_eq!(
939 parse_type_ref("Contact"),
940 Ok(TypeRef::Struct("Contact".into()))
941 );
942 assert_eq!(
943 parse_type_ref("MyWidget"),
944 Ok(TypeRef::Struct("MyWidget".into()))
945 );
946 }
947
948 #[test]
949 fn parse_type_ref_optional() {
950 assert_eq!(
951 parse_type_ref("string?"),
952 Ok(TypeRef::Optional(Box::new(TypeRef::StringUtf8)))
953 );
954 assert_eq!(
955 parse_type_ref("i32?"),
956 Ok(TypeRef::Optional(Box::new(TypeRef::I32)))
957 );
958 assert_eq!(
959 parse_type_ref("Contact?"),
960 Ok(TypeRef::Optional(Box::new(TypeRef::Struct(
961 "Contact".into()
962 ))))
963 );
964 }
965
966 #[test]
967 fn parse_type_ref_list() {
968 assert_eq!(
969 parse_type_ref("[i32]"),
970 Ok(TypeRef::List(Box::new(TypeRef::I32)))
971 );
972 assert_eq!(
973 parse_type_ref("[string]"),
974 Ok(TypeRef::List(Box::new(TypeRef::StringUtf8)))
975 );
976 assert_eq!(
977 parse_type_ref("[Contact]"),
978 Ok(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
979 );
980 }
981
982 #[test]
983 fn parse_type_ref_nested() {
984 assert_eq!(
985 parse_type_ref("[i32?]"),
986 Ok(TypeRef::List(Box::new(TypeRef::Optional(Box::new(
987 TypeRef::I32
988 )))))
989 );
990 assert_eq!(
991 parse_type_ref("[Contact]?"),
992 Ok(TypeRef::Optional(Box::new(TypeRef::List(Box::new(
993 TypeRef::Struct("Contact".into())
994 )))))
995 );
996 }
997
998 #[test]
999 fn parse_type_ref_empty_is_error() {
1000 assert!(parse_type_ref("").is_err());
1001 assert!(parse_type_ref(" ").is_err());
1002 }
1003
1004 #[test]
1005 fn typeref_primitive_round_trips() {
1006 for ty in [
1007 TypeRef::I32,
1008 TypeRef::U32,
1009 TypeRef::I64,
1010 TypeRef::F64,
1011 TypeRef::Bool,
1012 TypeRef::StringUtf8,
1013 TypeRef::Bytes,
1014 TypeRef::Handle,
1015 ] {
1016 let json = serde_json::to_string(&ty).unwrap();
1017 let back: TypeRef = serde_json::from_str(&json).unwrap();
1018 assert_eq!(back, ty);
1019 }
1020 }
1021
1022 #[test]
1023 fn typeref_optional_round_trip() {
1024 let ty = TypeRef::Optional(Box::new(TypeRef::StringUtf8));
1025 let json = serde_json::to_string(&ty).unwrap();
1026 assert_eq!(json, r#""string?""#);
1027 let back: TypeRef = serde_json::from_str(&json).unwrap();
1028 assert_eq!(back, ty);
1029 }
1030
1031 #[test]
1032 fn typeref_list_round_trip() {
1033 let ty = TypeRef::List(Box::new(TypeRef::I32));
1034 let json = serde_json::to_string(&ty).unwrap();
1035 assert_eq!(json, r#""[i32]""#);
1036 let back: TypeRef = serde_json::from_str(&json).unwrap();
1037 assert_eq!(back, ty);
1038 }
1039
1040 #[test]
1041 fn typeref_optional_struct_round_trip() {
1042 let ty = TypeRef::Optional(Box::new(TypeRef::Struct("Contact".into())));
1043 let json = serde_json::to_string(&ty).unwrap();
1044 assert_eq!(json, r#""Contact?""#);
1045 let back: TypeRef = serde_json::from_str(&json).unwrap();
1046 assert_eq!(back, ty);
1047 }
1048
1049 #[test]
1050 fn typeref_list_struct_round_trip() {
1051 let ty = TypeRef::List(Box::new(TypeRef::Struct("Contact".into())));
1052 let json = serde_json::to_string(&ty).unwrap();
1053 assert_eq!(json, r#""[Contact]""#);
1054 let back: TypeRef = serde_json::from_str(&json).unwrap();
1055 assert_eq!(back, ty);
1056 }
1057
1058 #[test]
1059 fn typeref_optional_yaml_deser() {
1060 let yaml = r#"
1061version: "0.4.0"
1062modules:
1063 - name: contacts
1064 functions:
1065 - name: find
1066 params:
1067 - name: id
1068 type: i32
1069 return: "Contact?"
1070"#;
1071 let api: Api = serde_yaml::from_str(yaml).unwrap();
1072 let f = &api.modules[0].functions[0];
1073 assert_eq!(
1074 f.returns,
1075 Some(TypeRef::Optional(Box::new(TypeRef::Struct(
1076 "Contact".into()
1077 ))))
1078 );
1079 }
1080
1081 #[test]
1082 fn typeref_list_yaml_deser() {
1083 let yaml = r#"
1084version: "0.4.0"
1085modules:
1086 - name: contacts
1087 functions:
1088 - name: list_all
1089 params: []
1090 return: "[Contact]"
1091"#;
1092 let api: Api = serde_yaml::from_str(yaml).unwrap();
1093 let f = &api.modules[0].functions[0];
1094 assert_eq!(
1095 f.returns,
1096 Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
1097 );
1098 }
1099
1100 #[test]
1101 fn typeref_hash_works_with_box_variants() {
1102 use std::collections::HashSet;
1103 let mut set = HashSet::new();
1104 set.insert(TypeRef::I32);
1105 set.insert(TypeRef::Optional(Box::new(TypeRef::I32)));
1106 set.insert(TypeRef::List(Box::new(TypeRef::I32)));
1107 set.insert(TypeRef::Optional(Box::new(TypeRef::Struct("Foo".into()))));
1108 set.insert(TypeRef::Map(
1109 Box::new(TypeRef::StringUtf8),
1110 Box::new(TypeRef::I32),
1111 ));
1112 assert_eq!(set.len(), 5);
1113 }
1114
1115 #[test]
1116 fn parse_type_ref_map_primitives() {
1117 assert_eq!(
1118 parse_type_ref("{string:i32}"),
1119 Ok(TypeRef::Map(
1120 Box::new(TypeRef::StringUtf8),
1121 Box::new(TypeRef::I32)
1122 ))
1123 );
1124 }
1125
1126 #[test]
1127 fn parse_type_ref_map_struct_value() {
1128 assert_eq!(
1129 parse_type_ref("{string:Contact}"),
1130 Ok(TypeRef::Map(
1131 Box::new(TypeRef::StringUtf8),
1132 Box::new(TypeRef::Struct("Contact".into()))
1133 ))
1134 );
1135 }
1136
1137 #[test]
1138 fn parse_type_ref_map_nested_value() {
1139 assert_eq!(
1140 parse_type_ref("{string:[i32]}"),
1141 Ok(TypeRef::Map(
1142 Box::new(TypeRef::StringUtf8),
1143 Box::new(TypeRef::List(Box::new(TypeRef::I32)))
1144 ))
1145 );
1146 }
1147
1148 #[test]
1149 fn parse_type_ref_map_missing_colon() {
1150 assert!(parse_type_ref("{string}").is_err());
1151 }
1152
1153 #[test]
1154 fn typeref_map_round_trip() {
1155 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
1156 let json = serde_json::to_string(&ty).unwrap();
1157 assert_eq!(json, r#""{string:i32}""#);
1158 let back: TypeRef = serde_json::from_str(&json).unwrap();
1159 assert_eq!(back, ty);
1160 }
1161
1162 #[test]
1163 fn typeref_map_struct_round_trip() {
1164 let ty = TypeRef::Map(
1165 Box::new(TypeRef::StringUtf8),
1166 Box::new(TypeRef::Struct("Contact".into())),
1167 );
1168 let json = serde_json::to_string(&ty).unwrap();
1169 assert_eq!(json, r#""{string:Contact}""#);
1170 let back: TypeRef = serde_json::from_str(&json).unwrap();
1171 assert_eq!(back, ty);
1172 }
1173
1174 #[test]
1175 fn typeref_map_yaml_deser() {
1176 let yaml = r#"
1177version: "0.4.0"
1178modules:
1179 - name: contacts
1180 functions:
1181 - name: get_metadata
1182 params: []
1183 return: "{string:i32}"
1184"#;
1185 let api: Api = serde_yaml::from_str(yaml).unwrap();
1186 let f = &api.modules[0].functions[0];
1187 assert_eq!(
1188 f.returns,
1189 Some(TypeRef::Map(
1190 Box::new(TypeRef::StringUtf8),
1191 Box::new(TypeRef::I32)
1192 ))
1193 );
1194 }
1195
1196 #[test]
1197 fn typeref_optional_map_round_trip() {
1198 let ty = TypeRef::Optional(Box::new(TypeRef::Map(
1199 Box::new(TypeRef::StringUtf8),
1200 Box::new(TypeRef::I32),
1201 )));
1202 let json = serde_json::to_string(&ty).unwrap();
1203 assert_eq!(json, r#""{string:i32}?""#);
1204 let back: TypeRef = serde_json::from_str(&json).unwrap();
1205 assert_eq!(back, ty);
1206 }
1207
1208 #[test]
1209 fn parse_map_string_to_i32() {
1210 assert_eq!(
1211 parse_type_ref("{string:i32}"),
1212 Ok(TypeRef::Map(
1213 Box::new(TypeRef::StringUtf8),
1214 Box::new(TypeRef::I32),
1215 ))
1216 );
1217 }
1218
1219 #[test]
1220 fn parse_map_string_to_struct() {
1221 assert_eq!(
1222 parse_type_ref("{string:Contact}"),
1223 Ok(TypeRef::Map(
1224 Box::new(TypeRef::StringUtf8),
1225 Box::new(TypeRef::Struct("Contact".into())),
1226 ))
1227 );
1228 }
1229
1230 #[test]
1231 fn parse_map_roundtrip() {
1232 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
1233 let json = serde_json::to_string(&ty).unwrap();
1234 let back: TypeRef = serde_json::from_str(&json).unwrap();
1235 assert_eq!(back, ty);
1236 }
1237
1238 #[test]
1239 fn parse_optional_map() {
1240 assert_eq!(
1241 parse_type_ref("{string:i32}?"),
1242 Ok(TypeRef::Optional(Box::new(TypeRef::Map(
1243 Box::new(TypeRef::StringUtf8),
1244 Box::new(TypeRef::I32),
1245 ))))
1246 );
1247 }
1248
1249 #[test]
1250 fn parse_map_of_lists() {
1251 assert_eq!(
1252 parse_type_ref("{string:[i32]}"),
1253 Ok(TypeRef::Map(
1254 Box::new(TypeRef::StringUtf8),
1255 Box::new(TypeRef::List(Box::new(TypeRef::I32))),
1256 ))
1257 );
1258 }
1259
1260 #[test]
1261 fn parse_type_ref_iterator() {
1262 assert_eq!(
1263 parse_type_ref("iter<i32>"),
1264 Ok(TypeRef::Iterator(Box::new(TypeRef::I32)))
1265 );
1266 assert_eq!(
1267 parse_type_ref("iter<string>"),
1268 Ok(TypeRef::Iterator(Box::new(TypeRef::StringUtf8)))
1269 );
1270 assert_eq!(
1271 parse_type_ref("iter<Contact>"),
1272 Ok(TypeRef::Iterator(Box::new(TypeRef::Struct(
1273 "Contact".into()
1274 ))))
1275 );
1276 }
1277
1278 #[test]
1279 fn typeref_iterator_round_trip() {
1280 let ty = TypeRef::Iterator(Box::new(TypeRef::I32));
1281 let json = serde_json::to_string(&ty).unwrap();
1282 assert_eq!(json, r#""iter<i32>""#);
1283 let back: TypeRef = serde_json::from_str(&json).unwrap();
1284 assert_eq!(back, ty);
1285 }
1286
1287 #[test]
1288 fn typeref_iterator_struct_round_trip() {
1289 let ty = TypeRef::Iterator(Box::new(TypeRef::Struct("Contact".into())));
1290 let json = serde_json::to_string(&ty).unwrap();
1291 assert_eq!(json, r#""iter<Contact>""#);
1292 let back: TypeRef = serde_json::from_str(&json).unwrap();
1293 assert_eq!(back, ty);
1294 }
1295
1296 #[test]
1297 fn parse_type_ref_borrowed() {
1298 assert_eq!(parse_type_ref("&str"), Ok(TypeRef::BorrowedStr));
1299 assert_eq!(parse_type_ref("&[u8]"), Ok(TypeRef::BorrowedBytes));
1300 }
1301
1302 #[test]
1303 fn typeref_borrowed_round_trip() {
1304 for ty in [TypeRef::BorrowedStr, TypeRef::BorrowedBytes] {
1305 let json = serde_json::to_string(&ty).unwrap();
1306 let back: TypeRef = serde_json::from_str(&json).unwrap();
1307 assert_eq!(back, ty);
1308 }
1309 }
1310
1311 #[test]
1312 fn typeref_borrowed_str_serializes_as_ampersand_str() {
1313 let json = serde_json::to_string(&TypeRef::BorrowedStr).unwrap();
1314 assert_eq!(json, r#""&str""#);
1315 }
1316
1317 #[test]
1318 fn typeref_borrowed_bytes_serializes_as_ampersand_u8() {
1319 let json = serde_json::to_string(&TypeRef::BorrowedBytes).unwrap();
1320 assert_eq!(json, r#""&[u8]""#);
1321 }
1322
1323 #[test]
1324 fn typeref_borrowed_yaml_deser() {
1325 let yaml = r#"
1326version: "0.4.0"
1327modules:
1328 - name: io
1329 functions:
1330 - name: write
1331 params:
1332 - name: data
1333 type: "&str"
1334 - name: raw
1335 type: "&[u8]"
1336"#;
1337 let api: Api = serde_yaml::from_str(yaml).unwrap();
1338 let f = &api.modules[0].functions[0];
1339 assert_eq!(f.params[0].ty, TypeRef::BorrowedStr);
1340 assert_eq!(f.params[1].ty, TypeRef::BorrowedBytes);
1341 }
1342
1343 #[test]
1344 fn parse_typed_handle() {
1345 assert_eq!(
1346 parse_type_ref("handle<Session>"),
1347 Ok(TypeRef::TypedHandle("Session".into()))
1348 );
1349 assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
1350 }
1351
1352 #[test]
1353 fn generators_field_parses_from_yaml() {
1354 let yaml = r#"
1355version: "0.4.0"
1356modules:
1357 - name: math
1358 functions: []
1359generators:
1360 swift:
1361 module_name: MySwiftModule
1362 android:
1363 package: com.example.app
1364"#;
1365 let api: Api = serde_yaml::from_str(yaml).unwrap();
1366 let generators = api.generators.as_ref().unwrap();
1367 let swift = generators["swift"].as_table().unwrap();
1368 assert_eq!(swift["module_name"].as_str(), Some("MySwiftModule"));
1369 let android = generators["android"].as_table().unwrap();
1370 assert_eq!(android["package"].as_str(), Some("com.example.app"));
1371 }
1372
1373 #[test]
1374 fn generators_defaults_to_none() {
1375 let yaml = r#"
1376version: "0.4.0"
1377modules:
1378 - name: math
1379 functions: []
1380"#;
1381 let api: Api = serde_yaml::from_str(yaml).unwrap();
1382 assert!(api.generators.is_none());
1383 }
1384
1385 #[test]
1386 fn parse_typed_handle_roundtrip() {
1387 let ty = TypeRef::TypedHandle("Connection".into());
1388 let json = serde_json::to_string(&ty).unwrap();
1389 assert_eq!(json, r#""handle<Connection>""#);
1390 let back: TypeRef = serde_json::from_str(&json).unwrap();
1391 assert_eq!(back, ty);
1392 }
1393
1394 #[test]
1395 fn callback_def_round_trip_yaml() {
1396 let yaml = r#"
1397version: "0.4.0"
1398modules:
1399 - name: events
1400 functions: []
1401 callbacks:
1402 - name: on_data
1403 params:
1404 - name: payload
1405 type: string
1406 doc: "Fired when data arrives"
1407"#;
1408 let api: Api = serde_yaml::from_str(yaml).unwrap();
1409 let m = &api.modules[0];
1410 assert_eq!(m.callbacks.len(), 1);
1411 let cb = &m.callbacks[0];
1412 assert_eq!(cb.name, "on_data");
1413 assert_eq!(cb.params.len(), 1);
1414 assert_eq!(cb.params[0].name, "payload");
1415 assert_eq!(cb.params[0].ty, TypeRef::StringUtf8);
1416 assert_eq!(cb.doc.as_deref(), Some("Fired when data arrives"));
1417 }
1418
1419 #[test]
1420 fn listener_def_round_trip_yaml() {
1421 let yaml = r#"
1422version: "0.4.0"
1423modules:
1424 - name: events
1425 functions: []
1426 callbacks:
1427 - name: on_data
1428 params: []
1429 listeners:
1430 - name: data_stream
1431 event_callback: on_data
1432 doc: "Subscribe to data events"
1433"#;
1434 let api: Api = serde_yaml::from_str(yaml).unwrap();
1435 let m = &api.modules[0];
1436 assert_eq!(m.listeners.len(), 1);
1437 let l = &m.listeners[0];
1438 assert_eq!(l.name, "data_stream");
1439 assert_eq!(l.event_callback, "on_data");
1440 assert_eq!(l.doc.as_deref(), Some("Subscribe to data events"));
1441 }
1442
1443 #[test]
1444 fn callbacks_and_listeners_default_to_empty() {
1445 let yaml = r#"
1446version: "0.4.0"
1447modules:
1448 - name: math
1449 functions: []
1450"#;
1451 let api: Api = serde_yaml::from_str(yaml).unwrap();
1452 assert!(api.modules[0].callbacks.is_empty());
1453 assert!(api.modules[0].listeners.is_empty());
1454 }
1455
1456 #[test]
1457 fn callback_def_json_round_trip() {
1458 let cb = CallbackDef {
1459 name: "on_event".to_string(),
1460 params: vec![Param {
1461 name: "data".to_string(),
1462 ty: TypeRef::I32,
1463 mutable: false,
1464 doc: None,
1465 }],
1466 doc: Some("event callback".to_string()),
1467 };
1468 let json = serde_json::to_string(&cb).unwrap();
1469 let back: CallbackDef = serde_json::from_str(&json).unwrap();
1470 assert_eq!(back, cb);
1471 }
1472
1473 #[test]
1474 fn listener_def_json_round_trip() {
1475 let l = ListenerDef {
1476 name: "watcher".to_string(),
1477 event_callback: "on_change".to_string(),
1478 doc: None,
1479 };
1480 let json = serde_json::to_string(&l).unwrap();
1481 let back: ListenerDef = serde_json::from_str(&json).unwrap();
1482 assert_eq!(back, l);
1483 }
1484
1485 #[test]
1486 fn builder_defaults_to_false() {
1487 let yaml = r#"
1488version: "0.4.0"
1489modules:
1490 - name: contacts
1491 functions: []
1492 structs:
1493 - name: Contact
1494 fields:
1495 - name: name
1496 type: string
1497"#;
1498 let api: Api = serde_yaml::from_str(yaml).unwrap();
1499 assert!(!api.modules[0].structs[0].builder);
1500 }
1501
1502 #[test]
1503 fn builder_true_round_trip() {
1504 let yaml = r#"
1505version: "0.4.0"
1506modules:
1507 - name: contacts
1508 functions: []
1509 structs:
1510 - name: Contact
1511 fields:
1512 - name: name
1513 type: string
1514 builder: true
1515"#;
1516 let api: Api = serde_yaml::from_str(yaml).unwrap();
1517 assert!(api.modules[0].structs[0].builder);
1518
1519 let json = serde_json::to_string(&api).unwrap();
1520 let back: Api = serde_json::from_str(&json).unwrap();
1521 assert!(back.modules[0].structs[0].builder);
1522 }
1523
1524 #[test]
1525 fn builder_false_explicit() {
1526 let json = r#"{
1527 "version": "0.4.0",
1528 "modules": [{
1529 "name": "geo",
1530 "functions": [],
1531 "structs": [{
1532 "name": "Point",
1533 "fields": [{"name": "x", "type": "f64"}],
1534 "builder": false
1535 }]
1536 }]
1537 }"#;
1538 let api: Api = serde_json::from_str(json).unwrap();
1539 assert!(!api.modules[0].structs[0].builder);
1540 }
1541
1542 #[test]
1543 fn param_mutable_defaults_to_false() {
1544 let yaml = r#"
1545version: "0.4.0"
1546modules:
1547 - name: io
1548 functions:
1549 - name: write
1550 params:
1551 - name: data
1552 type: string
1553"#;
1554 let api: Api = serde_yaml::from_str(yaml).unwrap();
1555 assert!(!api.modules[0].functions[0].params[0].mutable);
1556 }
1557
1558 #[test]
1559 fn param_mutable_true_round_trip() {
1560 let yaml = r#"
1561version: "0.4.0"
1562modules:
1563 - name: io
1564 functions:
1565 - name: fill_buffer
1566 params:
1567 - name: buf
1568 type: bytes
1569 mutable: true
1570"#;
1571 let api: Api = serde_yaml::from_str(yaml).unwrap();
1572 assert!(api.modules[0].functions[0].params[0].mutable);
1573
1574 let json = serde_json::to_string(&api).unwrap();
1575 let back: Api = serde_json::from_str(&json).unwrap();
1576 assert!(back.modules[0].functions[0].params[0].mutable);
1577 }
1578
1579 #[test]
1580 fn param_mutable_false_explicit() {
1581 let json = r#"{
1582 "version": "0.4.0",
1583 "modules": [{
1584 "name": "io",
1585 "functions": [{
1586 "name": "read",
1587 "params": [{"name": "buf", "type": "bytes", "mutable": false}]
1588 }]
1589 }]
1590 }"#;
1591 let api: Api = serde_json::from_str(json).unwrap();
1592 assert!(!api.modules[0].functions[0].params[0].mutable);
1593 }
1594
1595 #[test]
1596 fn deprecated_and_since_default_to_none() {
1597 let yaml = r#"
1598version: "0.4.0"
1599modules:
1600 - name: math
1601 functions:
1602 - name: add
1603 params: []
1604"#;
1605 let api: Api = serde_yaml::from_str(yaml).unwrap();
1606 let f = &api.modules[0].functions[0];
1607 assert_eq!(f.deprecated, None);
1608 assert_eq!(f.since, None);
1609 }
1610
1611 #[test]
1612 fn deprecated_and_since_round_trip() {
1613 let yaml = r#"
1614version: "0.4.0"
1615modules:
1616 - name: math
1617 functions:
1618 - name: add_old
1619 params: []
1620 deprecated: "Use add_v2 instead"
1621 since: "0.1.0"
1622"#;
1623 let api: Api = serde_yaml::from_str(yaml).unwrap();
1624 let f = &api.modules[0].functions[0];
1625 assert_eq!(f.deprecated.as_deref(), Some("Use add_v2 instead"));
1626 assert_eq!(f.since.as_deref(), Some("0.1.0"));
1627
1628 let json = serde_json::to_string(&api).unwrap();
1629 let back: Api = serde_json::from_str(&json).unwrap();
1630 let f2 = &back.modules[0].functions[0];
1631 assert_eq!(f2.deprecated.as_deref(), Some("Use add_v2 instead"));
1632 assert_eq!(f2.since.as_deref(), Some("0.1.0"));
1633 }
1634
1635 #[test]
1636 fn struct_field_default_value_round_trip() {
1637 let yaml = r#"
1638version: "0.4.0"
1639modules:
1640 - name: contacts
1641 functions: []
1642 structs:
1643 - name: Contact
1644 fields:
1645 - name: name
1646 type: string
1647 - name: age
1648 type: i32
1649 default: 0
1650"#;
1651 let api: Api = serde_yaml::from_str(yaml).unwrap();
1652 let fields = &api.modules[0].structs[0].fields;
1653 assert!(fields[0].default.is_none());
1654 assert_eq!(
1655 fields[1].default,
1656 Some(serde_yaml::Value::Number(serde_yaml::Number::from(0)))
1657 );
1658 }
1659
1660 #[test]
1661 fn serialization_omits_defaulted_fields() {
1662 let api = Api {
1666 version: "0.4.0".into(),
1667 modules: vec![Module {
1668 name: "calc".into(),
1669 functions: vec![Function {
1670 name: "add".into(),
1671 params: vec![Param {
1672 name: "a".into(),
1673 ty: TypeRef::I32,
1674 mutable: false,
1675 doc: None,
1676 }],
1677 returns: Some(TypeRef::I32),
1678 doc: None,
1679 r#async: false,
1680 cancellable: false,
1681 deprecated: None,
1682 since: None,
1683 }],
1684 structs: vec![],
1685 enums: vec![],
1686 callbacks: vec![],
1687 listeners: vec![],
1688 errors: None,
1689 modules: vec![],
1690 }],
1691 generators: None,
1692 package: None,
1693 };
1694 let yaml = serde_yaml::to_string(&api).unwrap();
1695 for needle in [
1696 "generators",
1697 "structs",
1698 "enums",
1699 "callbacks",
1700 "listeners",
1701 "errors",
1702 "modules:\n", "doc",
1704 "async",
1705 "cancellable",
1706 "deprecated",
1707 "since",
1708 "mutable",
1709 "null",
1710 "[]",
1711 "false",
1712 ] {
1713 if needle == "modules:\n" {
1717 continue;
1718 }
1719 assert!(
1720 !yaml.contains(needle),
1721 "default field `{needle}` leaked into canonical YAML:\n{yaml}"
1722 );
1723 }
1724 let back: Api = serde_yaml::from_str(&yaml).unwrap();
1726 assert_eq!(back, api);
1727 }
1728
1729 #[test]
1730 fn parse_type_ref_does_not_yield_callback() {
1731 assert_eq!(
1732 parse_type_ref("callback"),
1733 Ok(TypeRef::Struct("callback".into()))
1734 );
1735 }
1736
1737 #[test]
1738 fn api_json_schema_derives() {
1739 let schema = schemars::schema_for!(Api);
1740 let json = serde_json::to_value(&schema).unwrap();
1741 assert!(json.get("$schema").is_some());
1742 assert!(json.get("properties").is_some());
1743 assert_eq!(json.get("title").and_then(|v| v.as_str()), Some("Api"));
1744 let defs = json
1745 .get("definitions")
1746 .and_then(|v| v.as_object())
1747 .expect("definitions");
1748 assert!(defs.contains_key("Module"));
1749 assert!(defs.contains_key("Function"));
1750 assert!(defs.contains_key("Param"));
1751 assert!(defs.contains_key("TypeRef"));
1752 assert!(defs.contains_key("StructDef"));
1753 assert!(defs.contains_key("StructField"));
1754 assert!(defs.contains_key("EnumDef"));
1755 assert!(defs.contains_key("EnumVariant"));
1756 assert!(defs.contains_key("CallbackDef"));
1757 assert!(defs.contains_key("ListenerDef"));
1758 assert!(defs.contains_key("ErrorDomain"));
1759 assert!(defs.contains_key("ErrorCode"));
1760 }
1761
1762 #[test]
1763 fn typeref_json_schema_is_string_with_description() {
1764 let schema = schemars::schema_for!(TypeRef);
1765 let json = serde_json::to_value(&schema).unwrap();
1766 assert_eq!(json.get("type").and_then(|v| v.as_str()), Some("string"));
1767 assert!(json
1768 .get("description")
1769 .and_then(|v| v.as_str())
1770 .is_some_and(|s| s.contains("handle<") && s.contains("iter<")));
1771 }
1772}