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#[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#[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#[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
218impl 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#[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}