Skip to main content

forge_ir_bindgen/
convert.rs

1//! Conversions between bindgen-generated WIT types and [`forge_ir`].
2//!
3//! Both `ir-transformer` and `code-generator` worlds share the `types`,
4//! `host-api`, and `stage` interfaces, but `wasmtime::component::bindgen!`
5//! emits *separate* Rust modules per world (the types are structurally
6//! identical, nominally distinct). To avoid duplicating ~600 lines of
7//! conversion glue, we expand the conversions once per world via a
8//! `macro_rules!` macro keyed on the bindings root module.
9//!
10//! Public API:
11//!
12//! * `convert::transformer::*` — operates on
13//!   `crate::bindings::transformer::forge::plugin::types::*`.
14//! * `convert::generator::*` — operates on
15//!   `crate::bindings::generator::forge::plugin::types::*`.
16//!
17//! Both submodules expose:
18//!
19//! * `ir_to_wit(forge_ir::Ir) -> b::types::Ir`
20//! * `ir_from_wit(b::types::Ir) -> Result<forge_ir::Ir, BindgenError>`
21//! * `diagnostic_from_wit(b::types::Diagnostic) -> Result<forge_ir::Diagnostic, BindgenError>`
22//! * `plugin_info_from_wit(b::types::PluginInfo) -> forge_ir::PluginInfo`
23//! * `stage_error_from_wit(b::stage::StageError) -> StageErrorRepr`
24//!
25//! `StageErrorRepr` is a world-independent representation of the WIT
26//! `stage-error` variant; the host translates it to its own `StageError`
27//! type to avoid a circular dependency.
28
29use forge_ir as ir;
30
31/// Host-neutral representation of the WIT `stage-error` variant. Shared
32/// between both worlds; the host wraps it into its own `StageError`.
33#[derive(Debug, Clone, PartialEq)]
34pub enum StageErrorRepr {
35    Rejected {
36        reason: String,
37        diagnostics: Vec<ir::Diagnostic>,
38    },
39    PluginBug(String),
40    ConfigInvalid(String),
41    ResourceExceeded(ResourceKindRepr),
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ResourceKindRepr {
46    Fuel,
47    Memory,
48    Time,
49    OutputSize,
50}
51
52macro_rules! define_world_conversions {
53    ($mod:ident, $world:ident) => {
54        pub mod $mod {
55            //! Conversions between `forge_ir` and the
56            #![doc = concat!(" `", stringify!($world), "` world's WIT types.")]
57
58            use super::{ResourceKindRepr, StageErrorRepr};
59            use crate::bindings::$world::forge::plugin::{stage as b_stage, types as b};
60            use crate::BindgenError;
61            use forge_ir as ir;
62
63            // -------------------------------------------------------------
64            // PluginInfo
65            // -------------------------------------------------------------
66
67            pub fn plugin_info_from_wit(p: b::PluginInfo) -> ir::PluginInfo {
68                ir::PluginInfo {
69                    name: p.name,
70                    version: p.version,
71                }
72            }
73
74            pub fn plugin_info_to_wit(p: ir::PluginInfo) -> b::PluginInfo {
75                b::PluginInfo {
76                    name: p.name,
77                    version: p.version,
78                }
79            }
80
81            // -------------------------------------------------------------
82            // SpecLocation
83            // -------------------------------------------------------------
84
85            fn loc_from(l: b::SpecLocation) -> ir::SpecLocation {
86                ir::SpecLocation {
87                    pointer: l.pointer,
88                    file: l.file,
89                }
90            }
91
92            fn loc_to(l: ir::SpecLocation) -> b::SpecLocation {
93                b::SpecLocation {
94                    pointer: l.pointer,
95                    file: l.file,
96                }
97            }
98
99            // -------------------------------------------------------------
100            // Value
101            // -------------------------------------------------------------
102
103            fn value_from(v: b::Value) -> ir::Value {
104                match v {
105                    b::Value::Null => ir::Value::Null,
106                    b::Value::Bool(value) => ir::Value::Bool { value },
107                    b::Value::Int(value) => ir::Value::Int { value },
108                    b::Value::Float(value) => ir::Value::Float { value },
109                    b::Value::String(value) => ir::Value::String { value },
110                    b::Value::List(items) => ir::Value::List { items },
111                    b::Value::Object(fields) => ir::Value::Object {
112                        fields: fields.into_iter().collect(),
113                    },
114                }
115            }
116
117            fn value_to(v: ir::Value) -> b::Value {
118                match v {
119                    ir::Value::Null => b::Value::Null,
120                    ir::Value::Bool { value } => b::Value::Bool(value),
121                    ir::Value::Int { value } => b::Value::Int(value),
122                    ir::Value::Float { value } => b::Value::Float(value),
123                    ir::Value::String { value } => b::Value::String(value),
124                    ir::Value::List { items } => b::Value::List(items),
125                    ir::Value::Object { fields } => b::Value::Object(fields),
126                }
127            }
128
129            // Extensions are `Vec<(String, ValueRef)>` — identity copy
130            // across the boundary since `ValueRef` is `u32` on both sides.
131            // Kept as functions so call sites stay readable.
132            fn extensions_from(xs: Vec<(String, ir::ValueRef)>) -> Vec<(String, ir::ValueRef)> {
133                xs
134            }
135
136            fn extensions_to(xs: Vec<(String, ir::ValueRef)>) -> Vec<(String, ir::ValueRef)> {
137                xs
138            }
139
140            // -------------------------------------------------------------
141            // Diagnostics
142            // -------------------------------------------------------------
143
144            pub fn diagnostic_from_wit(d: b::Diagnostic) -> Result<ir::Diagnostic, BindgenError> {
145                Ok(ir::Diagnostic {
146                    severity: severity_from(d.severity),
147                    code: d.code,
148                    message: d.message,
149                    location: d.location.map(loc_from),
150                    related: d.related.into_iter().map(related_from).collect(),
151                    suggested_fix: d.suggested_fix.map(fix_from),
152                })
153            }
154
155            pub fn diagnostic_to_wit(d: ir::Diagnostic) -> b::Diagnostic {
156                b::Diagnostic {
157                    severity: severity_to(d.severity),
158                    code: d.code,
159                    message: d.message,
160                    location: d.location.map(loc_to),
161                    related: d.related.into_iter().map(related_to).collect(),
162                    suggested_fix: d.suggested_fix.map(fix_to),
163                }
164            }
165
166            fn severity_from(s: b::Severity) -> ir::Severity {
167                match s {
168                    b::Severity::Error => ir::Severity::Error,
169                    b::Severity::Warning => ir::Severity::Warning,
170                    b::Severity::Info => ir::Severity::Info,
171                    b::Severity::Hint => ir::Severity::Hint,
172                }
173            }
174
175            fn severity_to(s: ir::Severity) -> b::Severity {
176                match s {
177                    ir::Severity::Error => b::Severity::Error,
178                    ir::Severity::Warning => b::Severity::Warning,
179                    ir::Severity::Info => b::Severity::Info,
180                    ir::Severity::Hint => b::Severity::Hint,
181                }
182            }
183
184            fn related_from(r: b::RelatedInfo) -> ir::RelatedInfo {
185                ir::RelatedInfo {
186                    message: r.message,
187                    location: r.location.map(loc_from),
188                }
189            }
190
191            fn related_to(r: ir::RelatedInfo) -> b::RelatedInfo {
192                b::RelatedInfo {
193                    message: r.message,
194                    location: r.location.map(loc_to),
195                }
196            }
197
198            fn fix_from(f: b::FixSuggestion) -> ir::FixSuggestion {
199                ir::FixSuggestion {
200                    message: f.message,
201                    edits: f
202                        .edits
203                        .into_iter()
204                        .map(|e| ir::FixEdit {
205                            location: loc_from(e.location),
206                            replacement: e.replacement,
207                        })
208                        .collect(),
209                }
210            }
211
212            fn fix_to(f: ir::FixSuggestion) -> b::FixSuggestion {
213                b::FixSuggestion {
214                    message: f.message,
215                    edits: f
216                        .edits
217                        .into_iter()
218                        .map(|e| b::FixEdit {
219                            location: loc_to(e.location),
220                            replacement: e.replacement,
221                        })
222                        .collect(),
223                }
224            }
225
226            // -------------------------------------------------------------
227            // StageError
228            // -------------------------------------------------------------
229
230            pub fn stage_error_from_wit(e: b_stage::StageError) -> StageErrorRepr {
231                match e {
232                    b_stage::StageError::Rejected(r) => StageErrorRepr::Rejected {
233                        reason: r.reason,
234                        // Diagnostics from a rejection are best-effort; if
235                        // any fail to convert, surface them as a plugin-bug
236                        // by collapsing to an empty list with the reason
237                        // preserved.
238                        diagnostics: r
239                            .diagnostics
240                            .into_iter()
241                            .filter_map(|d| diagnostic_from_wit(d).ok())
242                            .collect(),
243                    },
244                    b_stage::StageError::PluginBug(s) => StageErrorRepr::PluginBug(s),
245                    b_stage::StageError::ConfigInvalid(s) => StageErrorRepr::ConfigInvalid(s),
246                    b_stage::StageError::ResourceExceeded(k) => {
247                        StageErrorRepr::ResourceExceeded(match k {
248                            b_stage::ResourceKind::Fuel => ResourceKindRepr::Fuel,
249                            b_stage::ResourceKind::Memory => ResourceKindRepr::Memory,
250                            b_stage::ResourceKind::Time => ResourceKindRepr::Time,
251                            b_stage::ResourceKind::OutputSize => ResourceKindRepr::OutputSize,
252                        })
253                    }
254                }
255            }
256
257            // -------------------------------------------------------------
258            // Top-level Ir
259            // -------------------------------------------------------------
260
261            pub fn ir_to_wit(i: ir::Ir) -> b::Ir {
262                b::Ir {
263                    info: api_info_to(i.info),
264                    operations: i.operations.into_iter().map(operation_to).collect(),
265                    types: i.types.into_iter().map(named_type_to).collect(),
266                    security_schemes: i
267                        .security_schemes
268                        .into_iter()
269                        .map(security_scheme_to)
270                        .collect(),
271                    servers: i.servers.into_iter().map(server_to).collect(),
272                    webhooks: i.webhooks.into_iter().map(webhook_to).collect(),
273                    external_docs: i.external_docs.map(external_docs_to),
274                    tags: i.tags.into_iter().map(tag_to).collect(),
275                    json_schema_dialect: i.json_schema_dialect,
276                    self_url: i.self_url,
277                    values: i.values.into_iter().map(value_to).collect(),
278                }
279            }
280
281            pub fn ir_from_wit(i: b::Ir) -> Result<ir::Ir, BindgenError> {
282                let out = ir::Ir {
283                    info: api_info_from(i.info),
284                    operations: i
285                        .operations
286                        .into_iter()
287                        .map(operation_from)
288                        .collect::<Result<_, _>>()?,
289                    types: i
290                        .types
291                        .into_iter()
292                        .map(named_type_from)
293                        .collect::<Result<_, _>>()?,
294                    security_schemes: i
295                        .security_schemes
296                        .into_iter()
297                        .map(security_scheme_from)
298                        .collect(),
299                    servers: i.servers.into_iter().map(server_from).collect(),
300                    webhooks: i
301                        .webhooks
302                        .into_iter()
303                        .map(webhook_from)
304                        .collect::<Result<_, _>>()?,
305                    external_docs: i.external_docs.map(external_docs_from),
306                    tags: i.tags.into_iter().map(tag_from).collect(),
307                    json_schema_dialect: i.json_schema_dialect,
308                    self_url: i.self_url,
309                    values: i.values.into_iter().map(value_from).collect(),
310                };
311                Ok(out)
312            }
313
314            // -------------------------------------------------------------
315            // ApiInfo / Server
316            // -------------------------------------------------------------
317
318            fn api_info_from(a: b::ApiInfo) -> ir::ApiInfo {
319                ir::ApiInfo {
320                    title: a.title,
321                    version: a.version,
322                    summary: a.summary,
323                    description: a.description,
324                    terms_of_service: a.terms_of_service,
325                    contact: a.contact.map(contact_from),
326                    license_name: a.license_name,
327                    license_url: a.license_url,
328                    license_identifier: a.license_identifier,
329                    extensions: extensions_from(a.extensions),
330                }
331            }
332
333            fn api_info_to(a: ir::ApiInfo) -> b::ApiInfo {
334                b::ApiInfo {
335                    title: a.title,
336                    version: a.version,
337                    summary: a.summary,
338                    description: a.description,
339                    terms_of_service: a.terms_of_service,
340                    contact: a.contact.map(contact_to),
341                    license_name: a.license_name,
342                    license_url: a.license_url,
343                    license_identifier: a.license_identifier,
344                    extensions: extensions_to(a.extensions),
345                }
346            }
347
348            fn contact_from(c: b::Contact) -> ir::Contact {
349                ir::Contact {
350                    name: c.name,
351                    url: c.url,
352                    email: c.email,
353                }
354            }
355
356            fn contact_to(c: ir::Contact) -> b::Contact {
357                b::Contact {
358                    name: c.name,
359                    url: c.url,
360                    email: c.email,
361                }
362            }
363
364            fn server_from(s: b::Server) -> ir::Server {
365                ir::Server {
366                    url: s.url,
367                    description: s.description,
368                    name: s.name,
369                    variables: s
370                        .variables
371                        .into_iter()
372                        .map(|(k, v)| (k, server_var_from(v)))
373                        .collect(),
374                    extensions: extensions_from(s.extensions),
375                }
376            }
377
378            fn server_to(s: ir::Server) -> b::Server {
379                b::Server {
380                    url: s.url,
381                    description: s.description,
382                    name: s.name,
383                    variables: s
384                        .variables
385                        .into_iter()
386                        .map(|(k, v)| (k, server_var_to(v)))
387                        .collect(),
388                    extensions: extensions_to(s.extensions),
389                }
390            }
391
392            fn server_var_from(v: b::ServerVariable) -> ir::ServerVariable {
393                ir::ServerVariable {
394                    default: v.default,
395                    r#enum: v.enum_,
396                    description: v.description,
397                    extensions: extensions_from(v.extensions),
398                }
399            }
400
401            fn server_var_to(v: ir::ServerVariable) -> b::ServerVariable {
402                b::ServerVariable {
403                    default: v.default,
404                    enum_: v.r#enum,
405                    description: v.description,
406                    extensions: extensions_to(v.extensions),
407                }
408            }
409
410            // -------------------------------------------------------------
411            // Types
412            // -------------------------------------------------------------
413
414            fn named_type_from(n: b::NamedType) -> Result<ir::NamedType, BindgenError> {
415                Ok(ir::NamedType {
416                    id: n.id,
417                    original_name: n.original_name,
418                    title: n.title,
419                    description: n.description,
420                    deprecated: n.deprecated,
421                    read_only: n.read_only,
422                    write_only: n.write_only,
423                    external_docs: n.external_docs.map(external_docs_from),
424                    default: n.default,
425                    examples: examples_from(n.examples),
426                    xml: n.xml.map(xml_object_from),
427                    definition: type_def_from(n.definition)?,
428                    extensions: extensions_from(n.extensions),
429                    location: n.location.map(loc_from),
430                })
431            }
432
433            fn named_type_to(n: ir::NamedType) -> b::NamedType {
434                b::NamedType {
435                    id: n.id,
436                    original_name: n.original_name,
437                    title: n.title,
438                    description: n.description,
439                    deprecated: n.deprecated,
440                    read_only: n.read_only,
441                    write_only: n.write_only,
442                    external_docs: n.external_docs.map(external_docs_to),
443                    default: n.default,
444                    examples: examples_to(n.examples),
445                    xml: n.xml.map(xml_object_to),
446                    definition: type_def_to(n.definition),
447                    extensions: extensions_to(n.extensions),
448                    location: n.location.map(loc_to),
449                }
450            }
451
452            fn external_docs_from(d: b::ExternalDocs) -> ir::ExternalDocs {
453                ir::ExternalDocs {
454                    description: d.description,
455                    url: d.url,
456                }
457            }
458
459            fn external_docs_to(d: ir::ExternalDocs) -> b::ExternalDocs {
460                b::ExternalDocs {
461                    description: d.description,
462                    url: d.url,
463                }
464            }
465
466            fn example_from(e: b::Example) -> ir::Example {
467                ir::Example {
468                    summary: e.summary,
469                    description: e.description,
470                    value: e.value,
471                    external_value: e.external_value,
472                    data_value: e.data_value,
473                    serialized_value: e.serialized_value,
474                }
475            }
476
477            fn example_to(e: ir::Example) -> b::Example {
478                b::Example {
479                    summary: e.summary,
480                    description: e.description,
481                    value: e.value,
482                    external_value: e.external_value,
483                    data_value: e.data_value,
484                    serialized_value: e.serialized_value,
485                }
486            }
487
488            fn examples_from(xs: Vec<(String, b::Example)>) -> Vec<(String, ir::Example)> {
489                xs.into_iter().map(|(k, v)| (k, example_from(v))).collect()
490            }
491
492            fn examples_to(xs: Vec<(String, ir::Example)>) -> Vec<(String, b::Example)> {
493                xs.into_iter().map(|(k, v)| (k, example_to(v))).collect()
494            }
495
496            fn xml_object_from(x: b::XmlObject) -> ir::XmlObject {
497                ir::XmlObject {
498                    name: x.name,
499                    namespace: x.namespace,
500                    prefix: x.prefix,
501                    attribute: x.attribute,
502                    wrapped: x.wrapped,
503                    text: x.text,
504                    ordered: x.ordered,
505                    extensions: x.extensions,
506                }
507            }
508
509            fn xml_object_to(x: ir::XmlObject) -> b::XmlObject {
510                b::XmlObject {
511                    name: x.name,
512                    namespace: x.namespace,
513                    prefix: x.prefix,
514                    attribute: x.attribute,
515                    wrapped: x.wrapped,
516                    text: x.text,
517                    ordered: x.ordered,
518                    extensions: x.extensions,
519                }
520            }
521
522            fn link_from(l: b::Link) -> ir::Link {
523                ir::Link {
524                    operation_ref: l.operation_ref,
525                    operation_id: l.operation_id,
526                    parameters: l.parameters,
527                    request_body: l.request_body,
528                    description: l.description,
529                    server: l.server.map(server_from),
530                    extensions: l.extensions,
531                }
532            }
533
534            fn link_to(l: ir::Link) -> b::Link {
535                b::Link {
536                    operation_ref: l.operation_ref,
537                    operation_id: l.operation_id,
538                    parameters: l.parameters,
539                    request_body: l.request_body,
540                    description: l.description,
541                    server: l.server.map(server_to),
542                    extensions: l.extensions,
543                }
544            }
545
546            fn links_from(xs: Vec<(String, b::Link)>) -> Vec<(String, ir::Link)> {
547                xs.into_iter().map(|(k, v)| (k, link_from(v))).collect()
548            }
549
550            fn links_to(xs: Vec<(String, ir::Link)>) -> Vec<(String, b::Link)> {
551                xs.into_iter().map(|(k, v)| (k, link_to(v))).collect()
552            }
553
554            fn webhook_from(w: b::Webhook) -> Result<ir::Webhook, BindgenError> {
555                Ok(ir::Webhook {
556                    name: w.name,
557                    summary: w.summary,
558                    description: w.description,
559                    operations: w
560                        .operations
561                        .into_iter()
562                        .map(operation_from)
563                        .collect::<Result<_, _>>()?,
564                })
565            }
566
567            fn webhook_to(w: ir::Webhook) -> b::Webhook {
568                b::Webhook {
569                    name: w.name,
570                    summary: w.summary,
571                    description: w.description,
572                    operations: w.operations.into_iter().map(operation_to).collect(),
573                }
574            }
575
576            fn callback_from(c: b::Callback) -> ir::Callback {
577                ir::Callback {
578                    name: c.name,
579                    expression: c.expression,
580                    operation_ids: c.operation_ids,
581                    extensions: c.extensions,
582                }
583            }
584
585            fn callback_to(c: ir::Callback) -> b::Callback {
586                b::Callback {
587                    name: c.name,
588                    expression: c.expression,
589                    operation_ids: c.operation_ids,
590                    extensions: c.extensions,
591                }
592            }
593
594            fn tag_from(t: b::Tag) -> ir::Tag {
595                ir::Tag {
596                    name: t.name,
597                    summary: t.summary,
598                    description: t.description,
599                    external_docs: t.external_docs.map(external_docs_from),
600                    parent: t.parent,
601                    kind: t.kind,
602                    extensions: t.extensions,
603                }
604            }
605
606            fn tag_to(t: ir::Tag) -> b::Tag {
607                b::Tag {
608                    name: t.name,
609                    summary: t.summary,
610                    description: t.description,
611                    external_docs: t.external_docs.map(external_docs_to),
612                    parent: t.parent,
613                    kind: t.kind,
614                    extensions: t.extensions,
615                }
616            }
617
618            fn type_def_from(d: b::TypeDef) -> Result<ir::TypeDef, BindgenError> {
619                Ok(match d {
620                    b::TypeDef::Primitive(p) => ir::TypeDef::Primitive(prim_from(p)),
621                    b::TypeDef::Object(o) => ir::TypeDef::Object(object_from(o)),
622                    b::TypeDef::Array(a) => ir::TypeDef::Array(array_from(a)),
623                    b::TypeDef::EnumString(e) => ir::TypeDef::EnumString(enum_str_from(e)),
624                    b::TypeDef::EnumInt(e) => ir::TypeDef::EnumInt(enum_int_from(e)),
625                    b::TypeDef::Union(u) => ir::TypeDef::Union(union_from(u)),
626                    b::TypeDef::Null => ir::TypeDef::Null,
627                })
628            }
629
630            fn type_def_to(d: ir::TypeDef) -> b::TypeDef {
631                match d {
632                    ir::TypeDef::Primitive(p) => b::TypeDef::Primitive(prim_to(p)),
633                    ir::TypeDef::Object(o) => b::TypeDef::Object(object_to(o)),
634                    ir::TypeDef::Array(a) => b::TypeDef::Array(array_to(a)),
635                    ir::TypeDef::EnumString(e) => b::TypeDef::EnumString(enum_str_to(e)),
636                    ir::TypeDef::EnumInt(e) => b::TypeDef::EnumInt(enum_int_to(e)),
637                    ir::TypeDef::Union(u) => b::TypeDef::Union(union_to(u)),
638                    ir::TypeDef::Null => b::TypeDef::Null,
639                }
640            }
641
642            // ---- primitive ----
643
644            fn prim_kind_from(k: b::PrimitiveKind) -> ir::PrimitiveKind {
645                use b::PrimitiveKind as W;
646                use ir::PrimitiveKind as I;
647                match k {
648                    W::PrimString => I::String,
649                    W::PrimInteger => I::Integer,
650                    W::PrimNumber => I::Number,
651                    W::PrimBool => I::Bool,
652                }
653            }
654
655            fn prim_kind_to(k: ir::PrimitiveKind) -> b::PrimitiveKind {
656                use b::PrimitiveKind as W;
657                use ir::PrimitiveKind as I;
658                match k {
659                    I::String => W::PrimString,
660                    I::Integer => W::PrimInteger,
661                    I::Number => W::PrimNumber,
662                    I::Bool => W::PrimBool,
663                }
664            }
665
666            fn prim_constraints_from(c: b::PrimitiveConstraints) -> ir::PrimitiveConstraints {
667                ir::PrimitiveConstraints {
668                    minimum: c.minimum,
669                    maximum: c.maximum,
670                    exclusive_minimum: c.exclusive_minimum,
671                    exclusive_maximum: c.exclusive_maximum,
672                    multiple_of: c.multiple_of,
673                    min_length: c.min_length,
674                    max_length: c.max_length,
675                    pattern: c.pattern,
676                    format_extension: c.format_extension,
677                    content_encoding: c.content_encoding,
678                    content_media_type: c.content_media_type,
679                    content_schema: c.content_schema,
680                }
681            }
682
683            fn prim_constraints_to(c: ir::PrimitiveConstraints) -> b::PrimitiveConstraints {
684                b::PrimitiveConstraints {
685                    minimum: c.minimum,
686                    maximum: c.maximum,
687                    exclusive_minimum: c.exclusive_minimum,
688                    exclusive_maximum: c.exclusive_maximum,
689                    multiple_of: c.multiple_of,
690                    min_length: c.min_length,
691                    max_length: c.max_length,
692                    pattern: c.pattern,
693                    format_extension: c.format_extension,
694                    content_encoding: c.content_encoding,
695                    content_media_type: c.content_media_type,
696                    content_schema: c.content_schema,
697                }
698            }
699
700            fn prim_from(p: b::PrimitiveType) -> ir::PrimitiveType {
701                ir::PrimitiveType {
702                    kind: prim_kind_from(p.kind),
703                    constraints: prim_constraints_from(p.constraints),
704                }
705            }
706
707            fn prim_to(p: ir::PrimitiveType) -> b::PrimitiveType {
708                b::PrimitiveType {
709                    kind: prim_kind_to(p.kind),
710                    constraints: prim_constraints_to(p.constraints),
711                }
712            }
713
714            // ---- array ----
715
716            fn array_from(a: b::ArrayType) -> ir::ArrayType {
717                ir::ArrayType {
718                    items: a.items,
719                    constraints: ir::ArrayConstraints {
720                        min_items: a.constraints.min_items,
721                        max_items: a.constraints.max_items,
722                        unique_items: a.constraints.unique_items,
723                    },
724                }
725            }
726
727            fn array_to(a: ir::ArrayType) -> b::ArrayType {
728                b::ArrayType {
729                    items: a.items,
730                    constraints: b::ArrayConstraints {
731                        min_items: a.constraints.min_items,
732                        max_items: a.constraints.max_items,
733                        unique_items: a.constraints.unique_items,
734                    },
735                }
736            }
737
738            // ---- object ----
739
740            fn object_from(o: b::ObjectType) -> ir::ObjectType {
741                ir::ObjectType {
742                    properties: o
743                        .properties
744                        .into_iter()
745                        .map(|p| ir::Property {
746                            name: p.name,
747                            r#type: p.type_,
748                            required: p.required,
749                            title: p.title,
750                            description: p.description,
751                            deprecated: p.deprecated,
752                            read_only: p.read_only,
753                            write_only: p.write_only,
754                            external_docs: p.external_docs.map(external_docs_from),
755                            default: p.default,
756                            examples: examples_from(p.examples),
757                            extensions: p.extensions,
758                        })
759                        .collect(),
760                    additional_properties: match o.additional_properties {
761                        b::AdditionalProperties::Forbidden => ir::AdditionalProperties::Forbidden,
762                        b::AdditionalProperties::Any => ir::AdditionalProperties::Any,
763                        b::AdditionalProperties::Typed(t) => {
764                            ir::AdditionalProperties::Typed { r#type: t }
765                        }
766                    },
767                    constraints: ir::ObjectConstraints {
768                        min_properties: o.constraints.min_properties,
769                        max_properties: o.constraints.max_properties,
770                    },
771                }
772            }
773
774            fn object_to(o: ir::ObjectType) -> b::ObjectType {
775                b::ObjectType {
776                    properties: o
777                        .properties
778                        .into_iter()
779                        .map(|p| b::Property {
780                            name: p.name,
781                            type_: p.r#type,
782                            required: p.required,
783                            title: p.title,
784                            description: p.description,
785                            deprecated: p.deprecated,
786                            read_only: p.read_only,
787                            write_only: p.write_only,
788                            external_docs: p.external_docs.map(external_docs_to),
789                            default: p.default,
790                            examples: examples_to(p.examples),
791                            extensions: p.extensions,
792                        })
793                        .collect(),
794                    additional_properties: match o.additional_properties {
795                        ir::AdditionalProperties::Forbidden => b::AdditionalProperties::Forbidden,
796                        ir::AdditionalProperties::Any => b::AdditionalProperties::Any,
797                        ir::AdditionalProperties::Typed { r#type } => {
798                            b::AdditionalProperties::Typed(r#type)
799                        }
800                    },
801                    constraints: b::ObjectConstraints {
802                        min_properties: o.constraints.min_properties,
803                        max_properties: o.constraints.max_properties,
804                    },
805                }
806            }
807
808            // ---- map ----
809
810            // ---- enums ----
811
812            fn enum_str_from(e: b::EnumStringType) -> ir::EnumStringType {
813                ir::EnumStringType {
814                    values: e
815                        .values
816                        .into_iter()
817                        .map(|v| ir::EnumStringValue { value: v.value })
818                        .collect(),
819                }
820            }
821
822            fn enum_str_to(e: ir::EnumStringType) -> b::EnumStringType {
823                b::EnumStringType {
824                    values: e
825                        .values
826                        .into_iter()
827                        .map(|v| b::EnumStringValue { value: v.value })
828                        .collect(),
829                }
830            }
831
832            fn enum_int_from(e: b::EnumIntType) -> ir::EnumIntType {
833                ir::EnumIntType {
834                    values: e
835                        .values
836                        .into_iter()
837                        .map(|v| ir::EnumIntValue { value: v.value })
838                        .collect(),
839                    kind: match e.kind {
840                        b::IntKind::Int32 => ir::IntKind::Int32,
841                        b::IntKind::Int64 => ir::IntKind::Int64,
842                    },
843                }
844            }
845
846            fn enum_int_to(e: ir::EnumIntType) -> b::EnumIntType {
847                b::EnumIntType {
848                    values: e
849                        .values
850                        .into_iter()
851                        .map(|v| b::EnumIntValue { value: v.value })
852                        .collect(),
853                    kind: match e.kind {
854                        ir::IntKind::Int32 => b::IntKind::Int32,
855                        ir::IntKind::Int64 => b::IntKind::Int64,
856                    },
857                }
858            }
859
860            // ---- union ----
861
862            fn union_from(u: b::UnionType) -> ir::UnionType {
863                ir::UnionType {
864                    variants: u
865                        .variants
866                        .into_iter()
867                        .map(|v| ir::UnionVariant {
868                            r#type: v.type_,
869                            tag: v.tag,
870                        })
871                        .collect(),
872                    discriminator: u.discriminator.map(|d| ir::Discriminator {
873                        property_name: d.property_name,
874                        mapping: d.mapping.into_iter().collect(),
875                        extensions: d.extensions,
876                    }),
877                    kind: match u.kind {
878                        b::UnionKind::OneOf => ir::UnionKind::OneOf,
879                        b::UnionKind::AnyOf => ir::UnionKind::AnyOf,
880                    },
881                }
882            }
883
884            fn union_to(u: ir::UnionType) -> b::UnionType {
885                b::UnionType {
886                    variants: u
887                        .variants
888                        .into_iter()
889                        .map(|v| b::UnionVariant {
890                            type_: v.r#type,
891                            tag: v.tag,
892                        })
893                        .collect(),
894                    discriminator: u.discriminator.map(|d| b::Discriminator {
895                        property_name: d.property_name,
896                        mapping: d.mapping,
897                        extensions: d.extensions,
898                    }),
899                    kind: match u.kind {
900                        ir::UnionKind::OneOf => b::UnionKind::OneOf,
901                        ir::UnionKind::AnyOf => b::UnionKind::AnyOf,
902                    },
903                }
904            }
905
906            // -------------------------------------------------------------
907            // Operations
908            // -------------------------------------------------------------
909
910            fn http_method_from(m: b::HttpMethod) -> ir::HttpMethod {
911                use b::HttpMethod as W;
912                use ir::HttpMethod as I;
913                match m {
914                    W::Get => I::Get,
915                    W::Put => I::Put,
916                    W::Post => I::Post,
917                    W::Delete => I::Delete,
918                    W::Options => I::Options,
919                    W::Head => I::Head,
920                    W::Patch => I::Patch,
921                    W::Trace => I::Trace,
922                    W::Other(s) => I::Other(s),
923                }
924            }
925
926            fn http_method_to(m: ir::HttpMethod) -> b::HttpMethod {
927                use b::HttpMethod as W;
928                use ir::HttpMethod as I;
929                match m {
930                    I::Get => W::Get,
931                    I::Put => W::Put,
932                    I::Post => W::Post,
933                    I::Delete => W::Delete,
934                    I::Options => W::Options,
935                    I::Head => W::Head,
936                    I::Patch => W::Patch,
937                    I::Trace => W::Trace,
938                    I::Other(s) => W::Other(s),
939                }
940            }
941
942            fn param_style_from(s: b::ParameterStyle) -> ir::ParameterStyle {
943                use b::ParameterStyle as W;
944                use ir::ParameterStyle as I;
945                match s {
946                    W::ParamForm => I::Form,
947                    W::ParamSimple => I::Simple,
948                    W::ParamLabel => I::Label,
949                    W::ParamMatrix => I::Matrix,
950                    W::ParamSpaceDelimited => I::SpaceDelimited,
951                    W::ParamPipeDelimited => I::PipeDelimited,
952                    W::ParamDeepObject => I::DeepObject,
953                }
954            }
955
956            fn param_style_to(s: ir::ParameterStyle) -> b::ParameterStyle {
957                use b::ParameterStyle as W;
958                use ir::ParameterStyle as I;
959                match s {
960                    I::Form => W::ParamForm,
961                    I::Simple => W::ParamSimple,
962                    I::Label => W::ParamLabel,
963                    I::Matrix => W::ParamMatrix,
964                    I::SpaceDelimited => W::ParamSpaceDelimited,
965                    I::PipeDelimited => W::ParamPipeDelimited,
966                    I::DeepObject => W::ParamDeepObject,
967                }
968            }
969
970            fn header_from(h: b::Header) -> ir::Header {
971                ir::Header {
972                    r#type: h.type_,
973                    required: h.required,
974                    description: h.description,
975                    deprecated: h.deprecated,
976                    examples: examples_from(h.examples),
977                    style: h.style.map(param_style_from),
978                    explode: h.explode,
979                    allow_reserved: h.allow_reserved,
980                    allow_empty_value: h.allow_empty_value,
981                    location: h.location.map(loc_from),
982                }
983            }
984
985            fn header_to(h: ir::Header) -> b::Header {
986                b::Header {
987                    type_: h.r#type,
988                    required: h.required,
989                    description: h.description,
990                    deprecated: h.deprecated,
991                    examples: examples_to(h.examples),
992                    style: h.style.map(param_style_to),
993                    explode: h.explode,
994                    allow_reserved: h.allow_reserved,
995                    allow_empty_value: h.allow_empty_value,
996                    location: h.location.map(loc_to),
997                }
998            }
999
1000            fn parameter_from(p: b::Parameter) -> ir::Parameter {
1001                ir::Parameter {
1002                    name: p.name,
1003                    r#type: p.type_,
1004                    required: p.required,
1005                    description: p.description,
1006                    deprecated: p.deprecated,
1007                    examples: examples_from(p.examples),
1008                    style: p.style.map(param_style_from),
1009                    explode: p.explode,
1010                    allow_empty_value: p.allow_empty_value,
1011                    allow_reserved: p.allow_reserved,
1012                    extensions: extensions_from(p.extensions),
1013                    location: p.location.map(loc_from),
1014                }
1015            }
1016
1017            fn parameter_to(p: ir::Parameter) -> b::Parameter {
1018                b::Parameter {
1019                    name: p.name,
1020                    type_: p.r#type,
1021                    required: p.required,
1022                    description: p.description,
1023                    deprecated: p.deprecated,
1024                    examples: examples_to(p.examples),
1025                    style: p.style.map(param_style_to),
1026                    explode: p.explode,
1027                    allow_empty_value: p.allow_empty_value,
1028                    allow_reserved: p.allow_reserved,
1029                    extensions: extensions_to(p.extensions),
1030                    location: p.location.map(loc_to),
1031                }
1032            }
1033
1034            fn body_from(b_: b::Body) -> ir::Body {
1035                ir::Body {
1036                    content: b_.content.into_iter().map(body_content_from).collect(),
1037                    required: b_.required,
1038                    description: b_.description,
1039                    extensions: extensions_from(b_.extensions),
1040                }
1041            }
1042
1043            fn body_to(b_: ir::Body) -> b::Body {
1044                b::Body {
1045                    content: b_.content.into_iter().map(body_content_to).collect(),
1046                    required: b_.required,
1047                    description: b_.description,
1048                    extensions: extensions_to(b_.extensions),
1049                }
1050            }
1051
1052            fn body_content_from(c: b::BodyContent) -> ir::BodyContent {
1053                ir::BodyContent {
1054                    media_type: c.media_type,
1055                    r#type: c.type_,
1056                    encoding: c
1057                        .encoding
1058                        .into_iter()
1059                        .map(|(k, v)| (k, encoding_from(v)))
1060                        .collect(),
1061                    item_schema: c.item_schema,
1062                    examples: examples_from(c.examples),
1063                    extensions: extensions_from(c.extensions),
1064                }
1065            }
1066
1067            fn body_content_to(c: ir::BodyContent) -> b::BodyContent {
1068                b::BodyContent {
1069                    media_type: c.media_type,
1070                    type_: c.r#type,
1071                    encoding: c
1072                        .encoding
1073                        .into_iter()
1074                        .map(|(k, v)| (k, encoding_to(v)))
1075                        .collect(),
1076                    item_schema: c.item_schema,
1077                    examples: examples_to(c.examples),
1078                    extensions: extensions_to(c.extensions),
1079                }
1080            }
1081
1082            fn encoding_from(e: b::Encoding) -> ir::Encoding {
1083                ir::Encoding {
1084                    content_type: e.content_type,
1085                    style: e.style.map(param_style_from),
1086                    explode: e.explode,
1087                    allow_reserved: e.allow_reserved,
1088                    headers: e
1089                        .headers
1090                        .into_iter()
1091                        .map(|(k, v)| (k, header_from(v)))
1092                        .collect(),
1093                    extensions: extensions_from(e.extensions),
1094                }
1095            }
1096
1097            fn encoding_to(e: ir::Encoding) -> b::Encoding {
1098                b::Encoding {
1099                    content_type: e.content_type,
1100                    style: e.style.map(param_style_to),
1101                    explode: e.explode,
1102                    allow_reserved: e.allow_reserved,
1103                    headers: e
1104                        .headers
1105                        .into_iter()
1106                        .map(|(k, v)| (k, header_to(v)))
1107                        .collect(),
1108                    extensions: extensions_to(e.extensions),
1109                }
1110            }
1111
1112            fn response_from(r: b::Response) -> ir::Response {
1113                ir::Response {
1114                    status: match r.status {
1115                        b::ResponseStatus::Explicit(code) => ir::ResponseStatus::Explicit { code },
1116                        b::ResponseStatus::Default => ir::ResponseStatus::Default,
1117                        b::ResponseStatus::Range(class) => ir::ResponseStatus::Range { class },
1118                    },
1119                    content: r.content.into_iter().map(body_content_from).collect(),
1120                    headers: r
1121                        .headers
1122                        .into_iter()
1123                        .map(|(k, v)| (k, header_from(v)))
1124                        .collect(),
1125                    summary: r.summary,
1126                    description: r.description,
1127                    links: links_from(r.links),
1128                    extensions: extensions_from(r.extensions),
1129                }
1130            }
1131
1132            fn response_to(r: ir::Response) -> b::Response {
1133                b::Response {
1134                    status: match r.status {
1135                        ir::ResponseStatus::Explicit { code } => b::ResponseStatus::Explicit(code),
1136                        ir::ResponseStatus::Default => b::ResponseStatus::Default,
1137                        ir::ResponseStatus::Range { class } => b::ResponseStatus::Range(class),
1138                    },
1139                    content: r.content.into_iter().map(body_content_to).collect(),
1140                    headers: r
1141                        .headers
1142                        .into_iter()
1143                        .map(|(k, v)| (k, header_to(v)))
1144                        .collect(),
1145                    summary: r.summary,
1146                    description: r.description,
1147                    links: links_to(r.links),
1148                    extensions: extensions_to(r.extensions),
1149                }
1150            }
1151
1152            fn operation_from(op: b::Operation) -> Result<ir::Operation, BindgenError> {
1153                Ok(ir::Operation {
1154                    id: op.id,
1155                    original_id: op.original_id,
1156                    method: http_method_from(op.method),
1157                    path_template: op.path_template,
1158                    path_params: op.path_params.into_iter().map(parameter_from).collect(),
1159                    query_params: op.query_params.into_iter().map(parameter_from).collect(),
1160                    header_params: op.header_params.into_iter().map(parameter_from).collect(),
1161                    cookie_params: op.cookie_params.into_iter().map(parameter_from).collect(),
1162                    querystring_params: op
1163                        .querystring_params
1164                        .into_iter()
1165                        .map(parameter_from)
1166                        .collect(),
1167                    request_body: op.request_body.map(body_from),
1168                    responses: op.responses.into_iter().map(response_from).collect(),
1169                    security: op
1170                        .security
1171                        .into_iter()
1172                        .map(|s| ir::SecurityRequirement {
1173                            scheme_id: s.scheme_id,
1174                            scopes: s.scopes,
1175                        })
1176                        .collect(),
1177                    tags: op.tags,
1178                    summary: op.summary,
1179                    description: op.description,
1180                    deprecated: op.deprecated,
1181                    external_docs: op.external_docs.map(external_docs_from),
1182                    extensions: op.extensions,
1183                    servers: op.servers.into_iter().map(server_from).collect(),
1184                    callbacks: op.callbacks.into_iter().map(callback_from).collect(),
1185                    location: op.location.map(loc_from),
1186                })
1187            }
1188
1189            fn operation_to(op: ir::Operation) -> b::Operation {
1190                b::Operation {
1191                    id: op.id,
1192                    original_id: op.original_id,
1193                    method: http_method_to(op.method),
1194                    path_template: op.path_template,
1195                    path_params: op.path_params.into_iter().map(parameter_to).collect(),
1196                    query_params: op.query_params.into_iter().map(parameter_to).collect(),
1197                    header_params: op.header_params.into_iter().map(parameter_to).collect(),
1198                    cookie_params: op.cookie_params.into_iter().map(parameter_to).collect(),
1199                    querystring_params: op
1200                        .querystring_params
1201                        .into_iter()
1202                        .map(parameter_to)
1203                        .collect(),
1204                    request_body: op.request_body.map(body_to),
1205                    responses: op.responses.into_iter().map(response_to).collect(),
1206                    security: op
1207                        .security
1208                        .into_iter()
1209                        .map(|s| b::SecurityRequirement {
1210                            scheme_id: s.scheme_id,
1211                            scopes: s.scopes,
1212                        })
1213                        .collect(),
1214                    tags: op.tags,
1215                    summary: op.summary,
1216                    description: op.description,
1217                    deprecated: op.deprecated,
1218                    external_docs: op.external_docs.map(external_docs_to),
1219                    extensions: op.extensions,
1220                    servers: op.servers.into_iter().map(server_to).collect(),
1221                    callbacks: op.callbacks.into_iter().map(callback_to).collect(),
1222                    location: op.location.map(loc_to),
1223                }
1224            }
1225
1226            // -------------------------------------------------------------
1227            // Security
1228            // -------------------------------------------------------------
1229
1230            fn security_scheme_from(s: b::SecurityScheme) -> ir::SecurityScheme {
1231                ir::SecurityScheme {
1232                    id: s.id,
1233                    kind: match s.kind {
1234                        b::SecuritySchemeKind::ApiKey(k) => {
1235                            ir::SecuritySchemeKind::ApiKey(ir::ApiKeyScheme {
1236                                name: k.name,
1237                                location: match k.location {
1238                                    b::ApiKeyLocation::Header => ir::ApiKeyLocation::Header,
1239                                    b::ApiKeyLocation::Query => ir::ApiKeyLocation::Query,
1240                                    b::ApiKeyLocation::Cookie => ir::ApiKeyLocation::Cookie,
1241                                },
1242                            })
1243                        }
1244                        b::SecuritySchemeKind::HttpBasic => ir::SecuritySchemeKind::HttpBasic,
1245                        b::SecuritySchemeKind::HttpBearer(f) => {
1246                            ir::SecuritySchemeKind::HttpBearer { bearer_format: f }
1247                        }
1248                        b::SecuritySchemeKind::MutualTls => ir::SecuritySchemeKind::MutualTls,
1249                        b::SecuritySchemeKind::Oauth2(o) => {
1250                            ir::SecuritySchemeKind::Oauth2(ir::OAuth2Scheme {
1251                                flows: o.flows.into_iter().map(oauth2_flow_from).collect(),
1252                            })
1253                        }
1254                        b::SecuritySchemeKind::OpenIdConnect(u) => {
1255                            ir::SecuritySchemeKind::OpenIdConnect { url: u }
1256                        }
1257                    },
1258                    description: s.description,
1259                    deprecated: s.deprecated,
1260                    extensions: extensions_from(s.extensions),
1261                }
1262            }
1263
1264            fn security_scheme_to(s: ir::SecurityScheme) -> b::SecurityScheme {
1265                b::SecurityScheme {
1266                    id: s.id,
1267                    kind: match s.kind {
1268                        ir::SecuritySchemeKind::ApiKey(k) => {
1269                            b::SecuritySchemeKind::ApiKey(b::ApiKeyScheme {
1270                                name: k.name,
1271                                location: match k.location {
1272                                    ir::ApiKeyLocation::Header => b::ApiKeyLocation::Header,
1273                                    ir::ApiKeyLocation::Query => b::ApiKeyLocation::Query,
1274                                    ir::ApiKeyLocation::Cookie => b::ApiKeyLocation::Cookie,
1275                                },
1276                            })
1277                        }
1278                        ir::SecuritySchemeKind::HttpBasic => b::SecuritySchemeKind::HttpBasic,
1279                        ir::SecuritySchemeKind::HttpBearer { bearer_format } => {
1280                            b::SecuritySchemeKind::HttpBearer(bearer_format)
1281                        }
1282                        ir::SecuritySchemeKind::MutualTls => b::SecuritySchemeKind::MutualTls,
1283                        ir::SecuritySchemeKind::Oauth2(o) => {
1284                            b::SecuritySchemeKind::Oauth2(b::Oauth2Scheme {
1285                                flows: o.flows.into_iter().map(oauth2_flow_to).collect(),
1286                            })
1287                        }
1288                        ir::SecuritySchemeKind::OpenIdConnect { url } => {
1289                            b::SecuritySchemeKind::OpenIdConnect(url)
1290                        }
1291                    },
1292                    description: s.description,
1293                    deprecated: s.deprecated,
1294                    extensions: extensions_to(s.extensions),
1295                }
1296            }
1297
1298            fn oauth2_flow_from(f: b::Oauth2Flow) -> ir::OAuth2Flow {
1299                ir::OAuth2Flow {
1300                    kind: match f.kind {
1301                        b::Oauth2FlowKind::Implicit => ir::OAuth2FlowKind::Implicit,
1302                        b::Oauth2FlowKind::Password => ir::OAuth2FlowKind::Password,
1303                        b::Oauth2FlowKind::ClientCredentials => {
1304                            ir::OAuth2FlowKind::ClientCredentials
1305                        }
1306                        b::Oauth2FlowKind::AuthorizationCode => {
1307                            ir::OAuth2FlowKind::AuthorizationCode
1308                        }
1309                    },
1310                    authorization_url: f.authorization_url,
1311                    token_url: f.token_url,
1312                    refresh_url: f.refresh_url,
1313                    scopes: f.scopes,
1314                    extensions: extensions_from(f.extensions),
1315                }
1316            }
1317
1318            fn oauth2_flow_to(f: ir::OAuth2Flow) -> b::Oauth2Flow {
1319                b::Oauth2Flow {
1320                    kind: match f.kind {
1321                        ir::OAuth2FlowKind::Implicit => b::Oauth2FlowKind::Implicit,
1322                        ir::OAuth2FlowKind::Password => b::Oauth2FlowKind::Password,
1323                        ir::OAuth2FlowKind::ClientCredentials => {
1324                            b::Oauth2FlowKind::ClientCredentials
1325                        }
1326                        ir::OAuth2FlowKind::AuthorizationCode => {
1327                            b::Oauth2FlowKind::AuthorizationCode
1328                        }
1329                    },
1330                    authorization_url: f.authorization_url,
1331                    token_url: f.token_url,
1332                    refresh_url: f.refresh_url,
1333                    scopes: f.scopes,
1334                    extensions: extensions_to(f.extensions),
1335                }
1336            }
1337        }
1338    };
1339}
1340
1341define_world_conversions!(transformer, transformer);
1342define_world_conversions!(generator, generator);
1343
1344#[cfg(test)]
1345mod tests {
1346    use super::*;
1347    use forge_ir::proptest_util::small_ir;
1348    use proptest::prelude::*;
1349
1350    proptest! {
1351        /// Roundtrip: forge_ir → wit (transformer) → forge_ir is identity.
1352        #[test]
1353        fn roundtrip_transformer(ir in small_ir()) {
1354            let wit = transformer::ir_to_wit(ir.clone());
1355            let back = transformer::ir_from_wit(wit).unwrap();
1356            prop_assert_eq!(ir, back);
1357        }
1358
1359        /// Roundtrip: forge_ir → wit (generator) → forge_ir is identity.
1360        #[test]
1361        fn roundtrip_generator(ir in small_ir()) {
1362            let wit = generator::ir_to_wit(ir.clone());
1363            let back = generator::ir_from_wit(wit).unwrap();
1364            prop_assert_eq!(ir, back);
1365        }
1366    }
1367}