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