Skip to main content

weaveffi_ir/
ir.rs

1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// The current IR schema version that the parser, validator, and every
7/// generator expect.
8///
9/// Schema bumps are tied to this crate's minor version: each minor release
10/// of `weaveffi-ir` may introduce at most one new schema version, and
11/// [`SUPPORTED_VERSIONS`] always lists every version the upgrader can
12/// read. The CLI's `weaveffi upgrade` subcommand guarantees an N-1 → N
13/// migration path between consecutive versions.
14///
15/// See [`docs/src/stability.md`](https://github.com/weavefoundry/weaveffi/blob/main/docs/src/stability.md)
16/// for the full schema-migration policy and the surfaces covered by
17/// SemVer.
18pub const CURRENT_SCHEMA_VERSION: &str = "0.3.0";
19
20pub const SUPPORTED_VERSIONS: &[&str] = &["0.1.0", "0.2.0", "0.3.0"];
21
22/// `Eq` is omitted because `toml::Value` contains `f64`.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
24#[schemars(description = "Top-level WeaveFFI API definition.")]
25pub struct Api {
26    pub version: String,
27    pub modules: Vec<Module>,
28    #[serde(default)]
29    #[schemars(with = "Option<BTreeMap<String, serde_json::Value>>")]
30    pub generators: Option<BTreeMap<String, toml::Value>>,
31}
32
33/// `Eq` is omitted because `StructField::default` contains `serde_yaml::Value`.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
35#[schemars(
36    description = "A WeaveFFI module: a named group of functions, types, callbacks, listeners, and errors."
37)]
38pub struct Module {
39    pub name: String,
40    pub functions: Vec<Function>,
41    #[serde(default)]
42    pub structs: Vec<StructDef>,
43    #[serde(default)]
44    pub enums: Vec<EnumDef>,
45    #[serde(default)]
46    pub callbacks: Vec<CallbackDef>,
47    #[serde(default)]
48    pub listeners: Vec<ListenerDef>,
49    #[serde(default)]
50    pub errors: Option<ErrorDomain>,
51    #[serde(default)]
52    pub modules: Vec<Module>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
56pub struct Function {
57    pub name: String,
58    pub params: Vec<Param>,
59    #[serde(rename = "return", default)]
60    pub returns: Option<TypeRef>,
61    #[serde(default)]
62    pub doc: Option<String>,
63    #[serde(default, rename = "async")]
64    pub r#async: bool,
65    #[serde(default)]
66    pub cancellable: bool,
67    #[serde(default)]
68    pub deprecated: Option<String>,
69    #[serde(default)]
70    pub since: Option<String>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
74pub struct Param {
75    pub name: String,
76    #[serde(rename = "type")]
77    pub ty: TypeRef,
78    #[serde(default)]
79    pub mutable: bool,
80    #[serde(default)]
81    pub doc: Option<String>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
85pub struct CallbackDef {
86    pub name: String,
87    pub params: Vec<Param>,
88    #[serde(default)]
89    pub doc: Option<String>,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
93pub struct ListenerDef {
94    pub name: String,
95    pub event_callback: String,
96    #[serde(default)]
97    pub doc: Option<String>,
98}
99
100/// A reference to a type in the IDL.
101///
102/// Callback-style behavior is **not** expressed as a `TypeRef` variant.
103/// Instead, callbacks and listeners are declared at the module level via
104/// `Module.callbacks` (see [`CallbackDef`]) and `Module.listeners` (see
105/// [`ListenerDef`]), and asynchronous functions use `async: true`. These
106/// primitives cover every pattern the FFI boundary needs to support, and
107/// keep the type system free of function-typed values that the C ABI
108/// cannot represent uniformly.
109#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110pub enum TypeRef {
111    I32,
112    U32,
113    I64,
114    F64,
115    Bool,
116    StringUtf8,
117    Bytes,
118    Handle,
119    TypedHandle(String),
120    Struct(String),
121    Enum(String),
122    BorrowedStr,
123    BorrowedBytes,
124    Optional(Box<TypeRef>),
125    List(Box<TypeRef>),
126    Map(Box<TypeRef>, Box<TypeRef>),
127    Iterator(Box<TypeRef>),
128}
129
130pub fn parse_type_ref(s: &str) -> Result<TypeRef, String> {
131    let s = s.trim();
132    if s.is_empty() {
133        return Err("empty type reference".to_string());
134    }
135    if s.starts_with('[') && s.ends_with(']') {
136        let inner = &s[1..s.len() - 1];
137        return parse_type_ref(inner).map(|t| TypeRef::List(Box::new(t)));
138    }
139    if s.starts_with('{') && s.ends_with('}') {
140        let inner = &s[1..s.len() - 1];
141        let colon = inner
142            .find(':')
143            .ok_or_else(|| "map type missing ':' separator".to_string())?;
144        let key = parse_type_ref(&inner[..colon])?;
145        let val = parse_type_ref(&inner[colon + 1..])?;
146        return Ok(TypeRef::Map(Box::new(key), Box::new(val)));
147    }
148    if let Some(inner) = s.strip_suffix('?') {
149        return parse_type_ref(inner).map(|t| TypeRef::Optional(Box::new(t)));
150    }
151    if let Some(inner) = s
152        .strip_prefix("handle<")
153        .and_then(|rest| rest.strip_suffix('>'))
154    {
155        return Ok(TypeRef::TypedHandle(inner.into()));
156    }
157    if let Some(inner) = s
158        .strip_prefix("iter<")
159        .and_then(|rest| rest.strip_suffix('>'))
160    {
161        return parse_type_ref(inner).map(|t| TypeRef::Iterator(Box::new(t)));
162    }
163    match s {
164        "i32" => Ok(TypeRef::I32),
165        "u32" => Ok(TypeRef::U32),
166        "i64" => Ok(TypeRef::I64),
167        "f64" => Ok(TypeRef::F64),
168        "bool" => Ok(TypeRef::Bool),
169        "string" => Ok(TypeRef::StringUtf8),
170        "bytes" => Ok(TypeRef::Bytes),
171        "handle" => Ok(TypeRef::Handle),
172        "&str" => Ok(TypeRef::BorrowedStr),
173        "&[u8]" => Ok(TypeRef::BorrowedBytes),
174        name => Ok(TypeRef::Struct(name.to_string())),
175    }
176}
177
178fn type_ref_to_string(ty: &TypeRef) -> String {
179    match ty {
180        TypeRef::I32 => "i32".to_string(),
181        TypeRef::U32 => "u32".to_string(),
182        TypeRef::I64 => "i64".to_string(),
183        TypeRef::F64 => "f64".to_string(),
184        TypeRef::Bool => "bool".to_string(),
185        TypeRef::StringUtf8 => "string".to_string(),
186        TypeRef::Bytes => "bytes".to_string(),
187        TypeRef::BorrowedStr => "&str".to_string(),
188        TypeRef::BorrowedBytes => "&[u8]".to_string(),
189        TypeRef::Handle => "handle".to_string(),
190        TypeRef::TypedHandle(name) => format!("handle<{name}>"),
191        TypeRef::Struct(name) | TypeRef::Enum(name) => name.clone(),
192        TypeRef::Optional(inner) => format!("{}?", type_ref_to_string(inner)),
193        TypeRef::List(inner) => format!("[{}]", type_ref_to_string(inner)),
194        TypeRef::Map(k, v) => format!("{{{}:{}}}", type_ref_to_string(k), type_ref_to_string(v)),
195        TypeRef::Iterator(inner) => format!("iter<{}>", type_ref_to_string(inner)),
196    }
197}
198
199impl Serialize for TypeRef {
200    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
201    where
202        S: serde::Serializer,
203    {
204        serializer.serialize_str(&type_ref_to_string(self))
205    }
206}
207
208impl<'de> Deserialize<'de> for TypeRef {
209    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210    where
211        D: serde::Deserializer<'de>,
212    {
213        let s = String::deserialize(deserializer)?;
214        parse_type_ref(&s).map_err(serde::de::Error::custom)
215    }
216}
217
218/// Manual `JsonSchema` impl because `TypeRef` (de)serializes as a string with
219/// custom syntax: primitive names (`i32`, `string`, ...), `&str`, `&[u8]`,
220/// `handle<{name}>`, `iter<{T}>`, `[{T}]`, `{ {K}: {V} }`, `{name}?`, or any
221/// user-defined struct/enum name.
222impl JsonSchema for TypeRef {
223    fn schema_name() -> String {
224        "TypeRef".to_string()
225    }
226
227    fn schema_id() -> std::borrow::Cow<'static, str> {
228        std::borrow::Cow::Borrowed(concat!(module_path!(), "::TypeRef"))
229    }
230
231    fn json_schema(_generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
232        let mut schema = schemars::schema::SchemaObject {
233            instance_type: Some(schemars::schema::InstanceType::String.into()),
234            ..Default::default()
235        };
236        let meta = schema.metadata();
237        meta.title = Some("TypeRef".to_string());
238        meta.description = Some(
239            "Reference to a type. Encoded as a string with custom syntax: \
240             primitives (`i32`, `u32`, `i64`, `f64`, `bool`, `string`, `bytes`, `handle`), \
241             borrowed types (`&str`, `&[u8]`), typed handles (`handle<{name}>`), \
242             iterators (`iter<{T}>`), lists (`[{T}]`), maps (`{{K:V}}`), \
243             optionals (`{T}?`), or any user-defined struct/enum name."
244                .to_string(),
245        );
246        schema.into()
247    }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
251pub struct EnumDef {
252    pub name: String,
253    #[serde(default)]
254    pub doc: Option<String>,
255    pub variants: Vec<EnumVariant>,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
259pub struct EnumVariant {
260    pub name: String,
261    pub value: i32,
262    #[serde(default)]
263    pub doc: Option<String>,
264}
265
266/// `Eq` is omitted because `StructField::default` contains `serde_yaml::Value`.
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
268#[schemars(description = "A struct (record) type with named fields.")]
269pub struct StructDef {
270    pub name: String,
271    #[serde(default)]
272    pub doc: Option<String>,
273    pub fields: Vec<StructField>,
274    #[serde(default)]
275    pub builder: bool,
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
279pub struct StructField {
280    pub name: String,
281    #[serde(rename = "type")]
282    pub ty: TypeRef,
283    #[serde(default)]
284    pub doc: Option<String>,
285    #[serde(default)]
286    #[schemars(with = "Option<serde_json::Value>")]
287    pub default: Option<serde_yaml::Value>,
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
291pub struct ErrorDomain {
292    pub name: String,
293    pub codes: Vec<ErrorCode>,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
297pub struct ErrorCode {
298    pub name: String,
299    pub code: i32,
300    pub message: String,
301    #[serde(default)]
302    pub doc: Option<String>,
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn struct_def_round_trip_yaml() {
311        let yaml = r#"
312version: "0.1.0"
313modules:
314  - name: geometry
315    functions: []
316    structs:
317      - name: Point
318        doc: "A 2D point"
319        fields:
320          - name: x
321            type: f64
322          - name: "y"
323            type: f64
324            doc: "Y coordinate"
325"#;
326        let api: Api = serde_yaml::from_str(yaml).unwrap();
327        let m = &api.modules[0];
328        assert_eq!(m.structs.len(), 1);
329        let s = &m.structs[0];
330        assert_eq!(s.name, "Point");
331        assert_eq!(s.doc.as_deref(), Some("A 2D point"));
332        assert_eq!(s.fields.len(), 2);
333        assert_eq!(s.fields[0].name, "x");
334        assert_eq!(s.fields[0].ty, TypeRef::F64);
335        assert_eq!(s.fields[0].doc, None);
336        assert_eq!(s.fields[1].name, "y");
337        assert_eq!(s.fields[1].doc.as_deref(), Some("Y coordinate"));
338    }
339
340    #[test]
341    fn struct_def_round_trip_json() {
342        let json = r#"{
343            "version": "0.1.0",
344            "modules": [{
345                "name": "geo",
346                "functions": [],
347                "structs": [{
348                    "name": "Rect",
349                    "fields": [
350                        {"name": "width", "type": "i32"},
351                        {"name": "height", "type": "i32"}
352                    ]
353                }]
354            }]
355        }"#;
356        let api: Api = serde_json::from_str(json).unwrap();
357        let s = &api.modules[0].structs[0];
358        assert_eq!(s.name, "Rect");
359        assert_eq!(s.doc, None);
360        assert_eq!(s.fields[0].ty, TypeRef::I32);
361    }
362
363    #[test]
364    fn structs_default_to_empty() {
365        let yaml = r#"
366version: "0.1.0"
367modules:
368  - name: math
369    functions: []
370"#;
371        let api: Api = serde_yaml::from_str(yaml).unwrap();
372        assert!(api.modules[0].structs.is_empty());
373    }
374
375    #[test]
376    fn typeref_struct_variant_serializes() {
377        let ty = TypeRef::Struct("Point".to_string());
378        let json = serde_json::to_string(&ty).unwrap();
379        assert_eq!(json, r#""Point""#);
380        let back: TypeRef = serde_json::from_str(&json).unwrap();
381        assert_eq!(back, ty);
382    }
383
384    #[test]
385    fn struct_field_with_struct_type() {
386        let field = StructField {
387            name: "origin".to_string(),
388            ty: TypeRef::Struct("Point".to_string()),
389            doc: None,
390            default: None,
391        };
392        let json = serde_json::to_string(&field).unwrap();
393        let back: StructField = serde_json::from_str(&json).unwrap();
394        assert_eq!(back, field);
395    }
396
397    #[test]
398    fn typeref_is_not_copy() {
399        let a = TypeRef::Struct("Foo".to_string());
400        let b = a.clone();
401        assert_eq!(a, b);
402    }
403
404    #[test]
405    fn enum_def_round_trip_yaml() {
406        let yaml = r#"
407version: "0.1.0"
408modules:
409  - name: graphics
410    functions: []
411    enums:
412      - name: Color
413        doc: "Primary colors"
414        variants:
415          - name: Red
416            value: 0
417          - name: Green
418            value: 1
419            doc: "The color green"
420          - name: Blue
421            value: 2
422"#;
423        let api: Api = serde_yaml::from_str(yaml).unwrap();
424        let m = &api.modules[0];
425        assert_eq!(m.enums.len(), 1);
426        let e = &m.enums[0];
427        assert_eq!(e.name, "Color");
428        assert_eq!(e.doc.as_deref(), Some("Primary colors"));
429        assert_eq!(e.variants.len(), 3);
430        assert_eq!(e.variants[0].name, "Red");
431        assert_eq!(e.variants[0].value, 0);
432        assert_eq!(e.variants[0].doc, None);
433        assert_eq!(e.variants[1].name, "Green");
434        assert_eq!(e.variants[1].value, 1);
435        assert_eq!(e.variants[1].doc.as_deref(), Some("The color green"));
436        assert_eq!(e.variants[2].name, "Blue");
437        assert_eq!(e.variants[2].value, 2);
438    }
439
440    #[test]
441    fn enum_def_round_trip_json() {
442        let json = r#"{
443            "version": "0.1.0",
444            "modules": [{
445                "name": "status",
446                "functions": [],
447                "enums": [{
448                    "name": "Status",
449                    "variants": [
450                        {"name": "Ok", "value": 0},
451                        {"name": "Error", "value": 1}
452                    ]
453                }]
454            }]
455        }"#;
456        let api: Api = serde_json::from_str(json).unwrap();
457        let e = &api.modules[0].enums[0];
458        assert_eq!(e.name, "Status");
459        assert_eq!(e.doc, None);
460        assert_eq!(e.variants.len(), 2);
461        assert_eq!(e.variants[1].value, 1);
462    }
463
464    #[test]
465    fn enums_default_to_empty() {
466        let yaml = r#"
467version: "0.1.0"
468modules:
469  - name: math
470    functions: []
471"#;
472        let api: Api = serde_yaml::from_str(yaml).unwrap();
473        assert!(api.modules[0].enums.is_empty());
474    }
475
476    #[test]
477    fn typeref_enum_variant_serializes_as_name() {
478        let ty = TypeRef::Enum("Color".to_string());
479        let json = serde_json::to_string(&ty).unwrap();
480        assert_eq!(json, r#""Color""#);
481    }
482
483    #[test]
484    fn enum_def_clone_and_eq() {
485        let e = EnumDef {
486            name: "Direction".to_string(),
487            doc: Some("Cardinal directions".to_string()),
488            variants: vec![
489                EnumVariant {
490                    name: "North".to_string(),
491                    value: 0,
492                    doc: None,
493                },
494                EnumVariant {
495                    name: "South".to_string(),
496                    value: 1,
497                    doc: None,
498                },
499            ],
500        };
501        assert_eq!(e, e.clone());
502    }
503
504    #[test]
505    fn struct_def_clone_and_eq() {
506        let s = StructDef {
507            name: "Color".to_string(),
508            doc: Some("RGB color".to_string()),
509            fields: vec![
510                StructField {
511                    name: "r".to_string(),
512                    ty: TypeRef::U32,
513                    doc: None,
514                    default: None,
515                },
516                StructField {
517                    name: "g".to_string(),
518                    ty: TypeRef::U32,
519                    doc: None,
520                    default: None,
521                },
522                StructField {
523                    name: "b".to_string(),
524                    ty: TypeRef::U32,
525                    doc: None,
526                    default: None,
527                },
528            ],
529            builder: false,
530        };
531        assert_eq!(s, s.clone());
532    }
533
534    #[test]
535    fn parse_type_ref_primitives() {
536        assert_eq!(parse_type_ref("i32"), Ok(TypeRef::I32));
537        assert_eq!(parse_type_ref("u32"), Ok(TypeRef::U32));
538        assert_eq!(parse_type_ref("i64"), Ok(TypeRef::I64));
539        assert_eq!(parse_type_ref("f64"), Ok(TypeRef::F64));
540        assert_eq!(parse_type_ref("bool"), Ok(TypeRef::Bool));
541        assert_eq!(parse_type_ref("string"), Ok(TypeRef::StringUtf8));
542        assert_eq!(parse_type_ref("bytes"), Ok(TypeRef::Bytes));
543        assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
544    }
545
546    #[test]
547    fn parse_type_ref_struct() {
548        assert_eq!(
549            parse_type_ref("Contact"),
550            Ok(TypeRef::Struct("Contact".into()))
551        );
552        assert_eq!(
553            parse_type_ref("MyWidget"),
554            Ok(TypeRef::Struct("MyWidget".into()))
555        );
556    }
557
558    #[test]
559    fn parse_type_ref_optional() {
560        assert_eq!(
561            parse_type_ref("string?"),
562            Ok(TypeRef::Optional(Box::new(TypeRef::StringUtf8)))
563        );
564        assert_eq!(
565            parse_type_ref("i32?"),
566            Ok(TypeRef::Optional(Box::new(TypeRef::I32)))
567        );
568        assert_eq!(
569            parse_type_ref("Contact?"),
570            Ok(TypeRef::Optional(Box::new(TypeRef::Struct(
571                "Contact".into()
572            ))))
573        );
574    }
575
576    #[test]
577    fn parse_type_ref_list() {
578        assert_eq!(
579            parse_type_ref("[i32]"),
580            Ok(TypeRef::List(Box::new(TypeRef::I32)))
581        );
582        assert_eq!(
583            parse_type_ref("[string]"),
584            Ok(TypeRef::List(Box::new(TypeRef::StringUtf8)))
585        );
586        assert_eq!(
587            parse_type_ref("[Contact]"),
588            Ok(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
589        );
590    }
591
592    #[test]
593    fn parse_type_ref_nested() {
594        assert_eq!(
595            parse_type_ref("[i32?]"),
596            Ok(TypeRef::List(Box::new(TypeRef::Optional(Box::new(
597                TypeRef::I32
598            )))))
599        );
600        assert_eq!(
601            parse_type_ref("[Contact]?"),
602            Ok(TypeRef::Optional(Box::new(TypeRef::List(Box::new(
603                TypeRef::Struct("Contact".into())
604            )))))
605        );
606    }
607
608    #[test]
609    fn parse_type_ref_empty_is_error() {
610        assert!(parse_type_ref("").is_err());
611        assert!(parse_type_ref("  ").is_err());
612    }
613
614    #[test]
615    fn typeref_primitive_round_trips() {
616        for ty in [
617            TypeRef::I32,
618            TypeRef::U32,
619            TypeRef::I64,
620            TypeRef::F64,
621            TypeRef::Bool,
622            TypeRef::StringUtf8,
623            TypeRef::Bytes,
624            TypeRef::Handle,
625        ] {
626            let json = serde_json::to_string(&ty).unwrap();
627            let back: TypeRef = serde_json::from_str(&json).unwrap();
628            assert_eq!(back, ty);
629        }
630    }
631
632    #[test]
633    fn typeref_optional_round_trip() {
634        let ty = TypeRef::Optional(Box::new(TypeRef::StringUtf8));
635        let json = serde_json::to_string(&ty).unwrap();
636        assert_eq!(json, r#""string?""#);
637        let back: TypeRef = serde_json::from_str(&json).unwrap();
638        assert_eq!(back, ty);
639    }
640
641    #[test]
642    fn typeref_list_round_trip() {
643        let ty = TypeRef::List(Box::new(TypeRef::I32));
644        let json = serde_json::to_string(&ty).unwrap();
645        assert_eq!(json, r#""[i32]""#);
646        let back: TypeRef = serde_json::from_str(&json).unwrap();
647        assert_eq!(back, ty);
648    }
649
650    #[test]
651    fn typeref_optional_struct_round_trip() {
652        let ty = TypeRef::Optional(Box::new(TypeRef::Struct("Contact".into())));
653        let json = serde_json::to_string(&ty).unwrap();
654        assert_eq!(json, r#""Contact?""#);
655        let back: TypeRef = serde_json::from_str(&json).unwrap();
656        assert_eq!(back, ty);
657    }
658
659    #[test]
660    fn typeref_list_struct_round_trip() {
661        let ty = TypeRef::List(Box::new(TypeRef::Struct("Contact".into())));
662        let json = serde_json::to_string(&ty).unwrap();
663        assert_eq!(json, r#""[Contact]""#);
664        let back: TypeRef = serde_json::from_str(&json).unwrap();
665        assert_eq!(back, ty);
666    }
667
668    #[test]
669    fn typeref_optional_yaml_deser() {
670        let yaml = r#"
671version: "0.1.0"
672modules:
673  - name: contacts
674    functions:
675      - name: find
676        params:
677          - name: id
678            type: i32
679        return: "Contact?"
680"#;
681        let api: Api = serde_yaml::from_str(yaml).unwrap();
682        let f = &api.modules[0].functions[0];
683        assert_eq!(
684            f.returns,
685            Some(TypeRef::Optional(Box::new(TypeRef::Struct(
686                "Contact".into()
687            ))))
688        );
689    }
690
691    #[test]
692    fn typeref_list_yaml_deser() {
693        let yaml = r#"
694version: "0.1.0"
695modules:
696  - name: contacts
697    functions:
698      - name: list_all
699        params: []
700        return: "[Contact]"
701"#;
702        let api: Api = serde_yaml::from_str(yaml).unwrap();
703        let f = &api.modules[0].functions[0];
704        assert_eq!(
705            f.returns,
706            Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into()))))
707        );
708    }
709
710    #[test]
711    fn typeref_hash_works_with_box_variants() {
712        use std::collections::HashSet;
713        let mut set = HashSet::new();
714        set.insert(TypeRef::I32);
715        set.insert(TypeRef::Optional(Box::new(TypeRef::I32)));
716        set.insert(TypeRef::List(Box::new(TypeRef::I32)));
717        set.insert(TypeRef::Optional(Box::new(TypeRef::Struct("Foo".into()))));
718        set.insert(TypeRef::Map(
719            Box::new(TypeRef::StringUtf8),
720            Box::new(TypeRef::I32),
721        ));
722        assert_eq!(set.len(), 5);
723    }
724
725    #[test]
726    fn parse_type_ref_map_primitives() {
727        assert_eq!(
728            parse_type_ref("{string:i32}"),
729            Ok(TypeRef::Map(
730                Box::new(TypeRef::StringUtf8),
731                Box::new(TypeRef::I32)
732            ))
733        );
734    }
735
736    #[test]
737    fn parse_type_ref_map_struct_value() {
738        assert_eq!(
739            parse_type_ref("{string:Contact}"),
740            Ok(TypeRef::Map(
741                Box::new(TypeRef::StringUtf8),
742                Box::new(TypeRef::Struct("Contact".into()))
743            ))
744        );
745    }
746
747    #[test]
748    fn parse_type_ref_map_nested_value() {
749        assert_eq!(
750            parse_type_ref("{string:[i32]}"),
751            Ok(TypeRef::Map(
752                Box::new(TypeRef::StringUtf8),
753                Box::new(TypeRef::List(Box::new(TypeRef::I32)))
754            ))
755        );
756    }
757
758    #[test]
759    fn parse_type_ref_map_missing_colon() {
760        assert!(parse_type_ref("{string}").is_err());
761    }
762
763    #[test]
764    fn typeref_map_round_trip() {
765        let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
766        let json = serde_json::to_string(&ty).unwrap();
767        assert_eq!(json, r#""{string:i32}""#);
768        let back: TypeRef = serde_json::from_str(&json).unwrap();
769        assert_eq!(back, ty);
770    }
771
772    #[test]
773    fn typeref_map_struct_round_trip() {
774        let ty = TypeRef::Map(
775            Box::new(TypeRef::StringUtf8),
776            Box::new(TypeRef::Struct("Contact".into())),
777        );
778        let json = serde_json::to_string(&ty).unwrap();
779        assert_eq!(json, r#""{string:Contact}""#);
780        let back: TypeRef = serde_json::from_str(&json).unwrap();
781        assert_eq!(back, ty);
782    }
783
784    #[test]
785    fn typeref_map_yaml_deser() {
786        let yaml = r#"
787version: "0.1.0"
788modules:
789  - name: contacts
790    functions:
791      - name: get_metadata
792        params: []
793        return: "{string:i32}"
794"#;
795        let api: Api = serde_yaml::from_str(yaml).unwrap();
796        let f = &api.modules[0].functions[0];
797        assert_eq!(
798            f.returns,
799            Some(TypeRef::Map(
800                Box::new(TypeRef::StringUtf8),
801                Box::new(TypeRef::I32)
802            ))
803        );
804    }
805
806    #[test]
807    fn typeref_optional_map_round_trip() {
808        let ty = TypeRef::Optional(Box::new(TypeRef::Map(
809            Box::new(TypeRef::StringUtf8),
810            Box::new(TypeRef::I32),
811        )));
812        let json = serde_json::to_string(&ty).unwrap();
813        assert_eq!(json, r#""{string:i32}?""#);
814        let back: TypeRef = serde_json::from_str(&json).unwrap();
815        assert_eq!(back, ty);
816    }
817
818    #[test]
819    fn parse_map_string_to_i32() {
820        assert_eq!(
821            parse_type_ref("{string:i32}"),
822            Ok(TypeRef::Map(
823                Box::new(TypeRef::StringUtf8),
824                Box::new(TypeRef::I32),
825            ))
826        );
827    }
828
829    #[test]
830    fn parse_map_string_to_struct() {
831        assert_eq!(
832            parse_type_ref("{string:Contact}"),
833            Ok(TypeRef::Map(
834                Box::new(TypeRef::StringUtf8),
835                Box::new(TypeRef::Struct("Contact".into())),
836            ))
837        );
838    }
839
840    #[test]
841    fn parse_map_roundtrip() {
842        let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
843        let json = serde_json::to_string(&ty).unwrap();
844        let back: TypeRef = serde_json::from_str(&json).unwrap();
845        assert_eq!(back, ty);
846    }
847
848    #[test]
849    fn parse_optional_map() {
850        assert_eq!(
851            parse_type_ref("{string:i32}?"),
852            Ok(TypeRef::Optional(Box::new(TypeRef::Map(
853                Box::new(TypeRef::StringUtf8),
854                Box::new(TypeRef::I32),
855            ))))
856        );
857    }
858
859    #[test]
860    fn parse_map_of_lists() {
861        assert_eq!(
862            parse_type_ref("{string:[i32]}"),
863            Ok(TypeRef::Map(
864                Box::new(TypeRef::StringUtf8),
865                Box::new(TypeRef::List(Box::new(TypeRef::I32))),
866            ))
867        );
868    }
869
870    #[test]
871    fn parse_type_ref_iterator() {
872        assert_eq!(
873            parse_type_ref("iter<i32>"),
874            Ok(TypeRef::Iterator(Box::new(TypeRef::I32)))
875        );
876        assert_eq!(
877            parse_type_ref("iter<string>"),
878            Ok(TypeRef::Iterator(Box::new(TypeRef::StringUtf8)))
879        );
880        assert_eq!(
881            parse_type_ref("iter<Contact>"),
882            Ok(TypeRef::Iterator(Box::new(TypeRef::Struct(
883                "Contact".into()
884            ))))
885        );
886    }
887
888    #[test]
889    fn typeref_iterator_round_trip() {
890        let ty = TypeRef::Iterator(Box::new(TypeRef::I32));
891        let json = serde_json::to_string(&ty).unwrap();
892        assert_eq!(json, r#""iter<i32>""#);
893        let back: TypeRef = serde_json::from_str(&json).unwrap();
894        assert_eq!(back, ty);
895    }
896
897    #[test]
898    fn typeref_iterator_struct_round_trip() {
899        let ty = TypeRef::Iterator(Box::new(TypeRef::Struct("Contact".into())));
900        let json = serde_json::to_string(&ty).unwrap();
901        assert_eq!(json, r#""iter<Contact>""#);
902        let back: TypeRef = serde_json::from_str(&json).unwrap();
903        assert_eq!(back, ty);
904    }
905
906    #[test]
907    fn parse_type_ref_borrowed() {
908        assert_eq!(parse_type_ref("&str"), Ok(TypeRef::BorrowedStr));
909        assert_eq!(parse_type_ref("&[u8]"), Ok(TypeRef::BorrowedBytes));
910    }
911
912    #[test]
913    fn typeref_borrowed_round_trip() {
914        for ty in [TypeRef::BorrowedStr, TypeRef::BorrowedBytes] {
915            let json = serde_json::to_string(&ty).unwrap();
916            let back: TypeRef = serde_json::from_str(&json).unwrap();
917            assert_eq!(back, ty);
918        }
919    }
920
921    #[test]
922    fn typeref_borrowed_str_serializes_as_ampersand_str() {
923        let json = serde_json::to_string(&TypeRef::BorrowedStr).unwrap();
924        assert_eq!(json, r#""&str""#);
925    }
926
927    #[test]
928    fn typeref_borrowed_bytes_serializes_as_ampersand_u8() {
929        let json = serde_json::to_string(&TypeRef::BorrowedBytes).unwrap();
930        assert_eq!(json, r#""&[u8]""#);
931    }
932
933    #[test]
934    fn typeref_borrowed_yaml_deser() {
935        let yaml = r#"
936version: "0.1.0"
937modules:
938  - name: io
939    functions:
940      - name: write
941        params:
942          - name: data
943            type: "&str"
944          - name: raw
945            type: "&[u8]"
946"#;
947        let api: Api = serde_yaml::from_str(yaml).unwrap();
948        let f = &api.modules[0].functions[0];
949        assert_eq!(f.params[0].ty, TypeRef::BorrowedStr);
950        assert_eq!(f.params[1].ty, TypeRef::BorrowedBytes);
951    }
952
953    #[test]
954    fn parse_typed_handle() {
955        assert_eq!(
956            parse_type_ref("handle<Session>"),
957            Ok(TypeRef::TypedHandle("Session".into()))
958        );
959        assert_eq!(parse_type_ref("handle"), Ok(TypeRef::Handle));
960    }
961
962    #[test]
963    fn generators_field_parses_from_yaml() {
964        let yaml = r#"
965version: "0.1.0"
966modules:
967  - name: math
968    functions: []
969generators:
970  swift:
971    module_name: MySwiftModule
972  android:
973    package: com.example.app
974"#;
975        let api: Api = serde_yaml::from_str(yaml).unwrap();
976        let generators = api.generators.as_ref().unwrap();
977        let swift = generators["swift"].as_table().unwrap();
978        assert_eq!(swift["module_name"].as_str(), Some("MySwiftModule"));
979        let android = generators["android"].as_table().unwrap();
980        assert_eq!(android["package"].as_str(), Some("com.example.app"));
981    }
982
983    #[test]
984    fn generators_defaults_to_none() {
985        let yaml = r#"
986version: "0.1.0"
987modules:
988  - name: math
989    functions: []
990"#;
991        let api: Api = serde_yaml::from_str(yaml).unwrap();
992        assert!(api.generators.is_none());
993    }
994
995    #[test]
996    fn parse_typed_handle_roundtrip() {
997        let ty = TypeRef::TypedHandle("Connection".into());
998        let json = serde_json::to_string(&ty).unwrap();
999        assert_eq!(json, r#""handle<Connection>""#);
1000        let back: TypeRef = serde_json::from_str(&json).unwrap();
1001        assert_eq!(back, ty);
1002    }
1003
1004    #[test]
1005    fn callback_def_round_trip_yaml() {
1006        let yaml = r#"
1007version: "0.1.0"
1008modules:
1009  - name: events
1010    functions: []
1011    callbacks:
1012      - name: on_data
1013        params:
1014          - name: payload
1015            type: string
1016        doc: "Fired when data arrives"
1017"#;
1018        let api: Api = serde_yaml::from_str(yaml).unwrap();
1019        let m = &api.modules[0];
1020        assert_eq!(m.callbacks.len(), 1);
1021        let cb = &m.callbacks[0];
1022        assert_eq!(cb.name, "on_data");
1023        assert_eq!(cb.params.len(), 1);
1024        assert_eq!(cb.params[0].name, "payload");
1025        assert_eq!(cb.params[0].ty, TypeRef::StringUtf8);
1026        assert_eq!(cb.doc.as_deref(), Some("Fired when data arrives"));
1027    }
1028
1029    #[test]
1030    fn listener_def_round_trip_yaml() {
1031        let yaml = r#"
1032version: "0.1.0"
1033modules:
1034  - name: events
1035    functions: []
1036    callbacks:
1037      - name: on_data
1038        params: []
1039    listeners:
1040      - name: data_stream
1041        event_callback: on_data
1042        doc: "Subscribe to data events"
1043"#;
1044        let api: Api = serde_yaml::from_str(yaml).unwrap();
1045        let m = &api.modules[0];
1046        assert_eq!(m.listeners.len(), 1);
1047        let l = &m.listeners[0];
1048        assert_eq!(l.name, "data_stream");
1049        assert_eq!(l.event_callback, "on_data");
1050        assert_eq!(l.doc.as_deref(), Some("Subscribe to data events"));
1051    }
1052
1053    #[test]
1054    fn callbacks_and_listeners_default_to_empty() {
1055        let yaml = r#"
1056version: "0.1.0"
1057modules:
1058  - name: math
1059    functions: []
1060"#;
1061        let api: Api = serde_yaml::from_str(yaml).unwrap();
1062        assert!(api.modules[0].callbacks.is_empty());
1063        assert!(api.modules[0].listeners.is_empty());
1064    }
1065
1066    #[test]
1067    fn callback_def_json_round_trip() {
1068        let cb = CallbackDef {
1069            name: "on_event".to_string(),
1070            params: vec![Param {
1071                name: "data".to_string(),
1072                ty: TypeRef::I32,
1073                mutable: false,
1074                doc: None,
1075            }],
1076            doc: Some("event callback".to_string()),
1077        };
1078        let json = serde_json::to_string(&cb).unwrap();
1079        let back: CallbackDef = serde_json::from_str(&json).unwrap();
1080        assert_eq!(back, cb);
1081    }
1082
1083    #[test]
1084    fn listener_def_json_round_trip() {
1085        let l = ListenerDef {
1086            name: "watcher".to_string(),
1087            event_callback: "on_change".to_string(),
1088            doc: None,
1089        };
1090        let json = serde_json::to_string(&l).unwrap();
1091        let back: ListenerDef = serde_json::from_str(&json).unwrap();
1092        assert_eq!(back, l);
1093    }
1094
1095    #[test]
1096    fn builder_defaults_to_false() {
1097        let yaml = r#"
1098version: "0.1.0"
1099modules:
1100  - name: contacts
1101    functions: []
1102    structs:
1103      - name: Contact
1104        fields:
1105          - name: name
1106            type: string
1107"#;
1108        let api: Api = serde_yaml::from_str(yaml).unwrap();
1109        assert!(!api.modules[0].structs[0].builder);
1110    }
1111
1112    #[test]
1113    fn builder_true_round_trip() {
1114        let yaml = r#"
1115version: "0.1.0"
1116modules:
1117  - name: contacts
1118    functions: []
1119    structs:
1120      - name: Contact
1121        fields:
1122          - name: name
1123            type: string
1124        builder: true
1125"#;
1126        let api: Api = serde_yaml::from_str(yaml).unwrap();
1127        assert!(api.modules[0].structs[0].builder);
1128
1129        let json = serde_json::to_string(&api).unwrap();
1130        let back: Api = serde_json::from_str(&json).unwrap();
1131        assert!(back.modules[0].structs[0].builder);
1132    }
1133
1134    #[test]
1135    fn builder_false_explicit() {
1136        let json = r#"{
1137            "version": "0.1.0",
1138            "modules": [{
1139                "name": "geo",
1140                "functions": [],
1141                "structs": [{
1142                    "name": "Point",
1143                    "fields": [{"name": "x", "type": "f64"}],
1144                    "builder": false
1145                }]
1146            }]
1147        }"#;
1148        let api: Api = serde_json::from_str(json).unwrap();
1149        assert!(!api.modules[0].structs[0].builder);
1150    }
1151
1152    #[test]
1153    fn param_mutable_defaults_to_false() {
1154        let yaml = r#"
1155version: "0.1.0"
1156modules:
1157  - name: io
1158    functions:
1159      - name: write
1160        params:
1161          - name: data
1162            type: string
1163"#;
1164        let api: Api = serde_yaml::from_str(yaml).unwrap();
1165        assert!(!api.modules[0].functions[0].params[0].mutable);
1166    }
1167
1168    #[test]
1169    fn param_mutable_true_round_trip() {
1170        let yaml = r#"
1171version: "0.1.0"
1172modules:
1173  - name: io
1174    functions:
1175      - name: fill_buffer
1176        params:
1177          - name: buf
1178            type: bytes
1179            mutable: true
1180"#;
1181        let api: Api = serde_yaml::from_str(yaml).unwrap();
1182        assert!(api.modules[0].functions[0].params[0].mutable);
1183
1184        let json = serde_json::to_string(&api).unwrap();
1185        let back: Api = serde_json::from_str(&json).unwrap();
1186        assert!(back.modules[0].functions[0].params[0].mutable);
1187    }
1188
1189    #[test]
1190    fn param_mutable_false_explicit() {
1191        let json = r#"{
1192            "version": "0.1.0",
1193            "modules": [{
1194                "name": "io",
1195                "functions": [{
1196                    "name": "read",
1197                    "params": [{"name": "buf", "type": "bytes", "mutable": false}]
1198                }]
1199            }]
1200        }"#;
1201        let api: Api = serde_json::from_str(json).unwrap();
1202        assert!(!api.modules[0].functions[0].params[0].mutable);
1203    }
1204
1205    #[test]
1206    fn deprecated_and_since_default_to_none() {
1207        let yaml = r#"
1208version: "0.1.0"
1209modules:
1210  - name: math
1211    functions:
1212      - name: add
1213        params: []
1214"#;
1215        let api: Api = serde_yaml::from_str(yaml).unwrap();
1216        let f = &api.modules[0].functions[0];
1217        assert_eq!(f.deprecated, None);
1218        assert_eq!(f.since, None);
1219    }
1220
1221    #[test]
1222    fn deprecated_and_since_round_trip() {
1223        let yaml = r#"
1224version: "0.1.0"
1225modules:
1226  - name: math
1227    functions:
1228      - name: add_old
1229        params: []
1230        deprecated: "Use add_v2 instead"
1231        since: "0.1.0"
1232"#;
1233        let api: Api = serde_yaml::from_str(yaml).unwrap();
1234        let f = &api.modules[0].functions[0];
1235        assert_eq!(f.deprecated.as_deref(), Some("Use add_v2 instead"));
1236        assert_eq!(f.since.as_deref(), Some("0.1.0"));
1237
1238        let json = serde_json::to_string(&api).unwrap();
1239        let back: Api = serde_json::from_str(&json).unwrap();
1240        let f2 = &back.modules[0].functions[0];
1241        assert_eq!(f2.deprecated.as_deref(), Some("Use add_v2 instead"));
1242        assert_eq!(f2.since.as_deref(), Some("0.1.0"));
1243    }
1244
1245    #[test]
1246    fn struct_field_default_value_round_trip() {
1247        let yaml = r#"
1248version: "0.1.0"
1249modules:
1250  - name: contacts
1251    functions: []
1252    structs:
1253      - name: Contact
1254        fields:
1255          - name: name
1256            type: string
1257          - name: age
1258            type: i32
1259            default: 0
1260"#;
1261        let api: Api = serde_yaml::from_str(yaml).unwrap();
1262        let fields = &api.modules[0].structs[0].fields;
1263        assert!(fields[0].default.is_none());
1264        assert_eq!(
1265            fields[1].default,
1266            Some(serde_yaml::Value::Number(serde_yaml::Number::from(0)))
1267        );
1268    }
1269
1270    #[test]
1271    fn parse_type_ref_does_not_yield_callback() {
1272        assert_eq!(
1273            parse_type_ref("callback"),
1274            Ok(TypeRef::Struct("callback".into()))
1275        );
1276    }
1277
1278    #[test]
1279    fn api_json_schema_derives() {
1280        let schema = schemars::schema_for!(Api);
1281        let json = serde_json::to_value(&schema).unwrap();
1282        assert!(json.get("$schema").is_some());
1283        assert!(json.get("properties").is_some());
1284        assert_eq!(json.get("title").and_then(|v| v.as_str()), Some("Api"));
1285        let defs = json
1286            .get("definitions")
1287            .and_then(|v| v.as_object())
1288            .expect("definitions");
1289        assert!(defs.contains_key("Module"));
1290        assert!(defs.contains_key("Function"));
1291        assert!(defs.contains_key("Param"));
1292        assert!(defs.contains_key("TypeRef"));
1293        assert!(defs.contains_key("StructDef"));
1294        assert!(defs.contains_key("StructField"));
1295        assert!(defs.contains_key("EnumDef"));
1296        assert!(defs.contains_key("EnumVariant"));
1297        assert!(defs.contains_key("CallbackDef"));
1298        assert!(defs.contains_key("ListenerDef"));
1299        assert!(defs.contains_key("ErrorDomain"));
1300        assert!(defs.contains_key("ErrorCode"));
1301    }
1302
1303    #[test]
1304    fn typeref_json_schema_is_string_with_description() {
1305        let schema = schemars::schema_for!(TypeRef);
1306        let json = serde_json::to_value(&schema).unwrap();
1307        assert_eq!(json.get("type").and_then(|v| v.as_str()), Some("string"));
1308        assert!(json
1309            .get("description")
1310            .and_then(|v| v.as_str())
1311            .is_some_and(|s| s.contains("handle<") && s.contains("iter<")));
1312    }
1313}