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                    description: a.description,
323                    summary: a.summary,
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                    description: a.description,
338                    summary: a.summary,
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                    documentation: n.documentation,
419                    title: n.title,
420                    read_only: n.read_only,
421                    write_only: n.write_only,
422                    external_docs: n.external_docs.map(external_docs_from),
423                    default: n.default,
424                    examples: examples_from(n.examples),
425                    xml: n.xml.map(xml_object_from),
426                    definition: type_def_from(n.definition)?,
427                    extensions: extensions_from(n.extensions),
428                    location: n.location.map(loc_from),
429                })
430            }
431
432            fn named_type_to(n: ir::NamedType) -> b::NamedType {
433                b::NamedType {
434                    id: n.id,
435                    original_name: n.original_name,
436                    documentation: n.documentation,
437                    title: n.title,
438                    read_only: n.read_only,
439                    write_only: n.write_only,
440                    external_docs: n.external_docs.map(external_docs_to),
441                    default: n.default,
442                    examples: examples_to(n.examples),
443                    xml: n.xml.map(xml_object_to),
444                    definition: type_def_to(n.definition),
445                    extensions: extensions_to(n.extensions),
446                    location: n.location.map(loc_to),
447                }
448            }
449
450            fn external_docs_from(d: b::ExternalDocs) -> ir::ExternalDocs {
451                ir::ExternalDocs {
452                    description: d.description,
453                    url: d.url,
454                }
455            }
456
457            fn external_docs_to(d: ir::ExternalDocs) -> b::ExternalDocs {
458                b::ExternalDocs {
459                    description: d.description,
460                    url: d.url,
461                }
462            }
463
464            fn example_from(e: b::Example) -> ir::Example {
465                ir::Example {
466                    summary: e.summary,
467                    description: e.description,
468                    value: e.value,
469                    external_value: e.external_value,
470                    data_value: e.data_value,
471                    serialized_value: e.serialized_value,
472                }
473            }
474
475            fn example_to(e: ir::Example) -> b::Example {
476                b::Example {
477                    summary: e.summary,
478                    description: e.description,
479                    value: e.value,
480                    external_value: e.external_value,
481                    data_value: e.data_value,
482                    serialized_value: e.serialized_value,
483                }
484            }
485
486            fn examples_from(xs: Vec<(String, b::Example)>) -> Vec<(String, ir::Example)> {
487                xs.into_iter().map(|(k, v)| (k, example_from(v))).collect()
488            }
489
490            fn examples_to(xs: Vec<(String, ir::Example)>) -> Vec<(String, b::Example)> {
491                xs.into_iter().map(|(k, v)| (k, example_to(v))).collect()
492            }
493
494            fn xml_object_from(x: b::XmlObject) -> ir::XmlObject {
495                ir::XmlObject {
496                    name: x.name,
497                    namespace: x.namespace,
498                    prefix: x.prefix,
499                    attribute: x.attribute,
500                    wrapped: x.wrapped,
501                    text: x.text,
502                    ordered: x.ordered,
503                    extensions: x.extensions,
504                }
505            }
506
507            fn xml_object_to(x: ir::XmlObject) -> b::XmlObject {
508                b::XmlObject {
509                    name: x.name,
510                    namespace: x.namespace,
511                    prefix: x.prefix,
512                    attribute: x.attribute,
513                    wrapped: x.wrapped,
514                    text: x.text,
515                    ordered: x.ordered,
516                    extensions: x.extensions,
517                }
518            }
519
520            fn link_from(l: b::Link) -> ir::Link {
521                ir::Link {
522                    operation_ref: l.operation_ref,
523                    operation_id: l.operation_id,
524                    parameters: l.parameters,
525                    request_body: l.request_body,
526                    description: l.description,
527                    server: l.server.map(server_from),
528                    extensions: l.extensions,
529                }
530            }
531
532            fn link_to(l: ir::Link) -> b::Link {
533                b::Link {
534                    operation_ref: l.operation_ref,
535                    operation_id: l.operation_id,
536                    parameters: l.parameters,
537                    request_body: l.request_body,
538                    description: l.description,
539                    server: l.server.map(server_to),
540                    extensions: l.extensions,
541                }
542            }
543
544            fn links_from(xs: Vec<(String, b::Link)>) -> Vec<(String, ir::Link)> {
545                xs.into_iter().map(|(k, v)| (k, link_from(v))).collect()
546            }
547
548            fn links_to(xs: Vec<(String, ir::Link)>) -> Vec<(String, b::Link)> {
549                xs.into_iter().map(|(k, v)| (k, link_to(v))).collect()
550            }
551
552            fn webhook_from(w: b::Webhook) -> Result<ir::Webhook, BindgenError> {
553                Ok(ir::Webhook {
554                    name: w.name,
555                    operations: w
556                        .operations
557                        .into_iter()
558                        .map(operation_from)
559                        .collect::<Result<_, _>>()?,
560                })
561            }
562
563            fn webhook_to(w: ir::Webhook) -> b::Webhook {
564                b::Webhook {
565                    name: w.name,
566                    operations: w.operations.into_iter().map(operation_to).collect(),
567                }
568            }
569
570            fn callback_from(c: b::Callback) -> ir::Callback {
571                ir::Callback {
572                    name: c.name,
573                    expression: c.expression,
574                    operation_ids: c.operation_ids,
575                    extensions: c.extensions,
576                }
577            }
578
579            fn callback_to(c: ir::Callback) -> b::Callback {
580                b::Callback {
581                    name: c.name,
582                    expression: c.expression,
583                    operation_ids: c.operation_ids,
584                    extensions: c.extensions,
585                }
586            }
587
588            fn tag_from(t: b::Tag) -> ir::Tag {
589                ir::Tag {
590                    name: t.name,
591                    summary: t.summary,
592                    description: t.description,
593                    external_docs: t.external_docs.map(external_docs_from),
594                    parent: t.parent,
595                    kind: t.kind,
596                    extensions: t.extensions,
597                }
598            }
599
600            fn tag_to(t: ir::Tag) -> b::Tag {
601                b::Tag {
602                    name: t.name,
603                    summary: t.summary,
604                    description: t.description,
605                    external_docs: t.external_docs.map(external_docs_to),
606                    parent: t.parent,
607                    kind: t.kind,
608                    extensions: t.extensions,
609                }
610            }
611
612            fn type_def_from(d: b::TypeDef) -> Result<ir::TypeDef, BindgenError> {
613                Ok(match d {
614                    b::TypeDef::Primitive(p) => ir::TypeDef::Primitive(prim_from(p)),
615                    b::TypeDef::Object(o) => ir::TypeDef::Object(object_from(o)),
616                    b::TypeDef::Array(a) => ir::TypeDef::Array(array_from(a)),
617                    b::TypeDef::EnumString(e) => ir::TypeDef::EnumString(enum_str_from(e)),
618                    b::TypeDef::EnumInt(e) => ir::TypeDef::EnumInt(enum_int_from(e)),
619                    b::TypeDef::Union(u) => ir::TypeDef::Union(union_from(u)),
620                    b::TypeDef::Null => ir::TypeDef::Null,
621                })
622            }
623
624            fn type_def_to(d: ir::TypeDef) -> b::TypeDef {
625                match d {
626                    ir::TypeDef::Primitive(p) => b::TypeDef::Primitive(prim_to(p)),
627                    ir::TypeDef::Object(o) => b::TypeDef::Object(object_to(o)),
628                    ir::TypeDef::Array(a) => b::TypeDef::Array(array_to(a)),
629                    ir::TypeDef::EnumString(e) => b::TypeDef::EnumString(enum_str_to(e)),
630                    ir::TypeDef::EnumInt(e) => b::TypeDef::EnumInt(enum_int_to(e)),
631                    ir::TypeDef::Union(u) => b::TypeDef::Union(union_to(u)),
632                    ir::TypeDef::Null => b::TypeDef::Null,
633                }
634            }
635
636            // ---- primitive ----
637
638            fn prim_kind_from(k: b::PrimitiveKind) -> ir::PrimitiveKind {
639                use b::PrimitiveKind as W;
640                use ir::PrimitiveKind as I;
641                match k {
642                    W::PrimString => I::String,
643                    W::PrimInteger => I::Integer,
644                    W::PrimNumber => I::Number,
645                    W::PrimBool => I::Bool,
646                }
647            }
648
649            fn prim_kind_to(k: ir::PrimitiveKind) -> b::PrimitiveKind {
650                use b::PrimitiveKind as W;
651                use ir::PrimitiveKind as I;
652                match k {
653                    I::String => W::PrimString,
654                    I::Integer => W::PrimInteger,
655                    I::Number => W::PrimNumber,
656                    I::Bool => W::PrimBool,
657                }
658            }
659
660            fn prim_constraints_from(c: b::PrimitiveConstraints) -> ir::PrimitiveConstraints {
661                ir::PrimitiveConstraints {
662                    minimum: c.minimum,
663                    maximum: c.maximum,
664                    exclusive_minimum: c.exclusive_minimum,
665                    exclusive_maximum: c.exclusive_maximum,
666                    multiple_of: c.multiple_of,
667                    min_length: c.min_length,
668                    max_length: c.max_length,
669                    pattern: c.pattern,
670                    format_extension: c.format_extension,
671                    content_encoding: c.content_encoding,
672                    content_media_type: c.content_media_type,
673                    content_schema: c.content_schema,
674                }
675            }
676
677            fn prim_constraints_to(c: ir::PrimitiveConstraints) -> b::PrimitiveConstraints {
678                b::PrimitiveConstraints {
679                    minimum: c.minimum,
680                    maximum: c.maximum,
681                    exclusive_minimum: c.exclusive_minimum,
682                    exclusive_maximum: c.exclusive_maximum,
683                    multiple_of: c.multiple_of,
684                    min_length: c.min_length,
685                    max_length: c.max_length,
686                    pattern: c.pattern,
687                    format_extension: c.format_extension,
688                    content_encoding: c.content_encoding,
689                    content_media_type: c.content_media_type,
690                    content_schema: c.content_schema,
691                }
692            }
693
694            fn prim_from(p: b::PrimitiveType) -> ir::PrimitiveType {
695                ir::PrimitiveType {
696                    kind: prim_kind_from(p.kind),
697                    constraints: prim_constraints_from(p.constraints),
698                }
699            }
700
701            fn prim_to(p: ir::PrimitiveType) -> b::PrimitiveType {
702                b::PrimitiveType {
703                    kind: prim_kind_to(p.kind),
704                    constraints: prim_constraints_to(p.constraints),
705                }
706            }
707
708            // ---- array ----
709
710            fn array_from(a: b::ArrayType) -> ir::ArrayType {
711                ir::ArrayType {
712                    items: a.items,
713                    constraints: ir::ArrayConstraints {
714                        min_items: a.constraints.min_items,
715                        max_items: a.constraints.max_items,
716                        unique_items: a.constraints.unique_items,
717                    },
718                }
719            }
720
721            fn array_to(a: ir::ArrayType) -> b::ArrayType {
722                b::ArrayType {
723                    items: a.items,
724                    constraints: b::ArrayConstraints {
725                        min_items: a.constraints.min_items,
726                        max_items: a.constraints.max_items,
727                        unique_items: a.constraints.unique_items,
728                    },
729                }
730            }
731
732            // ---- object ----
733
734            fn object_from(o: b::ObjectType) -> ir::ObjectType {
735                ir::ObjectType {
736                    properties: o
737                        .properties
738                        .into_iter()
739                        .map(|p| ir::Property {
740                            name: p.name,
741                            r#type: p.type_,
742                            required: p.required,
743                            documentation: p.documentation,
744                            deprecated: p.deprecated,
745                            read_only: p.read_only,
746                            write_only: p.write_only,
747                            default: p.default,
748                            extensions: p.extensions,
749                        })
750                        .collect(),
751                    additional_properties: match o.additional_properties {
752                        b::AdditionalProperties::Forbidden => ir::AdditionalProperties::Forbidden,
753                        b::AdditionalProperties::Any => ir::AdditionalProperties::Any,
754                        b::AdditionalProperties::Typed(t) => {
755                            ir::AdditionalProperties::Typed { r#type: t }
756                        }
757                    },
758                    constraints: ir::ObjectConstraints {
759                        min_properties: o.constraints.min_properties,
760                        max_properties: o.constraints.max_properties,
761                    },
762                }
763            }
764
765            fn object_to(o: ir::ObjectType) -> b::ObjectType {
766                b::ObjectType {
767                    properties: o
768                        .properties
769                        .into_iter()
770                        .map(|p| b::Property {
771                            name: p.name,
772                            type_: p.r#type,
773                            required: p.required,
774                            documentation: p.documentation,
775                            deprecated: p.deprecated,
776                            read_only: p.read_only,
777                            write_only: p.write_only,
778                            default: p.default,
779                            extensions: p.extensions,
780                        })
781                        .collect(),
782                    additional_properties: match o.additional_properties {
783                        ir::AdditionalProperties::Forbidden => b::AdditionalProperties::Forbidden,
784                        ir::AdditionalProperties::Any => b::AdditionalProperties::Any,
785                        ir::AdditionalProperties::Typed { r#type } => {
786                            b::AdditionalProperties::Typed(r#type)
787                        }
788                    },
789                    constraints: b::ObjectConstraints {
790                        min_properties: o.constraints.min_properties,
791                        max_properties: o.constraints.max_properties,
792                    },
793                }
794            }
795
796            // ---- map ----
797
798            // ---- enums ----
799
800            fn enum_str_from(e: b::EnumStringType) -> ir::EnumStringType {
801                ir::EnumStringType {
802                    values: e
803                        .values
804                        .into_iter()
805                        .map(|v| ir::EnumStringValue {
806                            value: v.value,
807                            documentation: v.documentation,
808                        })
809                        .collect(),
810                }
811            }
812
813            fn enum_str_to(e: ir::EnumStringType) -> b::EnumStringType {
814                b::EnumStringType {
815                    values: e
816                        .values
817                        .into_iter()
818                        .map(|v| b::EnumStringValue {
819                            value: v.value,
820                            documentation: v.documentation,
821                        })
822                        .collect(),
823                }
824            }
825
826            fn enum_int_from(e: b::EnumIntType) -> ir::EnumIntType {
827                ir::EnumIntType {
828                    values: e
829                        .values
830                        .into_iter()
831                        .map(|v| ir::EnumIntValue {
832                            value: v.value,
833                            documentation: v.documentation,
834                        })
835                        .collect(),
836                    kind: match e.kind {
837                        b::IntKind::Int32 => ir::IntKind::Int32,
838                        b::IntKind::Int64 => ir::IntKind::Int64,
839                    },
840                }
841            }
842
843            fn enum_int_to(e: ir::EnumIntType) -> b::EnumIntType {
844                b::EnumIntType {
845                    values: e
846                        .values
847                        .into_iter()
848                        .map(|v| b::EnumIntValue {
849                            value: v.value,
850                            documentation: v.documentation,
851                        })
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                    deprecated: h.deprecated,
975                    documentation: h.documentation,
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                    deprecated: h.deprecated,
990                    documentation: h.documentation,
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                    documentation: p.documentation,
1006                    deprecated: p.deprecated,
1007                    style: p.style.map(param_style_from),
1008                    explode: p.explode,
1009                    allow_empty_value: p.allow_empty_value,
1010                    allow_reserved: p.allow_reserved,
1011                    examples: examples_from(p.examples),
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                    documentation: p.documentation,
1023                    deprecated: p.deprecated,
1024                    style: p.style.map(param_style_to),
1025                    explode: p.explode,
1026                    allow_empty_value: p.allow_empty_value,
1027                    allow_reserved: p.allow_reserved,
1028                    examples: examples_to(p.examples),
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                    documentation: b_.documentation,
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                    documentation: b_.documentation,
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                    documentation: r.documentation,
1126                    links: links_from(r.links),
1127                    extensions: extensions_from(r.extensions),
1128                }
1129            }
1130
1131            fn response_to(r: ir::Response) -> b::Response {
1132                b::Response {
1133                    status: match r.status {
1134                        ir::ResponseStatus::Explicit { code } => b::ResponseStatus::Explicit(code),
1135                        ir::ResponseStatus::Default => b::ResponseStatus::Default,
1136                        ir::ResponseStatus::Range { class } => b::ResponseStatus::Range(class),
1137                    },
1138                    content: r.content.into_iter().map(body_content_to).collect(),
1139                    headers: r
1140                        .headers
1141                        .into_iter()
1142                        .map(|(k, v)| (k, header_to(v)))
1143                        .collect(),
1144                    documentation: r.documentation,
1145                    links: links_to(r.links),
1146                    extensions: extensions_to(r.extensions),
1147                }
1148            }
1149
1150            fn operation_from(op: b::Operation) -> Result<ir::Operation, BindgenError> {
1151                Ok(ir::Operation {
1152                    id: op.id,
1153                    original_id: op.original_id,
1154                    method: http_method_from(op.method),
1155                    path_template: op.path_template,
1156                    path_params: op.path_params.into_iter().map(parameter_from).collect(),
1157                    query_params: op.query_params.into_iter().map(parameter_from).collect(),
1158                    header_params: op.header_params.into_iter().map(parameter_from).collect(),
1159                    cookie_params: op.cookie_params.into_iter().map(parameter_from).collect(),
1160                    querystring_params: op
1161                        .querystring_params
1162                        .into_iter()
1163                        .map(parameter_from)
1164                        .collect(),
1165                    request_body: op.request_body.map(body_from),
1166                    responses: op.responses.into_iter().map(response_from).collect(),
1167                    security: op
1168                        .security
1169                        .into_iter()
1170                        .map(|s| ir::SecurityRequirement {
1171                            scheme_id: s.scheme_id,
1172                            scopes: s.scopes,
1173                        })
1174                        .collect(),
1175                    tags: op.tags,
1176                    documentation: op.documentation,
1177                    deprecated: op.deprecated,
1178                    extensions: op.extensions,
1179                    external_docs: op.external_docs.map(external_docs_from),
1180                    servers: op.servers.into_iter().map(server_from).collect(),
1181                    callbacks: op.callbacks.into_iter().map(callback_from).collect(),
1182                    location: op.location.map(loc_from),
1183                })
1184            }
1185
1186            fn operation_to(op: ir::Operation) -> b::Operation {
1187                b::Operation {
1188                    id: op.id,
1189                    original_id: op.original_id,
1190                    method: http_method_to(op.method),
1191                    path_template: op.path_template,
1192                    path_params: op.path_params.into_iter().map(parameter_to).collect(),
1193                    query_params: op.query_params.into_iter().map(parameter_to).collect(),
1194                    header_params: op.header_params.into_iter().map(parameter_to).collect(),
1195                    cookie_params: op.cookie_params.into_iter().map(parameter_to).collect(),
1196                    querystring_params: op
1197                        .querystring_params
1198                        .into_iter()
1199                        .map(parameter_to)
1200                        .collect(),
1201                    request_body: op.request_body.map(body_to),
1202                    responses: op.responses.into_iter().map(response_to).collect(),
1203                    security: op
1204                        .security
1205                        .into_iter()
1206                        .map(|s| b::SecurityRequirement {
1207                            scheme_id: s.scheme_id,
1208                            scopes: s.scopes,
1209                        })
1210                        .collect(),
1211                    tags: op.tags,
1212                    documentation: op.documentation,
1213                    deprecated: op.deprecated,
1214                    extensions: op.extensions,
1215                    external_docs: op.external_docs.map(external_docs_to),
1216                    servers: op.servers.into_iter().map(server_to).collect(),
1217                    callbacks: op.callbacks.into_iter().map(callback_to).collect(),
1218                    location: op.location.map(loc_to),
1219                }
1220            }
1221
1222            // -------------------------------------------------------------
1223            // Security
1224            // -------------------------------------------------------------
1225
1226            fn security_scheme_from(s: b::SecurityScheme) -> ir::SecurityScheme {
1227                ir::SecurityScheme {
1228                    id: s.id,
1229                    kind: match s.kind {
1230                        b::SecuritySchemeKind::ApiKey(k) => {
1231                            ir::SecuritySchemeKind::ApiKey(ir::ApiKeyScheme {
1232                                name: k.name,
1233                                location: match k.location {
1234                                    b::ApiKeyLocation::Header => ir::ApiKeyLocation::Header,
1235                                    b::ApiKeyLocation::Query => ir::ApiKeyLocation::Query,
1236                                    b::ApiKeyLocation::Cookie => ir::ApiKeyLocation::Cookie,
1237                                },
1238                            })
1239                        }
1240                        b::SecuritySchemeKind::HttpBasic => ir::SecuritySchemeKind::HttpBasic,
1241                        b::SecuritySchemeKind::HttpBearer(f) => {
1242                            ir::SecuritySchemeKind::HttpBearer { bearer_format: f }
1243                        }
1244                        b::SecuritySchemeKind::MutualTls => ir::SecuritySchemeKind::MutualTls,
1245                        b::SecuritySchemeKind::Oauth2(o) => {
1246                            ir::SecuritySchemeKind::Oauth2(ir::OAuth2Scheme {
1247                                flows: o.flows.into_iter().map(oauth2_flow_from).collect(),
1248                            })
1249                        }
1250                        b::SecuritySchemeKind::OpenIdConnect(u) => {
1251                            ir::SecuritySchemeKind::OpenIdConnect { url: u }
1252                        }
1253                    },
1254                    documentation: s.documentation,
1255                    deprecated: s.deprecated,
1256                    extensions: extensions_from(s.extensions),
1257                }
1258            }
1259
1260            fn security_scheme_to(s: ir::SecurityScheme) -> b::SecurityScheme {
1261                b::SecurityScheme {
1262                    id: s.id,
1263                    kind: match s.kind {
1264                        ir::SecuritySchemeKind::ApiKey(k) => {
1265                            b::SecuritySchemeKind::ApiKey(b::ApiKeyScheme {
1266                                name: k.name,
1267                                location: match k.location {
1268                                    ir::ApiKeyLocation::Header => b::ApiKeyLocation::Header,
1269                                    ir::ApiKeyLocation::Query => b::ApiKeyLocation::Query,
1270                                    ir::ApiKeyLocation::Cookie => b::ApiKeyLocation::Cookie,
1271                                },
1272                            })
1273                        }
1274                        ir::SecuritySchemeKind::HttpBasic => b::SecuritySchemeKind::HttpBasic,
1275                        ir::SecuritySchemeKind::HttpBearer { bearer_format } => {
1276                            b::SecuritySchemeKind::HttpBearer(bearer_format)
1277                        }
1278                        ir::SecuritySchemeKind::MutualTls => b::SecuritySchemeKind::MutualTls,
1279                        ir::SecuritySchemeKind::Oauth2(o) => {
1280                            b::SecuritySchemeKind::Oauth2(b::Oauth2Scheme {
1281                                flows: o.flows.into_iter().map(oauth2_flow_to).collect(),
1282                            })
1283                        }
1284                        ir::SecuritySchemeKind::OpenIdConnect { url } => {
1285                            b::SecuritySchemeKind::OpenIdConnect(url)
1286                        }
1287                    },
1288                    documentation: s.documentation,
1289                    deprecated: s.deprecated,
1290                    extensions: extensions_to(s.extensions),
1291                }
1292            }
1293
1294            fn oauth2_flow_from(f: b::Oauth2Flow) -> ir::OAuth2Flow {
1295                ir::OAuth2Flow {
1296                    kind: match f.kind {
1297                        b::Oauth2FlowKind::Implicit => ir::OAuth2FlowKind::Implicit,
1298                        b::Oauth2FlowKind::Password => ir::OAuth2FlowKind::Password,
1299                        b::Oauth2FlowKind::ClientCredentials => {
1300                            ir::OAuth2FlowKind::ClientCredentials
1301                        }
1302                        b::Oauth2FlowKind::AuthorizationCode => {
1303                            ir::OAuth2FlowKind::AuthorizationCode
1304                        }
1305                    },
1306                    authorization_url: f.authorization_url,
1307                    token_url: f.token_url,
1308                    refresh_url: f.refresh_url,
1309                    scopes: f.scopes,
1310                    extensions: extensions_from(f.extensions),
1311                }
1312            }
1313
1314            fn oauth2_flow_to(f: ir::OAuth2Flow) -> b::Oauth2Flow {
1315                b::Oauth2Flow {
1316                    kind: match f.kind {
1317                        ir::OAuth2FlowKind::Implicit => b::Oauth2FlowKind::Implicit,
1318                        ir::OAuth2FlowKind::Password => b::Oauth2FlowKind::Password,
1319                        ir::OAuth2FlowKind::ClientCredentials => {
1320                            b::Oauth2FlowKind::ClientCredentials
1321                        }
1322                        ir::OAuth2FlowKind::AuthorizationCode => {
1323                            b::Oauth2FlowKind::AuthorizationCode
1324                        }
1325                    },
1326                    authorization_url: f.authorization_url,
1327                    token_url: f.token_url,
1328                    refresh_url: f.refresh_url,
1329                    scopes: f.scopes,
1330                    extensions: extensions_to(f.extensions),
1331                }
1332            }
1333        }
1334    };
1335}
1336
1337define_world_conversions!(transformer, transformer);
1338define_world_conversions!(generator, generator);
1339
1340#[cfg(test)]
1341mod tests {
1342    use super::*;
1343    use forge_ir::proptest_util::small_ir;
1344    use proptest::prelude::*;
1345
1346    proptest! {
1347        /// Roundtrip: forge_ir → wit (transformer) → forge_ir is identity.
1348        #[test]
1349        fn roundtrip_transformer(ir in small_ir()) {
1350            let wit = transformer::ir_to_wit(ir.clone());
1351            let back = transformer::ir_from_wit(wit).unwrap();
1352            prop_assert_eq!(ir, back);
1353        }
1354
1355        /// Roundtrip: forge_ir → wit (generator) → forge_ir is identity.
1356        #[test]
1357        fn roundtrip_generator(ir in small_ir()) {
1358            let wit = generator::ir_to_wit(ir.clone());
1359            let back = generator::ir_from_wit(wit).unwrap();
1360            prop_assert_eq!(ir, back);
1361        }
1362    }
1363}