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