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