Skip to main content

lenso_contract_codegen/
lib.rs

1//! Portable Capability Descriptor validation and binding generation.
2//!
3//! A locked Descriptor and its package-local JSON Schemas are the portable
4//! cross-language authority. They may be derived from compiled source types,
5//! but every backend consumes the same checked snapshot. This crate deliberately
6//! lives above the Kernel: it checks evolution rules and emits deterministic
7//! Rust and TypeScript artifacts before an App is booted.
8
9use std::{
10    collections::{BTreeMap, BTreeSet, HashSet},
11    error::Error,
12    fmt,
13    fmt::Write as _,
14    fs,
15    path::{Path, PathBuf},
16};
17
18use regex::Regex;
19use serde_json::{Map, Value};
20use sha2::{Digest as _, Sha256};
21
22mod browser;
23mod ir;
24mod rust_debug;
25mod source;
26mod wit;
27
28pub use browser::generate_browser_request_client;
29pub use source::{check_source_snapshot, write_source_snapshot};
30
31use ir::{ContractIr, ErrorVariantIr, FieldIr, ObjectAdditionalIr, OperationIr, TypeIr};
32
33const GENERATED_HEADER: &str = "// @generated by lenso-contract-codegen; do not edit.\n";
34const TYPESCRIPT_HEADER: &str = "/* @generated by lenso-contract-codegen; do not edit. */\n";
35const MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
36
37/// A parsed Descriptor `SemVer`.
38#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
39struct Version(semver::Version);
40
41impl Version {
42    fn parse(value: &str) -> Result<Self, CodegenError> {
43        semver::Version::parse(value)
44            .map(Self)
45            .map_err(|error| CodegenError::InvalidDescriptor {
46                detail: format!("Descriptor version `{value}` is not valid SemVer: {error}"),
47            })
48    }
49
50    const fn major(&self) -> u64 {
51        self.0.major
52    }
53
54    const fn minor(&self) -> u64 {
55        self.0.minor
56    }
57}
58
59fn parse_numeric_component(value: &str) -> Option<u64> {
60    (!value.is_empty() && (value == "0" || !value.starts_with('0')))
61        .then(|| value.parse().ok())
62        .flatten()
63}
64
65#[derive(Clone, Debug)]
66struct Operation {
67    name: String,
68    interaction: String,
69    request_schema: Value,
70    response_schema: Value,
71    domain_error_schema: Value,
72}
73
74fn contract_ir(descriptor: &Descriptor) -> ContractIr {
75    ContractIr {
76        capability_id: descriptor.capability_id.clone(),
77        version: descriptor.version.clone(),
78        descriptor_digest: descriptor.digest.clone(),
79        portable: descriptor.portable,
80        cross_lane_transfer: descriptor.cross_lane_transfer,
81        operations: descriptor
82            .operations
83            .iter()
84            .map(|operation| OperationIr {
85                name: operation.name.clone(),
86                interaction: operation.interaction.clone(),
87                request: type_ir_from_schema(&operation.request_schema),
88                response: type_ir_from_schema(&operation.response_schema),
89                domain_errors: error_variant_ir_definitions(&operation.domain_error_schema),
90            })
91            .collect(),
92    }
93}
94
95fn type_ir_from_schema(schema: &Value) -> TypeIr {
96    if let Some(types) = schema.get("type").and_then(Value::as_array) {
97        let non_null_types = types
98            .iter()
99            .filter(|schema_type| *schema_type != "null")
100            .cloned()
101            .collect::<Vec<_>>();
102        if non_null_types.is_empty() {
103            return TypeIr::Null;
104        }
105        if non_null_types.len() == 1 {
106            let mut narrowed = schema.as_object().cloned().unwrap_or_default();
107            narrowed.insert("type".to_owned(), non_null_types[0].clone());
108            let base = type_ir_non_null(&Value::Object(narrowed));
109            return if non_null_types.len() == types.len() {
110                base
111            } else {
112                TypeIr::Nullable(Box::new(base))
113            };
114        }
115    }
116    type_ir_non_null(schema)
117}
118
119fn type_ir_non_null(schema: &Value) -> TypeIr {
120    let Some(object) = schema.as_object() else {
121        return TypeIr::Any;
122    };
123    if let Some(values) = object.get("enum").and_then(Value::as_array) {
124        return TypeIr::Enum {
125            name: schema_declared_type_name(object),
126            values: values
127                .iter()
128                .filter_map(Value::as_str)
129                .map(ToOwned::to_owned)
130                .collect(),
131        };
132    }
133    if let Some(schema_type) = object.get("type").and_then(Value::as_str) {
134        return match schema_type {
135            "object" => {
136                let required = required_fields(schema);
137                let mut properties = object
138                    .get("properties")
139                    .and_then(Value::as_object)
140                    .into_iter()
141                    .flatten()
142                    .collect::<Vec<_>>();
143                // Projection order is intentionally independent from JSON map
144                // insertion order. Source authoring preserves declaration order
145                // for locked snapshots; language backends retain the historical
146                // canonical name order.
147                properties.sort_unstable_by_key(|(name, _)| *name);
148                let fields = properties
149                    .into_iter()
150                    .map(|(name, schema)| {
151                        let ty = type_ir_from_schema(schema);
152                        let ty = if name.ends_with("_json") && ty == TypeIr::String {
153                            TypeIr::RawJson
154                        } else {
155                            ty
156                        };
157                        FieldIr {
158                            name: name.clone(),
159                            required: required.contains(name),
160                            sensitive: schema
161                                .get("x-lenso-sensitive")
162                                .and_then(Value::as_bool)
163                                .unwrap_or(false),
164                            ty,
165                        }
166                    })
167                    .collect();
168                let additional = match object.get("additionalProperties") {
169                    Some(Value::Bool(false)) => ObjectAdditionalIr::Closed,
170                    Some(Value::Bool(true)) | None => ObjectAdditionalIr::Any,
171                    Some(schema) => {
172                        ObjectAdditionalIr::Typed(Box::new(type_ir_from_schema(schema)))
173                    }
174                };
175                TypeIr::Object {
176                    name: schema_declared_type_name(object),
177                    fields,
178                    additional,
179                }
180            }
181            "array" => object.get("items").map_or(TypeIr::Any, |items| {
182                TypeIr::Array(Box::new(type_ir_from_schema(items)))
183            }),
184            "string" => match object.get("format").and_then(Value::as_str) {
185                Some("int64") => TypeIr::Int64,
186                Some("uint64") => TypeIr::Uint64,
187                Some("byte") => TypeIr::Bytes,
188                Some("date-time") => TypeIr::Timestamp,
189                Some("duration") => TypeIr::Duration,
190                _ => TypeIr::String,
191            },
192            "integer" => TypeIr::Integer,
193            "number" => TypeIr::Number,
194            "boolean" => TypeIr::Boolean,
195            "null" => TypeIr::Null,
196            _ => TypeIr::Any,
197        };
198    }
199    TypeIr::Any
200}
201
202fn schema_declared_type_name(schema: &Map<String, Value>) -> Option<String> {
203    schema
204        .get("title")
205        .and_then(Value::as_str)
206        .filter(|title| !title.is_empty())
207        .map(pascal_case)
208}
209
210/// A validated, self-contained portable Capability Descriptor.
211#[derive(Clone, Debug)]
212pub struct Descriptor {
213    capability_id: String,
214    capability_major: u64,
215    version: String,
216    digest: String,
217    parsed_version: Version,
218    portable: bool,
219    cross_lane_transfer: bool,
220    operations: Vec<Operation>,
221}
222
223impl Descriptor {
224    /// Returns the stable Capability series identity, including its major.
225    #[must_use]
226    pub fn capability_id(&self) -> &str {
227        &self.capability_id
228    }
229
230    /// Returns the major encoded in the Capability identity.
231    #[must_use]
232    pub const fn capability_major(&self) -> u64 {
233        self.capability_major
234    }
235
236    /// Returns the exact Descriptor `SemVer` selected for generation.
237    #[must_use]
238    pub fn version(&self) -> &str {
239        &self.version
240    }
241
242    /// Returns the canonical digest of the resolved Descriptor and Schemas.
243    #[must_use]
244    pub fn descriptor_digest(&self) -> &str {
245        &self.digest
246    }
247
248    /// Returns whether the Capability is intended to cross Runtime Adapters.
249    #[must_use]
250    pub const fn portable(&self) -> bool {
251        self.portable
252    }
253
254    /// Returns whether generated native values support transfer across Execution Lanes.
255    #[must_use]
256    pub const fn cross_lane_transfer(&self) -> bool {
257        self.cross_lane_transfer
258    }
259
260    /// Returns the stable Operation names in deterministic lexical order.
261    #[must_use]
262    pub fn operation_names(&self) -> Vec<&str> {
263        self.operations
264            .iter()
265            .map(|operation| operation.name.as_str())
266            .collect()
267    }
268}
269
270/// Metadata embedded in every generated language artifact.
271#[derive(Clone, Debug, Eq, PartialEq)]
272pub struct GeneratedMetadata {
273    /// Stable `namespace.name@major` identity.
274    pub capability_id: String,
275    /// Exact Descriptor `SemVer`.
276    pub descriptor_version: String,
277    /// Canonical SHA-256 digest of the resolved Descriptor and Schemas.
278    pub descriptor_digest: String,
279    /// Whether the source Descriptor is portable.
280    pub portable: bool,
281    /// Whether generated native values may transfer across Execution Lanes.
282    pub cross_lane_transfer: bool,
283}
284
285/// One language projection emitted from a portable Capability contract.
286#[derive(Clone, Copy, Debug, Eq, PartialEq)]
287pub enum ProjectionLanguage {
288    /// Native Rust value, consumer, and provider bindings.
289    Rust,
290    /// TypeScript value, consumer, and provider bindings.
291    TypeScript,
292    /// WebAssembly Component Model WIT provider and consumer worlds.
293    Wit,
294    /// Rust bindings plus the generated byte-Adapter Capability codec.
295    RustRuntime,
296    /// Portable Rust values and a typed Process Plugin dependency client.
297    RustPlugin,
298}
299
300/// One independently generated language projection.
301#[derive(Clone, Debug, Eq, PartialEq)]
302pub struct GeneratedProjection {
303    /// Metadata embedded in the generated projection.
304    pub metadata: GeneratedMetadata,
305    /// Selected target language.
306    pub language: ProjectionLanguage,
307    /// Generated provider, client, value, and error bindings.
308    pub source: String,
309}
310
311/// Compatibility aggregate for callers that intentionally generate both projections.
312#[derive(Clone, Debug, Eq, PartialEq)]
313pub struct GeneratedArtifacts {
314    /// Metadata shared by all generated languages.
315    pub metadata: GeneratedMetadata,
316    /// Rust provider, client, value, and error bindings.
317    pub rust: String,
318    /// TypeScript provider, client, value, and error bindings.
319    pub typescript: String,
320}
321
322/// Errors produced while parsing, validating, or generating a contract.
323#[derive(Debug)]
324pub enum CodegenError {
325    /// A file could not be read.
326    Io {
327        path: PathBuf,
328        source: std::io::Error,
329    },
330    /// Descriptor or Schema data is malformed.
331    InvalidDescriptor { detail: String },
332    /// A Schema cannot be used by the portable value profile.
333    UnsupportedSchema { path: PathBuf, detail: String },
334    /// A Descriptor interaction is not supported by the generated language bindings.
335    UnsupportedInteraction {
336        operation: String,
337        interaction: String,
338    },
339    /// A portable JSON Schema shape has no exact Component Model representation.
340    UnsupportedWit { detail: String },
341    /// A JSON value would be lossy when represented by JavaScript numbers.
342    InvalidPortableValue { path: String, detail: String },
343    /// A checked-in generated artifact differs from its source.
344    GeneratedArtifactDrift { path: PathBuf },
345}
346
347impl fmt::Display for CodegenError {
348    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
349        match self {
350            Self::Io { path, source } => write!(formatter, "{}: {source}", path.display()),
351            Self::InvalidDescriptor { detail } => {
352                write!(formatter, "invalid Descriptor: {detail}")
353            }
354            Self::UnsupportedSchema { path, detail } => {
355                write!(formatter, "unsupported Schema {}: {detail}", path.display())
356            }
357            Self::UnsupportedInteraction {
358                operation,
359                interaction,
360            } => write!(
361                formatter,
362                "Operation `{operation}` uses unsupported interaction `{interaction}`"
363            ),
364            Self::UnsupportedWit { detail } => {
365                write!(formatter, "unsupported WIT projection: {detail}")
366            }
367            Self::InvalidPortableValue { path, detail } => {
368                write!(formatter, "invalid portable value at `{path}`: {detail}")
369            }
370            Self::GeneratedArtifactDrift { path } => {
371                write!(formatter, "generated artifact is stale: {}", path.display())
372            }
373        }
374    }
375}
376
377impl Error for CodegenError {
378    fn source(&self) -> Option<&(dyn Error + 'static)> {
379        match self {
380            Self::Io { source, .. } => Some(source),
381            _ => None,
382        }
383    }
384}
385
386/// Reasons why a Descriptor evolution is not safe for an existing Capability
387/// series.
388#[derive(Clone, Debug, Eq, PartialEq)]
389pub enum CompatibilityError {
390    /// The two files do not describe the same Capability series.
391    IdentityChanged { from: String, to: String },
392    /// The new Descriptor is not newer than the old one.
393    VersionNotAdvanced { from: String, to: String },
394    /// The change is not allowed by the old series and requires a new major.
395    BreakingChanges { changes: Vec<String> },
396}
397
398impl fmt::Display for CompatibilityError {
399    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
400        match self {
401            Self::IdentityChanged { from, to } => {
402                write!(
403                    formatter,
404                    "Capability identity changed from `{from}` to `{to}`"
405                )
406            }
407            Self::VersionNotAdvanced { from, to } => {
408                write!(
409                    formatter,
410                    "Descriptor version must advance from `{from}` to `{to}`"
411                )
412            }
413            Self::BreakingChanges { changes } => write!(
414                formatter,
415                "breaking Descriptor changes require a new major: {}",
416                changes.join("; ")
417            ),
418        }
419    }
420}
421
422impl Error for CompatibilityError {}
423
424/// Reads and validates one Descriptor, resolving package-local `$ref` files.
425#[allow(clippy::too_many_lines)]
426pub fn load_descriptor(path: &Path) -> Result<Descriptor, CodegenError> {
427    let descriptor_path = canonical_path(path)?;
428    let package_root = descriptor_path
429        .parent()
430        .unwrap_or_else(|| Path::new("."))
431        .to_path_buf();
432    let descriptor_value = read_json(&descriptor_path)?;
433    let object = descriptor_value
434        .as_object()
435        .ok_or_else(|| CodegenError::InvalidDescriptor {
436            detail: "the Descriptor root must be an object".to_owned(),
437        })?;
438
439    let capability_id = required_string(object, "id")?;
440    let (identity, identity_major) = capability_identity(&capability_id)?;
441    let version = required_string(object, "version")?;
442    let parsed_version = Version::parse(&version)?;
443    let portable = object
444        .get("portable")
445        .and_then(Value::as_bool)
446        .unwrap_or(false);
447    let cross_lane_transfer = object
448        .get("cross_lane_transfer")
449        .and_then(Value::as_bool)
450        .unwrap_or(false);
451    let operation_values = object
452        .get("operations")
453        .and_then(Value::as_array)
454        .ok_or_else(|| CodegenError::InvalidDescriptor {
455            detail: "`operations` must be an array".to_owned(),
456        })?;
457    if operation_values.is_empty() {
458        return Err(CodegenError::InvalidDescriptor {
459            detail: "a Descriptor must declare at least one Operation".to_owned(),
460        });
461    }
462
463    let mut operations = Vec::with_capacity(operation_values.len());
464    let mut operation_names = BTreeSet::new();
465    let mut generated_operation_names = BTreeSet::new();
466    let mut generated_type_names = BTreeSet::new();
467    let mut generated_client_method_names = BTreeSet::from([
468        "new".to_owned(),
469        "from_context".to_owned(),
470        "from_dependencies".to_owned(),
471    ]);
472    let capability_name = identity
473        .rsplit('.')
474        .next()
475        .map_or_else(|| "Capability".to_owned(), pascal_case);
476    for operation_value in operation_values {
477        let operation =
478            operation_value
479                .as_object()
480                .ok_or_else(|| CodegenError::InvalidDescriptor {
481                    detail: "each Operation must be an object".to_owned(),
482                })?;
483        let name = required_string(operation, "name")?;
484        if !is_identifier(&name) || !is_rust_member_name(&name) {
485            return Err(CodegenError::InvalidDescriptor {
486                detail: format!(
487                    "Operation name `{name}` must start with a letter or `_` and contain only letters, digits, `_`, or `-`"
488                ),
489            });
490        }
491        if !operation_names.insert(name.clone()) {
492            return Err(CodegenError::InvalidDescriptor {
493                detail: format!("Operation `{name}` is declared more than once"),
494            });
495        }
496        for generated_name in [rust_field_name(&name), pascal_case(&name)] {
497            if !generated_operation_names.insert(generated_name.clone()) {
498                return Err(CodegenError::InvalidDescriptor {
499                    detail: format!(
500                        "Operation name `{name}` collides after code generation as `{generated_name}`"
501                    ),
502                });
503            }
504        }
505        let operation_name = pascal_case(&name);
506        let mut generated_names = vec![
507            format!("{operation_name}Request"),
508            format!("{operation_name}Response"),
509            format!("{operation_name}Error"),
510            format!("{operation_name}InvocationError"),
511            format!("{operation_name}Result"),
512            format!("{capability_name}{operation_name}"),
513        ];
514        generated_names.push(if operation_values.len() == 1 {
515            format!("{capability_name}InvocationError")
516        } else {
517            format!("{capability_name}{operation_name}InvocationError")
518        });
519        for generated_name in generated_names {
520            if !is_generated_type_name(&generated_name)
521                || !generated_type_names.insert(generated_name.clone())
522            {
523                return Err(CodegenError::InvalidDescriptor {
524                    detail: format!(
525                        "Operation name `{name}` collides after type generation as `{generated_name}`"
526                    ),
527                });
528            }
529        }
530        let interaction = required_string(operation, "interaction")?;
531        if !matches!(interaction.as_str(), "request" | "stream" | "event") {
532            return Err(CodegenError::InvalidDescriptor {
533                detail: format!("Operation `{name}` has unsupported interaction `{interaction}`"),
534            });
535        }
536        if matches!(interaction.as_str(), "request" | "stream" | "event") {
537            let client_method_name = rust_field_name(&name);
538            for generated_name in [
539                client_method_name.clone(),
540                format!("{client_method_name}_with_context"),
541            ] {
542                if !generated_client_method_names.insert(generated_name.clone()) {
543                    return Err(CodegenError::InvalidDescriptor {
544                        detail: format!(
545                            "Operation name `{name}` collides with the generated Client API as `{generated_name}`"
546                        ),
547                    });
548                }
549            }
550        }
551        let request_schema_path =
552            schema_path(&descriptor_path, operation, "request_schema", &name)?;
553        let response_schema_path =
554            schema_path(&descriptor_path, operation, "response_schema", &name)?;
555        let domain_error_schema_path =
556            schema_path(&descriptor_path, operation, "domain_error_schema", &name)?;
557        let mut ref_stack = Vec::new();
558        let request_source = read_json(&request_schema_path)?;
559        let mut request_schema = resolve_refs(
560            &request_source,
561            &request_source,
562            &request_schema_path,
563            &package_root,
564            &mut ref_stack,
565        )?;
566        normalize_nullable_unions(&mut request_schema);
567        validate_schema_profile(&request_schema, &request_schema_path)?;
568        validate_value_generation_schema(&request_schema, &request_schema_path)?;
569        let mut ref_stack = Vec::new();
570        let response_source = read_json(&response_schema_path)?;
571        let mut response_schema = resolve_refs(
572            &response_source,
573            &response_source,
574            &response_schema_path,
575            &package_root,
576            &mut ref_stack,
577        )?;
578        normalize_nullable_unions(&mut response_schema);
579        validate_schema_profile(&response_schema, &response_schema_path)?;
580        validate_value_generation_schema(&response_schema, &response_schema_path)?;
581        let mut ref_stack = Vec::new();
582        let domain_error_source = read_json(&domain_error_schema_path)?;
583        let mut domain_error_schema = resolve_refs(
584            &domain_error_source,
585            &domain_error_source,
586            &domain_error_schema_path,
587            &package_root,
588            &mut ref_stack,
589        )?;
590        normalize_nullable_unions(&mut domain_error_schema);
591        validate_schema_profile(&domain_error_schema, &domain_error_schema_path)?;
592        validate_domain_error_schema(&domain_error_schema, &domain_error_schema_path)?;
593        operations.push(Operation {
594            name,
595            interaction,
596            request_schema,
597            response_schema,
598            domain_error_schema,
599        });
600    }
601    operations.sort_by(|left, right| left.name.cmp(&right.name));
602
603    let capability_id = format!("{identity}@{identity_major}");
604    let descriptor_digest = resolved_descriptor_digest(
605        &capability_id,
606        &version,
607        portable,
608        cross_lane_transfer,
609        &operations,
610    );
611    Ok(Descriptor {
612        capability_id,
613        capability_major: identity_major,
614        version,
615        digest: descriptor_digest,
616        parsed_version,
617        portable,
618        cross_lane_transfer,
619        operations,
620    })
621}
622
623fn resolved_descriptor_digest(
624    capability_id: &str,
625    version: &str,
626    portable: bool,
627    cross_lane_transfer: bool,
628    operations: &[Operation],
629) -> String {
630    let operations = operations
631        .iter()
632        .map(|operation| {
633            serde_json::json!({
634                "name": operation.name,
635                "interaction": operation.interaction,
636                "request_schema": operation.request_schema,
637                "response_schema": operation.response_schema,
638                "domain_error_schema": operation.domain_error_schema,
639            })
640        })
641        .collect::<Vec<_>>();
642    let value = serde_json::json!({
643        "capability_id": capability_id,
644        "version": version,
645        "portable": portable,
646        "cross_lane_transfer": cross_lane_transfer,
647        "operations": operations,
648    });
649    let canonical = canonicalize_json(&value);
650    format!("sha256:{:x}", Sha256::digest(canonical.as_bytes()))
651}
652
653fn canonicalize_json(value: &Value) -> String {
654    match value {
655        Value::Array(values) => format!(
656            "[{}]",
657            values
658                .iter()
659                .map(canonicalize_json)
660                .collect::<Vec<_>>()
661                .join(",")
662        ),
663        Value::Object(object) => {
664            let mut keys = object.keys().collect::<Vec<_>>();
665            keys.sort();
666            format!(
667                "{{{}}}",
668                keys.into_iter()
669                    .map(|key| format!(
670                        "{}:{}",
671                        serde_json::to_string(key).expect("object keys serialize"),
672                        canonicalize_json(&object[key])
673                    ))
674                    .collect::<Vec<_>>()
675                    .join(",")
676            )
677        }
678        _ => serde_json::to_string(value).expect("JSON values serialize"),
679    }
680}
681
682fn required_string(object: &Map<String, Value>, key: &str) -> Result<String, CodegenError> {
683    object
684        .get(key)
685        .and_then(Value::as_str)
686        .filter(|value| !value.is_empty())
687        .map(ToOwned::to_owned)
688        .ok_or_else(|| CodegenError::InvalidDescriptor {
689            detail: format!("`{key}` must be a non-empty string"),
690        })
691}
692
693fn capability_identity(value: &str) -> Result<(String, u64), CodegenError> {
694    let Some((identity, major)) = value.rsplit_once('@') else {
695        return Err(CodegenError::InvalidDescriptor {
696            detail: format!("Capability id `{value}` must use `namespace.name@major`"),
697        });
698    };
699    let generated_name = identity
700        .rsplit('.')
701        .next()
702        .map(pascal_case)
703        .unwrap_or_default();
704    if identity.is_empty()
705        || identity.split('.').count() < 2
706        || !identity.split('.').all(is_identifier)
707        || !is_generated_type_name(&generated_name)
708    {
709        return Err(CodegenError::InvalidDescriptor {
710            detail: format!("Capability id `{value}` must use `namespace.name@major`"),
711        });
712    }
713    let Some(major) = parse_numeric_component(major) else {
714        return Err(CodegenError::InvalidDescriptor {
715            detail: format!("Capability id `{value}` has an invalid major"),
716        });
717    };
718    Ok((identity.to_owned(), major))
719}
720
721fn schema_path(
722    descriptor_path: &Path,
723    operation: &Map<String, Value>,
724    key: &str,
725    operation_name: &str,
726) -> Result<PathBuf, CodegenError> {
727    let value = required_string(operation, key).map_err(|error| match error {
728        CodegenError::InvalidDescriptor { detail } => CodegenError::InvalidDescriptor {
729            detail: format!("Operation `{operation_name}`: {detail}"),
730        },
731        other => other,
732    })?;
733    let candidate = descriptor_path
734        .parent()
735        .unwrap_or_else(|| Path::new("."))
736        .join(value);
737    let canonical = canonical_path(&candidate)?;
738    let package_root = canonical_path(descriptor_path.parent().unwrap_or_else(|| Path::new(".")))?;
739    if !canonical.starts_with(&package_root) {
740        return Err(CodegenError::UnsupportedSchema {
741            path: canonical,
742            detail: "Schema paths must remain inside the Descriptor package".to_owned(),
743        });
744    }
745    Ok(canonical)
746}
747
748fn canonical_path(path: &Path) -> Result<PathBuf, CodegenError> {
749    fs::canonicalize(path).map_err(|source| CodegenError::Io {
750        path: path.to_path_buf(),
751        source,
752    })
753}
754
755fn read_json(path: &Path) -> Result<Value, CodegenError> {
756    let source = fs::read_to_string(path).map_err(|source| CodegenError::Io {
757        path: path.to_path_buf(),
758        source,
759    })?;
760    serde_json::from_str(&source).map_err(|error| CodegenError::InvalidDescriptor {
761        detail: format!("{}: {error}", path.display()),
762    })
763}
764
765fn resolve_refs(
766    value: &Value,
767    root: &Value,
768    source_path: &Path,
769    package_root: &Path,
770    stack: &mut Vec<(PathBuf, String)>,
771) -> Result<Value, CodegenError> {
772    match value {
773        Value::Object(object) => {
774            if let Some(reference) = object.get("$ref").and_then(Value::as_str) {
775                let target_value = if reference.starts_with('#') {
776                    let fragment = reference.strip_prefix('#').unwrap_or_default();
777                    let target_path = source_path.to_path_buf();
778                    let key = (target_path.clone(), reference.to_owned());
779                    if stack.contains(&key) {
780                        return Err(CodegenError::UnsupportedSchema {
781                            path: source_path.to_path_buf(),
782                            detail: format!("cyclic local Schema `$ref` `{reference}`"),
783                        });
784                    }
785                    let target = json_pointer(root, fragment).ok_or_else(|| {
786                        CodegenError::UnsupportedSchema {
787                            path: source_path.to_path_buf(),
788                            detail: format!(
789                                "local JSON Pointer `$ref` `{reference}` was not found"
790                            ),
791                        }
792                    })?;
793                    stack.push(key);
794                    let resolved = resolve_refs(target, root, source_path, package_root, stack)?;
795                    stack.pop();
796                    resolved
797                } else {
798                    let (reference_path, fragment) =
799                        reference.split_once('#').unwrap_or((reference, ""));
800                    let target_path = source_path
801                        .parent()
802                        .unwrap_or_else(|| Path::new("."))
803                        .join(reference_path);
804                    let target_path = canonical_path(&target_path)?;
805                    if !target_path.starts_with(package_root) {
806                        return Err(CodegenError::UnsupportedSchema {
807                            path: source_path.to_path_buf(),
808                            detail: format!(
809                                "external Schema `$ref` `{reference}` leaves the Descriptor package"
810                            ),
811                        });
812                    }
813                    let key = (target_path.clone(), format!("#{fragment}"));
814                    if stack.contains(&key) {
815                        return Err(CodegenError::UnsupportedSchema {
816                            path: source_path.to_path_buf(),
817                            detail: format!(
818                                "cyclic Schema `$ref` through {}",
819                                target_path.display()
820                            ),
821                        });
822                    }
823                    let target_root = read_json(&target_path)?;
824                    let target = json_pointer(&target_root, fragment).ok_or_else(|| {
825                        CodegenError::UnsupportedSchema {
826                            path: source_path.to_path_buf(),
827                            detail: format!(
828                                "external JSON Pointer `$ref` `{reference}` was not found"
829                            ),
830                        }
831                    })?;
832                    stack.push(key);
833                    let resolved =
834                        resolve_refs(target, &target_root, &target_path, package_root, stack)?;
835                    stack.pop();
836                    resolved
837                };
838                let mut merged = target_value.as_object().cloned().ok_or_else(|| {
839                    CodegenError::UnsupportedSchema {
840                        path: source_path.to_path_buf(),
841                        detail: "a `$ref` target must resolve to an object Schema".to_owned(),
842                    }
843                })?;
844                restore_reference_type_name(&mut merged, reference);
845                for (key, child) in object {
846                    if key != "$ref" {
847                        merged.insert(
848                            key.clone(),
849                            resolve_refs(child, root, source_path, package_root, stack)?,
850                        );
851                    }
852                }
853                return Ok(Value::Object(merged));
854            }
855            let mut resolved = Map::new();
856            for (key, child) in object {
857                resolved.insert(
858                    key.clone(),
859                    resolve_refs(child, root, source_path, package_root, stack)?,
860                );
861            }
862            Ok(Value::Object(resolved))
863        }
864        Value::Array(values) => values
865            .iter()
866            .map(|child| resolve_refs(child, root, source_path, package_root, stack))
867            .collect::<Result<Vec<_>, _>>()
868            .map(Value::Array),
869        _ => Ok(value.clone()),
870    }
871}
872
873fn reference_definition_name(reference: &str) -> Option<String> {
874    let (_, fragment) = reference.rsplit_once('#')?;
875    let name = fragment.strip_prefix("/$defs/")?;
876    if name.is_empty() || name.contains('/') {
877        return None;
878    }
879    Some(name.replace("~1", "/").replace("~0", "~"))
880}
881
882fn restore_reference_type_name(schema: &mut Map<String, Value>, reference: &str) {
883    if schema.get("title").is_none()
884        && let Some(name) = reference_definition_name(reference)
885    {
886        schema.insert("title".to_owned(), Value::String(name));
887    }
888}
889
890fn json_pointer<'a>(root: &'a Value, fragment: &str) -> Option<&'a Value> {
891    if fragment.is_empty() {
892        return Some(root);
893    }
894    let pointer = fragment.strip_prefix('/')?;
895    let mut current = root;
896    for token in pointer.split('/') {
897        let token = token.replace("~1", "/").replace("~0", "~");
898        current = match current {
899            Value::Object(object) => object.get(&token)?,
900            Value::Array(array) => array.get(token.parse::<usize>().ok()?)?,
901            _ => return None,
902        };
903    }
904    Some(current)
905}
906
907fn normalize_nullable_unions(value: &mut Value) {
908    match value {
909        Value::Object(object) => {
910            for child in object.values_mut() {
911                normalize_nullable_unions(child);
912            }
913            let nullable = ["anyOf", "oneOf"].into_iter().find_map(|key| {
914                let alternatives = object.get(key)?.as_array()?;
915                if alternatives.len() != 2 {
916                    return None;
917                }
918                let null_index = alternatives.iter().position(|alternative| {
919                    alternative.get("type").and_then(Value::as_str) == Some("null")
920                })?;
921                let non_null = alternatives.get(1 - null_index)?.as_object()?.clone();
922                let schema_type = non_null
923                    .get("type")
924                    .and_then(Value::as_str)
925                    .map(ToOwned::to_owned)?;
926                Some((key, schema_type, non_null))
927            });
928            if let Some((union_key, schema_type, mut normalized)) = nullable {
929                normalized.insert(
930                    "type".to_owned(),
931                    Value::Array(vec![
932                        Value::String(schema_type),
933                        Value::String("null".to_owned()),
934                    ]),
935                );
936                for (key, child) in std::mem::take(object) {
937                    if key != union_key {
938                        normalized.entry(key).or_insert(child);
939                    }
940                }
941                *object = normalized;
942            }
943        }
944        Value::Array(values) => {
945            for child in values {
946                normalize_nullable_unions(child);
947            }
948        }
949        _ => {}
950    }
951}
952
953const SUPPORTED_SCHEMA_KEYWORDS: &[&str] = &[
954    "$defs",
955    "additionalProperties",
956    "anyOf",
957    "const",
958    "else",
959    "enum",
960    "exclusiveMaximum",
961    "exclusiveMinimum",
962    "format",
963    "if",
964    "items",
965    "maxItems",
966    "maxLength",
967    "maxProperties",
968    "maximum",
969    "minItems",
970    "minLength",
971    "minProperties",
972    "minimum",
973    "oneOf",
974    "pattern",
975    "properties",
976    "propertyNames",
977    "required",
978    "then",
979    "type",
980    "uniqueItems",
981];
982
983const HARMLESS_SCHEMA_ANNOTATIONS: &[&str] = &[
984    "$anchor",
985    "$comment",
986    "$dynamicAnchor",
987    "$id",
988    "$schema",
989    "default",
990    "deprecated",
991    "description",
992    "examples",
993    "readOnly",
994    "title",
995    "writeOnly",
996    "x-lenso-max-container-items",
997    "x-lenso-max-depth",
998    "x-lenso-max-encoded-bytes",
999    "x-lenso-max-nodes",
1000    "x-lenso-max-string-length",
1001    "x-lenso-sensitive",
1002];
1003
1004const PORTABLE_SCHEMA_TYPES: &[&str] = &[
1005    "array", "boolean", "integer", "null", "number", "object", "string",
1006];
1007
1008const PORTABLE_STRING_FORMATS: &[&str] = &["byte", "date-time", "duration", "int64", "uint64"];
1009const MAX_PORTABLE_PATTERN_CHARS: usize = 4_096;
1010const MAX_PORTABLE_REPETITION: u32 = 10_000;
1011const PORTABLE_UNICODE_PROPERTY_ESCAPE: &str = r"\p{Letter}";
1012
1013fn validate_schema_profile(schema: &Value, source_path: &Path) -> Result<(), CodegenError> {
1014    let Some(object) = schema.as_object() else {
1015        return Err(unsupported_schema(
1016            source_path,
1017            "a JSON Schema must be an object",
1018        ));
1019    };
1020
1021    for keyword in object.keys() {
1022        if !SUPPORTED_SCHEMA_KEYWORDS.contains(&keyword.as_str())
1023            && !HARMLESS_SCHEMA_ANNOTATIONS.contains(&keyword.as_str())
1024        {
1025            return Err(unsupported_schema(
1026                source_path,
1027                format!("Schema keyword `{keyword}` is outside the portable profile"),
1028            ));
1029        }
1030    }
1031
1032    validate_schema_type_keyword(object, source_path)?;
1033    validate_schema_composition_keywords(object, source_path)?;
1034    validate_schema_scalar_keywords(object, source_path)?;
1035    validate_schema_numeric_keywords(object, source_path)?;
1036    validate_schema_children(object, source_path)
1037}
1038
1039fn validate_schema_type_keyword(
1040    object: &Map<String, Value>,
1041    source_path: &Path,
1042) -> Result<(), CodegenError> {
1043    if let Some(schema_type) = object.get("type") {
1044        match schema_type {
1045            Value::String(schema_type) => validate_schema_type(schema_type, source_path)?,
1046            Value::Array(types) if !types.is_empty() => {
1047                let mut unique = BTreeSet::new();
1048                for schema_type in types {
1049                    let Some(schema_type) = schema_type.as_str() else {
1050                        return Err(unsupported_schema(
1051                            source_path,
1052                            "Schema type unions must contain strings",
1053                        ));
1054                    };
1055                    validate_schema_type(schema_type, source_path)?;
1056                    if !unique.insert(schema_type) {
1057                        return Err(unsupported_schema(
1058                            source_path,
1059                            "Schema type unions must be unique",
1060                        ));
1061                    }
1062                }
1063            }
1064            Value::Array(_) => {
1065                return Err(unsupported_schema(
1066                    source_path,
1067                    "a Schema type union cannot be empty",
1068                ));
1069            }
1070            _ => {
1071                return Err(unsupported_schema(
1072                    source_path,
1073                    "Schema `type` must be a string or array",
1074                ));
1075            }
1076        }
1077    }
1078
1079    Ok(())
1080}
1081
1082fn validate_schema_composition_keywords(
1083    object: &Map<String, Value>,
1084    source_path: &Path,
1085) -> Result<(), CodegenError> {
1086    for keyword in ["oneOf", "anyOf"] {
1087        if let Some(value) = object.get(keyword) {
1088            let Some(alternatives) = value.as_array().filter(|values| !values.is_empty()) else {
1089                return Err(unsupported_schema(
1090                    source_path,
1091                    format!("Schema `{keyword}` must be a non-empty array of Schemas"),
1092                ));
1093            };
1094            for alternative in alternatives {
1095                validate_schema_profile(alternative, source_path)?;
1096            }
1097        }
1098    }
1099
1100    Ok(())
1101}
1102
1103fn validate_schema_scalar_keywords(
1104    object: &Map<String, Value>,
1105    source_path: &Path,
1106) -> Result<(), CodegenError> {
1107    if let Some(values) = object.get("enum")
1108        && values.as_array().is_none_or(Vec::is_empty)
1109    {
1110        return Err(unsupported_schema(
1111            source_path,
1112            "Schema `enum` must be a non-empty array",
1113        ));
1114    }
1115    if let Some(required) = object.get("required")
1116        && required
1117            .as_array()
1118            .is_none_or(|values| values.iter().any(|value| !value.is_string()))
1119    {
1120        return Err(unsupported_schema(
1121            source_path,
1122            "Schema `required` must be an array of strings",
1123        ));
1124    }
1125    if let Some(pattern) = object.get("pattern") {
1126        let Some(pattern) = pattern.as_str() else {
1127            return Err(unsupported_schema(
1128                source_path,
1129                "Schema `pattern` must be a string",
1130            ));
1131        };
1132        compile_portable_pattern(pattern).map_err(|error| {
1133            unsupported_schema(
1134                source_path,
1135                format!("Schema `pattern` is outside the portable regex subset: {error}"),
1136            )
1137        })?;
1138    }
1139    if let Some(format) = object.get("format") {
1140        let Some(format) = format.as_str() else {
1141            return Err(unsupported_schema(
1142                source_path,
1143                "Schema `format` must be a string",
1144            ));
1145        };
1146        if !PORTABLE_STRING_FORMATS.contains(&format) {
1147            return Err(unsupported_schema(
1148                source_path,
1149                format!("format `{format}` is outside the portable value profile"),
1150            ));
1151        }
1152    }
1153    if object
1154        .get("uniqueItems")
1155        .is_some_and(|value| !value.is_boolean())
1156    {
1157        return Err(unsupported_schema(
1158            source_path,
1159            "Schema `uniqueItems` must be a boolean",
1160        ));
1161    }
1162
1163    Ok(())
1164}
1165
1166fn validate_schema_numeric_keywords(
1167    object: &Map<String, Value>,
1168    source_path: &Path,
1169) -> Result<(), CodegenError> {
1170    for keyword in [
1171        "minLength",
1172        "maxLength",
1173        "minItems",
1174        "maxItems",
1175        "minProperties",
1176        "maxProperties",
1177    ] {
1178        if let Some(value) = object.get(keyword)
1179            && non_negative_safe_schema_integer(value).is_none()
1180        {
1181            return Err(unsupported_schema(
1182                source_path,
1183                format!("Schema `{keyword}` must be a non-negative safe integer"),
1184            ));
1185        }
1186    }
1187    for keyword in ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"] {
1188        if let Some(value) = object.get(keyword)
1189            && value.as_f64().is_none_or(|number| !number.is_finite())
1190        {
1191            return Err(unsupported_schema(
1192                source_path,
1193                format!("Schema `{keyword}` must be a finite number"),
1194            ));
1195        }
1196    }
1197
1198    Ok(())
1199}
1200
1201fn validate_schema_children(
1202    object: &Map<String, Value>,
1203    source_path: &Path,
1204) -> Result<(), CodegenError> {
1205    if let Some(items) = object.get("items") {
1206        validate_schema_profile(items, source_path)?;
1207    }
1208    for keyword in ["properties", "$defs"] {
1209        if let Some(value) = object.get(keyword) {
1210            let Some(schemas) = value.as_object() else {
1211                return Err(unsupported_schema(
1212                    source_path,
1213                    format!("Schema `{keyword}` must be an object of Schemas"),
1214                ));
1215            };
1216            for child in schemas.values() {
1217                validate_schema_profile(child, source_path)?;
1218            }
1219        }
1220    }
1221    if let Some(additional) = object.get("additionalProperties")
1222        && !additional.is_boolean()
1223    {
1224        validate_schema_profile(additional, source_path)?;
1225    }
1226    if let Some(property_names) = object.get("propertyNames") {
1227        validate_schema_profile(property_names, source_path)?;
1228    }
1229    if object.contains_key("then") || object.contains_key("else") {
1230        let Some(condition) = object.get("if") else {
1231            return Err(unsupported_schema(
1232                source_path,
1233                "Schema `then` and `else` require an `if` Schema",
1234            ));
1235        };
1236        validate_schema_profile(condition, source_path)?;
1237    } else if let Some(condition) = object.get("if") {
1238        validate_schema_profile(condition, source_path)?;
1239    }
1240    for keyword in ["then", "else"] {
1241        if let Some(branch) = object.get(keyword) {
1242            validate_schema_profile(branch, source_path)?;
1243        }
1244    }
1245
1246    Ok(())
1247}
1248
1249fn validate_schema_type(schema_type: &str, source_path: &Path) -> Result<(), CodegenError> {
1250    if PORTABLE_SCHEMA_TYPES.contains(&schema_type) {
1251        Ok(())
1252    } else {
1253        Err(unsupported_schema(
1254            source_path,
1255            format!("Schema type `{schema_type}` is outside the portable profile"),
1256        ))
1257    }
1258}
1259
1260/// Compiles only the regular-expression syntax whose match semantics are shared
1261/// by Rust `regex` and ECMAScript Unicode-mode `RegExp` for portable Schemas.
1262/// Engine-specific shorthand classes, internal anchors, wildcard dots,
1263/// lookarounds, backreferences, inline flags, and backtracking-unsafe
1264/// repetition shapes fail closed before either runtime uses them. Fully
1265/// anchored patterns may use the complete safe subset; start-anchored prefix
1266/// patterns are limited to fixed-shape expressions.
1267fn compile_portable_pattern(pattern: &str) -> Result<Regex, String> {
1268    if !has_portable_pattern_syntax(pattern) {
1269        return Err("pattern uses syntax outside the shared Rust/ECMAScript subset".to_owned());
1270    }
1271    Regex::new(pattern).map_err(|error| error.to_string())
1272}
1273
1274#[derive(Clone, Copy, Debug, Default)]
1275#[expect(
1276    clippy::struct_excessive_bools,
1277    reason = "these are independent regex risk facts mirrored explicitly in the TypeScript gate"
1278)]
1279struct PortablePatternAtom {
1280    can_match_empty: bool,
1281    has_variable_shape: bool,
1282    is_quantified: bool,
1283    prefix_can_match_empty: bool,
1284    contains_variable_repetition: bool,
1285    contains_alternation: bool,
1286    is_group: bool,
1287}
1288
1289#[derive(Debug)]
1290#[expect(
1291    clippy::struct_excessive_bools,
1292    reason = "branch-local and group-wide regex risk facts are independent parser state"
1293)]
1294struct PortablePatternFrame {
1295    sequence_can_match_empty: bool,
1296    earlier_branch_can_match_empty: bool,
1297    contains_variable_repetition: bool,
1298    contains_alternation: bool,
1299    branch_has_variable_shape: bool,
1300    last_atom: Option<PortablePatternAtom>,
1301}
1302
1303impl Default for PortablePatternFrame {
1304    fn default() -> Self {
1305        Self {
1306            sequence_can_match_empty: true,
1307            earlier_branch_can_match_empty: false,
1308            contains_variable_repetition: false,
1309            contains_alternation: false,
1310            branch_has_variable_shape: false,
1311            last_atom: None,
1312        }
1313    }
1314}
1315
1316#[derive(Clone, Copy, Debug)]
1317struct PortableRepetition {
1318    minimum: u32,
1319    maximum: Option<u32>,
1320}
1321
1322impl PortableRepetition {
1323    fn has_variable_extent(self) -> bool {
1324        self.maximum != Some(self.minimum)
1325    }
1326}
1327
1328#[expect(
1329    clippy::too_many_lines,
1330    reason = "keeping the single-pass scanner locally isomorphic with the TypeScript gate makes parity auditable"
1331)]
1332fn has_portable_pattern_syntax(pattern: &str) -> bool {
1333    if pattern.chars().count() > MAX_PORTABLE_PATTERN_CHARS
1334        || pattern.as_bytes().first() != Some(&b'^')
1335    {
1336        return false;
1337    }
1338
1339    let bytes = pattern.as_bytes();
1340    let mut index = 0;
1341    let mut in_class = false;
1342    let mut class_start = None;
1343    let mut frames = vec![PortablePatternFrame::default()];
1344    while index < bytes.len() {
1345        if in_class {
1346            match bytes[index] {
1347                b'\\' => {
1348                    if pattern[index..].starts_with(PORTABLE_UNICODE_PROPERTY_ESCAPE) {
1349                        index += PORTABLE_UNICODE_PROPERTY_ESCAPE.len();
1350                        continue;
1351                    }
1352                    let Some(&escaped) = bytes.get(index + 1) else {
1353                        return false;
1354                    };
1355                    if !matches!(
1356                        escaped,
1357                        b'^' | b'$'
1358                            | b'\\'
1359                            | b'.'
1360                            | b'*'
1361                            | b'+'
1362                            | b'?'
1363                            | b'('
1364                            | b')'
1365                            | b'['
1366                            | b']'
1367                            | b'{'
1368                            | b'}'
1369                            | b'|'
1370                            | b'/'
1371                            | b'-'
1372                    ) {
1373                        return false;
1374                    }
1375                    index += 2;
1376                }
1377                b']' => {
1378                    let Some(start) = class_start.take() else {
1379                        return false;
1380                    };
1381                    if matches!(&pattern[start..index], "" | "^") {
1382                        return false;
1383                    }
1384                    in_class = false;
1385                    let Some(frame) = frames.last_mut() else {
1386                        return false;
1387                    };
1388                    if !push_portable_pattern_atom(frame, PortablePatternAtom::default()) {
1389                        return false;
1390                    }
1391                    index += 1;
1392                }
1393                b'[' => return false,
1394                operator @ (b'&' | b'-' | b'~' | b'|')
1395                    if bytes.get(index + 1) == Some(&operator) =>
1396                {
1397                    return false;
1398                }
1399                byte if byte.is_ascii_control() => return false,
1400                byte if byte.is_ascii() => index += 1,
1401                _ => {
1402                    let Some(character) = pattern[index..].chars().next() else {
1403                        return false;
1404                    };
1405                    if character.is_control() {
1406                        return false;
1407                    }
1408                    index += character.len_utf8();
1409                }
1410            }
1411            continue;
1412        }
1413
1414        match bytes[index] {
1415            b'\\' => {
1416                if pattern[index..].starts_with(PORTABLE_UNICODE_PROPERTY_ESCAPE) {
1417                    index += PORTABLE_UNICODE_PROPERTY_ESCAPE.len();
1418                } else {
1419                    let Some(&escaped) = bytes.get(index + 1) else {
1420                        return false;
1421                    };
1422                    if !matches!(
1423                        escaped,
1424                        b'^' | b'$'
1425                            | b'\\'
1426                            | b'.'
1427                            | b'*'
1428                            | b'+'
1429                            | b'?'
1430                            | b'('
1431                            | b')'
1432                            | b'['
1433                            | b']'
1434                            | b'{'
1435                            | b'}'
1436                            | b'|'
1437                            | b'/'
1438                    ) {
1439                        return false;
1440                    }
1441                    index += 2;
1442                }
1443                let Some(frame) = frames.last_mut() else {
1444                    return false;
1445                };
1446                if !push_portable_pattern_atom(frame, PortablePatternAtom::default()) {
1447                    return false;
1448                }
1449            }
1450            b'[' => {
1451                in_class = true;
1452                class_start = Some(index + 1);
1453                index += 1;
1454            }
1455            b'(' => {
1456                if bytes.get(index + 1) == Some(&b'?') {
1457                    if !pattern[index..].starts_with("(?:") {
1458                        return false;
1459                    }
1460                    index += 3;
1461                } else {
1462                    index += 1;
1463                }
1464                frames.push(PortablePatternFrame::default());
1465            }
1466            b')' => {
1467                if frames.len() == 1 {
1468                    return false;
1469                }
1470                let Some(group) = frames.pop() else {
1471                    return false;
1472                };
1473                let can_match_empty =
1474                    group.earlier_branch_can_match_empty || group.sequence_can_match_empty;
1475                if can_match_empty {
1476                    return false;
1477                }
1478                let atom = PortablePatternAtom {
1479                    can_match_empty,
1480                    has_variable_shape: group.contains_variable_repetition
1481                        || group.contains_alternation,
1482                    contains_variable_repetition: group.contains_variable_repetition,
1483                    contains_alternation: group.contains_alternation,
1484                    is_group: true,
1485                    ..PortablePatternAtom::default()
1486                };
1487                let Some(parent) = frames.last_mut() else {
1488                    return false;
1489                };
1490                if !push_portable_pattern_atom(parent, atom) {
1491                    return false;
1492                }
1493                index += 1;
1494            }
1495            b'|' => {
1496                if frames.len() == 1 {
1497                    return false;
1498                }
1499                let Some(frame) = frames.last_mut() else {
1500                    return false;
1501                };
1502                frame.contains_alternation = true;
1503                frame.earlier_branch_can_match_empty |= frame.sequence_can_match_empty;
1504                frame.sequence_can_match_empty = true;
1505                frame.branch_has_variable_shape = false;
1506                frame.last_atom = None;
1507                index += 1;
1508            }
1509            b'*' | b'+' | b'?' => {
1510                let repetition = match bytes[index] {
1511                    b'*' => PortableRepetition {
1512                        minimum: 0,
1513                        maximum: None,
1514                    },
1515                    b'+' => PortableRepetition {
1516                        minimum: 1,
1517                        maximum: None,
1518                    },
1519                    b'?' => PortableRepetition {
1520                        minimum: 0,
1521                        maximum: Some(1),
1522                    },
1523                    _ => unreachable!(),
1524                };
1525                let Some(frame) = frames.last_mut() else {
1526                    return false;
1527                };
1528                if !apply_portable_repetition(frame, repetition) {
1529                    return false;
1530                }
1531                index += 1;
1532            }
1533            b'{' => {
1534                let Some(close_offset) = pattern[index + 1..].find('}') else {
1535                    return false;
1536                };
1537                let close = index + 1 + close_offset;
1538                let Some(repetition) = parse_portable_repetition(&pattern[index + 1..close]) else {
1539                    return false;
1540                };
1541                let Some(frame) = frames.last_mut() else {
1542                    return false;
1543                };
1544                if !apply_portable_repetition(frame, repetition) {
1545                    return false;
1546                }
1547                index = close + 1;
1548            }
1549            b'^' if index == 0 => {
1550                let Some(frame) = frames.last_mut() else {
1551                    return false;
1552                };
1553                frame.last_atom = None;
1554                index += 1;
1555            }
1556            b'$' if is_portable_end_anchor(pattern, index) => {
1557                let Some(frame) = frames.last_mut() else {
1558                    return false;
1559                };
1560                frame.last_atom = None;
1561                index += 1;
1562            }
1563            b']' | b'}' | b'.' | b'^' | b'$' => return false,
1564            byte if byte.is_ascii_control() => return false,
1565            byte if byte.is_ascii() => {
1566                let Some(frame) = frames.last_mut() else {
1567                    return false;
1568                };
1569                if !push_portable_pattern_atom(frame, PortablePatternAtom::default()) {
1570                    return false;
1571                }
1572                index += 1;
1573            }
1574            _ => {
1575                let Some(character) = pattern[index..].chars().next() else {
1576                    return false;
1577                };
1578                if character.is_control() {
1579                    return false;
1580                }
1581                let Some(frame) = frames.last_mut() else {
1582                    return false;
1583                };
1584                if !push_portable_pattern_atom(frame, PortablePatternAtom::default()) {
1585                    return false;
1586                }
1587                index += character.len_utf8();
1588            }
1589        }
1590    }
1591
1592    if in_class || frames.len() != 1 {
1593        return false;
1594    }
1595    let frame = &frames[0];
1596    has_portable_end_anchor(pattern)
1597        || (frame.last_atom.is_some()
1598            && !frame.contains_variable_repetition
1599            && !frame.contains_alternation)
1600}
1601
1602fn has_portable_end_anchor(pattern: &str) -> bool {
1603    let bytes = pattern.as_bytes();
1604    if bytes.last() != Some(&b'$') {
1605        return false;
1606    }
1607    bytes[..bytes.len() - 1]
1608        .iter()
1609        .rev()
1610        .take_while(|byte| **byte == b'\\')
1611        .count()
1612        % 2
1613        == 0
1614}
1615
1616fn is_portable_end_anchor(pattern: &str, index: usize) -> bool {
1617    index + 1 == pattern.len() && has_portable_end_anchor(pattern)
1618}
1619
1620fn push_portable_pattern_atom(
1621    frame: &mut PortablePatternFrame,
1622    mut atom: PortablePatternAtom,
1623) -> bool {
1624    if atom.has_variable_shape && frame.branch_has_variable_shape {
1625        return false;
1626    }
1627    atom.prefix_can_match_empty = frame.sequence_can_match_empty;
1628    frame.sequence_can_match_empty &= atom.can_match_empty;
1629    frame.branch_has_variable_shape |= atom.has_variable_shape;
1630    frame.contains_variable_repetition |= atom.contains_variable_repetition;
1631    frame.contains_alternation |= atom.contains_alternation;
1632    frame.last_atom = Some(atom);
1633    true
1634}
1635
1636fn apply_portable_repetition(
1637    frame: &mut PortablePatternFrame,
1638    repetition: PortableRepetition,
1639) -> bool {
1640    let Some(atom) = frame.last_atom.as_mut() else {
1641        return false;
1642    };
1643    if atom.is_quantified || repetition.maximum == Some(0) {
1644        return false;
1645    }
1646    if atom.is_group
1647        && (atom.can_match_empty || atom.contains_variable_repetition || atom.contains_alternation)
1648    {
1649        return false;
1650    }
1651
1652    let has_variable_extent = repetition.has_variable_extent();
1653    if has_variable_extent && frame.branch_has_variable_shape {
1654        return false;
1655    }
1656    atom.is_quantified = true;
1657    atom.has_variable_shape |= has_variable_extent;
1658    atom.can_match_empty = repetition.minimum == 0 || atom.can_match_empty;
1659    frame.sequence_can_match_empty = atom.prefix_can_match_empty && atom.can_match_empty;
1660    frame.branch_has_variable_shape |= has_variable_extent;
1661    frame.contains_variable_repetition |= has_variable_extent;
1662    true
1663}
1664
1665fn parse_portable_repetition(value: &str) -> Option<PortableRepetition> {
1666    let (minimum, maximum) = if let Some((minimum, maximum)) = value.split_once(',') {
1667        if maximum.contains(',') {
1668            return None;
1669        }
1670        let minimum = parse_portable_repetition_bound(minimum)?;
1671        let maximum = if maximum.is_empty() {
1672            None
1673        } else {
1674            Some(parse_portable_repetition_bound(maximum)?)
1675        };
1676        (minimum, maximum)
1677    } else {
1678        let minimum = parse_portable_repetition_bound(value)?;
1679        (minimum, Some(minimum))
1680    };
1681    if maximum.is_some_and(|maximum| maximum < minimum) {
1682        return None;
1683    }
1684    Some(PortableRepetition { minimum, maximum })
1685}
1686
1687fn parse_portable_repetition_bound(value: &str) -> Option<u32> {
1688    if value.is_empty()
1689        || value.len() > 1 && value.starts_with('0')
1690        || !value.bytes().all(|byte| byte.is_ascii_digit())
1691    {
1692        return None;
1693    }
1694    value
1695        .parse::<u32>()
1696        .ok()
1697        .filter(|value| *value <= MAX_PORTABLE_REPETITION)
1698}
1699
1700#[expect(
1701    clippy::cast_possible_truncation,
1702    clippy::cast_sign_loss,
1703    reason = "the finite, non-negative, integral, safe-integer checks make this conversion exact"
1704)]
1705fn non_negative_safe_schema_integer(value: &Value) -> Option<u64> {
1706    let number = value.as_f64()?;
1707    (number.is_finite()
1708        && number >= 0.0
1709        && number.fract() == 0.0
1710        && number <= 9_007_199_254_740_991.0)
1711        .then_some(number as u64)
1712}
1713
1714fn unsupported_schema(source_path: &Path, detail: impl Into<String>) -> CodegenError {
1715    CodegenError::UnsupportedSchema {
1716        path: source_path.to_path_buf(),
1717        detail: detail.into(),
1718    }
1719}
1720
1721#[allow(clippy::too_many_lines)]
1722fn validate_value_generation_schema(
1723    schema: &Value,
1724    source_path: &Path,
1725) -> Result<(), CodegenError> {
1726    let Some(object) = schema.as_object() else {
1727        return Err(CodegenError::UnsupportedSchema {
1728            path: source_path.to_path_buf(),
1729            detail: "generated value Schemas must be objects".to_owned(),
1730        });
1731    };
1732    if object.contains_key("oneOf") || object.contains_key("anyOf") {
1733        return Err(CodegenError::UnsupportedSchema {
1734            path: source_path.to_path_buf(),
1735            detail: "generated value Schemas do not support oneOf/anyOf unions".to_owned(),
1736        });
1737    }
1738    if object.contains_key("allOf") {
1739        return Err(CodegenError::UnsupportedSchema {
1740            path: source_path.to_path_buf(),
1741            detail: "generated value Schemas do not support allOf unions".to_owned(),
1742        });
1743    }
1744    if object.contains_key("const") {
1745        return Err(CodegenError::UnsupportedSchema {
1746            path: source_path.to_path_buf(),
1747            detail: "generated value Schemas do not support const fields".to_owned(),
1748        });
1749    }
1750    if let Some(types) = object.get("type").and_then(Value::as_array) {
1751        let non_null = types
1752            .iter()
1753            .filter(|schema_type| *schema_type != "null")
1754            .count();
1755        if non_null > 1 {
1756            return Err(CodegenError::UnsupportedSchema {
1757                path: source_path.to_path_buf(),
1758                detail: "generated value Schemas only support nullable type unions".to_owned(),
1759            });
1760        }
1761        let mut narrowed = object.clone();
1762        let Some(schema_type) = types.iter().find(|schema_type| *schema_type != "null") else {
1763            return Ok(());
1764        };
1765        narrowed.insert("type".to_owned(), schema_type.clone());
1766        return validate_value_generation_schema(&Value::Object(narrowed), source_path);
1767    }
1768    if let Some(values) = object.get("enum").and_then(Value::as_array) {
1769        if values.is_empty() || !values.iter().all(Value::is_string) {
1770            return Err(CodegenError::UnsupportedSchema {
1771                path: source_path.to_path_buf(),
1772                detail: "generated enum values must be non-empty strings".to_owned(),
1773            });
1774        }
1775        let mut enum_names = BTreeSet::new();
1776        for value in values.iter().filter_map(Value::as_str) {
1777            let name = pascal_case(value);
1778            if !is_generated_enum_variant(&name) || !enum_names.insert(name) {
1779                return Err(CodegenError::UnsupportedSchema {
1780                    path: source_path.to_path_buf(),
1781                    detail: "generated enum values collide after Rust variant generation"
1782                        .to_owned(),
1783                });
1784            }
1785        }
1786    }
1787    let Some(schema_type) = object.get("type").and_then(Value::as_str) else {
1788        if object.contains_key("enum") {
1789            return Ok(());
1790        }
1791        return Err(CodegenError::UnsupportedSchema {
1792            path: source_path.to_path_buf(),
1793            detail: "generated value Schemas need a supported type or string enum".to_owned(),
1794        });
1795    };
1796    match schema_type {
1797        "object" => {
1798            let properties = object
1799                .get("properties")
1800                .and_then(Value::as_object)
1801                .cloned()
1802                .unwrap_or_default();
1803            let mut rust_names = BTreeSet::new();
1804            let mut nested_type_names = BTreeSet::new();
1805            for (name, property) in &properties {
1806                if !rust_names.insert(rust_field_name(name)) {
1807                    return Err(CodegenError::UnsupportedSchema {
1808                        path: source_path.to_path_buf(),
1809                        detail: format!(
1810                            "object properties collide after Rust field normalization near `{name}`"
1811                        ),
1812                    });
1813                }
1814                if !nested_type_names.insert(pascal_case(name)) {
1815                    return Err(CodegenError::UnsupportedSchema {
1816                        path: source_path.to_path_buf(),
1817                        detail: format!(
1818                            "object properties collide after nested type-name generation near `{name}`"
1819                        ),
1820                    });
1821                }
1822                validate_value_generation_schema(property, source_path)?;
1823            }
1824            match object.get("additionalProperties") {
1825                Some(Value::Bool(false)) => {}
1826                Some(Value::Bool(true)) | None if !properties.is_empty() => {
1827                    return Err(CodegenError::UnsupportedSchema {
1828                        path: source_path.to_path_buf(),
1829                        detail: "open objects with declared properties would lose unknown fields"
1830                            .to_owned(),
1831                    });
1832                }
1833                Some(additional) if !additional.is_boolean() && !properties.is_empty() => {
1834                    return Err(CodegenError::UnsupportedSchema {
1835                        path: source_path.to_path_buf(),
1836                        detail: "objects cannot combine declared properties with a map payload"
1837                            .to_owned(),
1838                    });
1839                }
1840                Some(additional) if !additional.is_boolean() => {
1841                    validate_value_generation_schema(additional, source_path)?;
1842                }
1843                _ => {}
1844            }
1845        }
1846        "array" => {
1847            let items = object
1848                .get("items")
1849                .ok_or_else(|| CodegenError::UnsupportedSchema {
1850                    path: source_path.to_path_buf(),
1851                    detail: "generated array Schemas must declare items".to_owned(),
1852                })?;
1853            validate_value_generation_schema(items, source_path)?;
1854        }
1855        "string" | "integer" | "number" | "boolean" | "null" => {}
1856        _ => {
1857            return Err(CodegenError::UnsupportedSchema {
1858                path: source_path.to_path_buf(),
1859                detail: format!("Schema type `{schema_type}` cannot be generated"),
1860            });
1861        }
1862    }
1863    Ok(())
1864}
1865
1866fn validate_domain_error_schema(schema: &Value, source_path: &Path) -> Result<(), CodegenError> {
1867    if schema
1868        .as_object()
1869        .is_some_and(|object| object.contains_key("allOf"))
1870    {
1871        return Err(CodegenError::UnsupportedSchema {
1872            path: source_path.to_path_buf(),
1873            detail: "Domain Error Schemas do not support allOf unions".to_owned(),
1874        });
1875    }
1876    let variants = schema
1877        .get("oneOf")
1878        .and_then(Value::as_array)
1879        .filter(|variants| !variants.is_empty())
1880        .ok_or_else(|| CodegenError::UnsupportedSchema {
1881            path: source_path.to_path_buf(),
1882            detail: "Domain Error Schemas must be a non-empty oneOf union".to_owned(),
1883        })?;
1884    let mut codes = BTreeSet::new();
1885    let mut names = BTreeSet::from(["Unknown".to_owned()]);
1886    for variant in variants {
1887        if variant
1888            .as_object()
1889            .is_some_and(|object| object.contains_key("allOf"))
1890        {
1891            return Err(CodegenError::UnsupportedSchema {
1892                path: source_path.to_path_buf(),
1893                detail: "Domain Error variants do not support allOf unions".to_owned(),
1894            });
1895        }
1896        if let Some(code) = variant.get("const").and_then(Value::as_str) {
1897            let name = pascal_case(code);
1898            if !codes.insert(code.to_owned())
1899                || !names.insert(name.clone())
1900                || !is_generated_enum_variant(&name)
1901            {
1902                return Err(CodegenError::UnsupportedSchema {
1903                    path: source_path.to_path_buf(),
1904                    detail: format!("Domain Error code `{code}` is duplicated or name-colliding"),
1905                });
1906            }
1907            continue;
1908        }
1909        let object = variant
1910            .as_object()
1911            .ok_or_else(|| CodegenError::UnsupportedSchema {
1912                path: source_path.to_path_buf(),
1913                detail: "Domain Error variants must be string consts or objects".to_owned(),
1914            })?;
1915        let properties = object
1916            .get("properties")
1917            .and_then(Value::as_object)
1918            .ok_or_else(|| CodegenError::UnsupportedSchema {
1919                path: source_path.to_path_buf(),
1920                detail: "structured Domain Error variants need properties".to_owned(),
1921            })?;
1922        if object.get("additionalProperties") != Some(&Value::Bool(false))
1923            || !required_fields(variant).contains("code")
1924            || properties
1925                .keys()
1926                .any(|name| name != "code" && name != "payload")
1927        {
1928            return Err(CodegenError::UnsupportedSchema {
1929                path: source_path.to_path_buf(),
1930                detail:
1931                    "structured Domain Error objects must explicitly allow only code and payload"
1932                        .to_owned(),
1933            });
1934        }
1935        let code = object
1936            .get("properties")
1937            .and_then(Value::as_object)
1938            .and_then(|properties| properties.get("code"))
1939            .and_then(|code| code.get("const"))
1940            .and_then(Value::as_str)
1941            .ok_or_else(|| CodegenError::UnsupportedSchema {
1942                path: source_path.to_path_buf(),
1943                detail: "structured Domain Error variants need a const string code".to_owned(),
1944            })?;
1945        let name = pascal_case(code);
1946        if !codes.insert(code.to_owned())
1947            || !names.insert(name.clone())
1948            || !is_generated_enum_variant(&name)
1949        {
1950            return Err(CodegenError::UnsupportedSchema {
1951                path: source_path.to_path_buf(),
1952                detail: format!("Domain Error code `{code}` is duplicated or name-colliding"),
1953            });
1954        }
1955        if let Some(payload) = object
1956            .get("properties")
1957            .and_then(Value::as_object)
1958            .and_then(|properties| properties.get("payload"))
1959        {
1960            validate_value_generation_schema(payload, source_path)?;
1961        }
1962    }
1963    Ok(())
1964}
1965
1966fn generation_input(path: &Path) -> Result<(GeneratedMetadata, ContractIr), CodegenError> {
1967    let descriptor = load_descriptor(path)?;
1968    let contract = contract_ir(&descriptor);
1969    let metadata = GeneratedMetadata {
1970        capability_id: descriptor.capability_id.clone(),
1971        descriptor_version: descriptor.version.clone(),
1972        descriptor_digest: descriptor.digest.clone(),
1973        portable: descriptor.portable,
1974        cross_lane_transfer: descriptor.cross_lane_transfer,
1975    };
1976    Ok((metadata, contract))
1977}
1978
1979/// Generates one selected language projection from a Descriptor source.
1980pub fn generate_projection(
1981    path: &Path,
1982    language: ProjectionLanguage,
1983) -> Result<GeneratedProjection, CodegenError> {
1984    let (metadata, contract) = generation_input(path)?;
1985    let source = match language {
1986        ProjectionLanguage::Rust => generate_rust(&contract),
1987        ProjectionLanguage::TypeScript => generate_typescript(&contract),
1988        ProjectionLanguage::Wit => wit::generate_wit(&contract)?,
1989        ProjectionLanguage::RustRuntime => generate_rust_runtime(&contract)?,
1990        ProjectionLanguage::RustPlugin => generate_rust_plugin(&contract)?,
1991    };
1992    Ok(GeneratedProjection {
1993        metadata,
1994        language,
1995        source,
1996    })
1997}
1998
1999/// Generates both language artifacts from one Descriptor source.
2000pub fn generate(path: &Path) -> Result<GeneratedArtifacts, CodegenError> {
2001    let (metadata, contract) = generation_input(path)?;
2002    Ok(GeneratedArtifacts {
2003        metadata,
2004        rust: generate_rust(&contract),
2005        typescript: generate_typescript(&contract),
2006    })
2007}
2008
2009/// Writes generated artifacts to checked-in paths.
2010pub fn write_generated(
2011    descriptor_path: &Path,
2012    rust_path: &Path,
2013    typescript_path: &Path,
2014) -> Result<(), CodegenError> {
2015    let artifacts = generate(descriptor_path)?;
2016    write_artifact(rust_path, &artifacts.rust)?;
2017    write_artifact(typescript_path, &artifacts.typescript)?;
2018    Ok(())
2019}
2020
2021/// Writes one selected language projection to a checked-in path.
2022pub fn write_projection(
2023    descriptor_path: &Path,
2024    language: ProjectionLanguage,
2025    output_path: &Path,
2026) -> Result<(), CodegenError> {
2027    let projection = generate_projection(descriptor_path, language)?;
2028    write_artifact(output_path, &projection.source)
2029}
2030
2031/// Fails when either checked-in artifact is not exactly reproducible.
2032pub fn check_generated(
2033    descriptor_path: &Path,
2034    rust_path: &Path,
2035    typescript_path: &Path,
2036) -> Result<(), CodegenError> {
2037    let artifacts = generate(descriptor_path)?;
2038    check_artifact(rust_path, &artifacts.rust)?;
2039    check_artifact(typescript_path, &artifacts.typescript)?;
2040    Ok(())
2041}
2042
2043/// Fails when one selected language projection is not exactly reproducible.
2044pub fn check_projection(
2045    descriptor_path: &Path,
2046    language: ProjectionLanguage,
2047    output_path: &Path,
2048) -> Result<(), CodegenError> {
2049    let projection = generate_projection(descriptor_path, language)?;
2050    check_artifact(output_path, &projection.source)
2051}
2052
2053/// Round-trips one JSON value through the portable wire representation.
2054///
2055/// Raw JSON numbers are restricted to JavaScript's safe integer range. Wide
2056/// integers must therefore be represented by the Descriptor's `int64` or
2057/// `uint64` decimal-string formats, which this function preserves exactly.
2058pub fn round_trip_portable_json(value: &Value) -> Result<Value, CodegenError> {
2059    validate_portable_value(value, "$".to_owned())?;
2060    let encoded = serde_json::to_vec(value).expect("JSON values are serializable");
2061    serde_json::from_slice(&encoded).map_err(|error| CodegenError::InvalidPortableValue {
2062        path: "$".to_owned(),
2063        detail: format!("wire JSON could not be decoded: {error}"),
2064    })
2065}
2066
2067/// Validates a wire value against a package-local JSON Schema.
2068///
2069/// This is intentionally a small validator for the Descriptor's portable
2070/// profile rather than a general JSON Schema implementation. It makes the
2071/// profile's loss-sensitive mappings executable: wide integers must use their
2072/// decimal-string formats, bytes must be canonical base64, and timestamp and
2073/// duration strings must have their documented wire shapes.
2074pub fn validate_wire_value(schema_path: &Path, value: &Value) -> Result<(), CodegenError> {
2075    let schema_path = canonical_path(schema_path)?;
2076    let package_root = schema_path
2077        .parent()
2078        .unwrap_or_else(|| Path::new("."))
2079        .to_path_buf();
2080    let source = read_json(&schema_path)?;
2081    let mut ref_stack = Vec::new();
2082    let schema = resolve_refs(
2083        &source,
2084        &source,
2085        &schema_path,
2086        &package_root,
2087        &mut ref_stack,
2088    )?;
2089    validate_portable_value(value, "$".to_owned())?;
2090    validate_schema_profile(&schema, &schema_path)?;
2091    validate_wire_value_inner(&schema, value, "$", &schema_path)
2092}
2093
2094fn validate_wire_value_inner(
2095    schema: &Value,
2096    value: &Value,
2097    path: &str,
2098    source_path: &Path,
2099) -> Result<(), CodegenError> {
2100    if let Some(constant) = schema.get("const")
2101        && !portable_json_semantic_equal(value, constant)
2102    {
2103        return invalid_wire_value(path, "value does not match the Schema const", source_path);
2104    }
2105    if let Some(values) = schema.get("enum").and_then(Value::as_array)
2106        && !values
2107            .iter()
2108            .any(|candidate| portable_json_semantic_equal(candidate, value))
2109    {
2110        return invalid_wire_value(
2111            path,
2112            "value is not included in the Schema enum",
2113            source_path,
2114        );
2115    }
2116
2117    if let Some(alternatives) = schema.get("oneOf").and_then(Value::as_array) {
2118        let matches = alternatives
2119            .iter()
2120            .filter(|alternative| {
2121                validate_wire_value_inner(alternative, value, path, source_path).is_ok()
2122            })
2123            .count();
2124        if matches == 0
2125            && is_open_domain_error_schema(schema)
2126            && is_unknown_domain_error_wire(schema, value)
2127        {
2128            return validate_portable_value(value, path.to_owned());
2129        }
2130        if matches != 1 {
2131            return invalid_wire_value(
2132                path,
2133                "value must match exactly one Schema oneOf alternative",
2134                source_path,
2135            );
2136        }
2137    }
2138    if let Some(alternatives) = schema.get("anyOf").and_then(Value::as_array)
2139        && !alternatives.iter().any(|alternative| {
2140            validate_wire_value_inner(alternative, value, path, source_path).is_ok()
2141        })
2142    {
2143        return invalid_wire_value(
2144            path,
2145            "value must match at least one Schema anyOf alternative",
2146            source_path,
2147        );
2148    }
2149    if let Some(condition) = schema.get("if") {
2150        let branch = if validate_wire_value_inner(condition, value, path, source_path).is_ok() {
2151            schema.get("then")
2152        } else {
2153            schema.get("else")
2154        };
2155        if let Some(branch) = branch {
2156            validate_wire_value_inner(branch, value, path, source_path)?;
2157        }
2158    }
2159
2160    match schema.get("type") {
2161        Some(Value::String(schema_type)) => {
2162            validate_wire_type(schema, schema_type, value, path, source_path)
2163        }
2164        Some(Value::Array(types)) => {
2165            let mut errors = Vec::new();
2166            for schema_type in types {
2167                let Some(schema_type) = schema_type.as_str() else {
2168                    continue;
2169                };
2170                match validate_wire_type(schema, schema_type, value, path, source_path) {
2171                    Ok(()) => return Ok(()),
2172                    Err(error) => errors.push(error.to_string()),
2173                }
2174            }
2175            invalid_wire_value(
2176                path,
2177                &format!(
2178                    "value does not match any Schema type ({})",
2179                    errors.join("; ")
2180                ),
2181                source_path,
2182            )
2183        }
2184        Some(_) => invalid_wire_value(path, "Schema type must be a string or array", source_path),
2185        None if schema.as_object().is_some_and(|object| {
2186            object.contains_key("properties")
2187                || object.contains_key("required")
2188                || object.contains_key("additionalProperties")
2189        }) =>
2190        {
2191            validate_wire_type(schema, "object", value, path, source_path)
2192        }
2193        None if schema
2194            .as_object()
2195            .is_some_and(|object| object.contains_key("const") || object.contains_key("enum")) =>
2196        {
2197            Ok(())
2198        }
2199        None => Ok(()),
2200    }
2201}
2202
2203fn is_open_domain_error_schema(schema: &Value) -> bool {
2204    schema
2205        .get("oneOf")
2206        .and_then(Value::as_array)
2207        .is_some_and(|variants| {
2208            !variants.is_empty()
2209                && variants.iter().all(|variant| {
2210                    variant.get("const").and_then(Value::as_str).is_some()
2211                        || variant
2212                            .get("properties")
2213                            .and_then(Value::as_object)
2214                            .and_then(|properties| properties.get("code"))
2215                            .and_then(|code| code.get("const"))
2216                            .and_then(Value::as_str)
2217                            .is_some()
2218                })
2219        })
2220}
2221
2222fn is_unknown_domain_error_wire(schema: &Value, value: &Value) -> bool {
2223    let known_codes = schema
2224        .get("oneOf")
2225        .and_then(Value::as_array)
2226        .into_iter()
2227        .flatten()
2228        .filter_map(|variant| {
2229            variant.get("const").and_then(Value::as_str).or_else(|| {
2230                variant
2231                    .get("properties")
2232                    .and_then(Value::as_object)
2233                    .and_then(|properties| properties.get("code"))
2234                    .and_then(|code| code.get("const"))
2235                    .and_then(Value::as_str)
2236            })
2237        })
2238        .collect::<BTreeSet<_>>();
2239    match value {
2240        Value::String(code) => !known_codes.contains(code.as_str()),
2241        Value::Object(object) => object
2242            .get("code")
2243            .and_then(Value::as_str)
2244            .is_some_and(|code| !known_codes.contains(code)),
2245        _ => false,
2246    }
2247}
2248
2249fn validate_wire_type(
2250    schema: &Value,
2251    schema_type: &str,
2252    value: &Value,
2253    path: &str,
2254    source_path: &Path,
2255) -> Result<(), CodegenError> {
2256    match schema_type {
2257        "object" => validate_wire_object(schema, value, path, source_path),
2258        "array" => validate_wire_array(schema, value, path, source_path),
2259        "string" => validate_wire_string(schema, value, path, source_path),
2260        "integer" => {
2261            if !value.is_number()
2262                || value.as_i64().is_none() && value.as_u64().is_none()
2263                || !is_safe_json_integer(value)
2264            {
2265                return invalid_wire_value(path, "expected an integer", source_path);
2266            }
2267            validate_numeric_constraint(schema, value, path, source_path)
2268        }
2269        "number" => {
2270            if value
2271                .as_number()
2272                .is_none_or(|number| !is_safe_json_number(number))
2273            {
2274                return invalid_wire_value(path, "expected a finite number", source_path);
2275            }
2276            validate_numeric_constraint(schema, value, path, source_path)
2277        }
2278        "boolean" => {
2279            if !value.is_boolean() {
2280                return invalid_wire_value(path, "expected a boolean", source_path);
2281            }
2282            Ok(())
2283        }
2284        "null" => {
2285            if !value.is_null() {
2286                return invalid_wire_value(path, "expected null", source_path);
2287            }
2288            Ok(())
2289        }
2290        _ => invalid_wire_value(
2291            path,
2292            "Schema type is outside the portable profile",
2293            source_path,
2294        ),
2295    }
2296}
2297
2298fn is_safe_json_integer(value: &Value) -> bool {
2299    value
2300        .as_i64()
2301        .is_some_and(|value| (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value))
2302        || value
2303            .as_u64()
2304            .is_some_and(|value| value <= MAX_SAFE_INTEGER as u64)
2305}
2306
2307fn is_safe_json_number(number: &serde_json::Number) -> bool {
2308    number
2309        .as_i64()
2310        .is_some_and(|value| (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value))
2311        || number
2312            .as_u64()
2313            .is_some_and(|value| value <= MAX_SAFE_INTEGER as u64)
2314        || number.as_f64().is_some_and(|value| {
2315            value.is_finite() && (value.abs() <= 9_007_199_254_740_991.0 || value.fract() != 0.0)
2316        })
2317}
2318
2319fn validate_wire_object(
2320    schema: &Value,
2321    value: &Value,
2322    path: &str,
2323    source_path: &Path,
2324) -> Result<(), CodegenError> {
2325    let Value::Object(properties) = value else {
2326        return invalid_wire_value(path, "expected an object", source_path);
2327    };
2328    let property_count = u64::try_from(properties.len()).unwrap_or(u64::MAX);
2329    if let Some(minimum) = schema
2330        .get("minProperties")
2331        .and_then(non_negative_safe_schema_integer)
2332        && property_count < minimum
2333    {
2334        return invalid_wire_value(
2335            path,
2336            "object has fewer properties than minProperties",
2337            source_path,
2338        );
2339    }
2340    if let Some(maximum) = schema
2341        .get("maxProperties")
2342        .and_then(non_negative_safe_schema_integer)
2343        && property_count > maximum
2344    {
2345        return invalid_wire_value(
2346            path,
2347            "object has more properties than maxProperties",
2348            source_path,
2349        );
2350    }
2351    let required = required_fields(schema);
2352    for field in required {
2353        if !properties.contains_key(&field) {
2354            return invalid_wire_value(
2355                &format!("{path}.{field}"),
2356                "required field is missing",
2357                source_path,
2358            );
2359        }
2360    }
2361    let declared = schema
2362        .get("properties")
2363        .and_then(Value::as_object)
2364        .cloned()
2365        .unwrap_or_default();
2366    for (field, field_value) in properties {
2367        if let Some(property_names) = schema.get("propertyNames") {
2368            validate_wire_value_inner(
2369                property_names,
2370                &Value::String(field.clone()),
2371                &format!("{path}.{field}"),
2372                source_path,
2373            )?;
2374        }
2375        if let Some(field_schema) = declared.get(field) {
2376            validate_wire_value_inner(
2377                field_schema,
2378                field_value,
2379                &format!("{path}.{field}"),
2380                source_path,
2381            )?;
2382            continue;
2383        }
2384        match schema.get("additionalProperties") {
2385            Some(Value::Bool(false)) => {
2386                return invalid_wire_value(
2387                    &format!("{path}.{field}"),
2388                    "additional property is not allowed",
2389                    source_path,
2390                );
2391            }
2392            Some(additional_schema) if !additional_schema.is_boolean() => {
2393                validate_wire_value_inner(
2394                    additional_schema,
2395                    field_value,
2396                    &format!("{path}.{field}"),
2397                    source_path,
2398                )?;
2399            }
2400            _ => {}
2401        }
2402    }
2403    Ok(())
2404}
2405
2406fn validate_wire_array(
2407    schema: &Value,
2408    value: &Value,
2409    path: &str,
2410    source_path: &Path,
2411) -> Result<(), CodegenError> {
2412    let Value::Array(values) = value else {
2413        return invalid_wire_value(path, "expected an array", source_path);
2414    };
2415    let length = u64::try_from(values.len()).unwrap_or(u64::MAX);
2416    if let Some(minimum) = schema
2417        .get("minItems")
2418        .and_then(non_negative_safe_schema_integer)
2419        && length < minimum
2420    {
2421        return invalid_wire_value(path, "array has fewer items than minItems", source_path);
2422    }
2423    if let Some(maximum) = schema
2424        .get("maxItems")
2425        .and_then(non_negative_safe_schema_integer)
2426        && length > maximum
2427    {
2428        return invalid_wire_value(path, "array has more items than maxItems", source_path);
2429    }
2430    if schema.get("uniqueItems") == Some(&Value::Bool(true)) {
2431        let mut fingerprints = HashSet::with_capacity(values.len());
2432        for value in values {
2433            if !fingerprints.insert(portable_json_fingerprint(value)) {
2434                return invalid_wire_value(path, "array items must be unique", source_path);
2435            }
2436        }
2437    }
2438    if let Some(items) = schema.get("items") {
2439        for (index, item) in values.iter().enumerate() {
2440            validate_wire_value_inner(items, item, &format!("{path}[{index}]"), source_path)?;
2441        }
2442    }
2443    Ok(())
2444}
2445
2446fn portable_json_fingerprint(value: &Value) -> String {
2447    let mut output = String::new();
2448    write_portable_json_fingerprint(value, &mut output);
2449    output
2450}
2451
2452fn portable_json_semantic_equal(left: &Value, right: &Value) -> bool {
2453    portable_json_fingerprint(left) == portable_json_fingerprint(right)
2454}
2455
2456fn write_portable_json_fingerprint(value: &Value, output: &mut String) {
2457    match value {
2458        Value::Null => output.push('n'),
2459        Value::Bool(value) => output.push_str(if *value { "b1" } else { "b0" }),
2460        Value::Number(value) => {
2461            let number = value
2462                .as_f64()
2463                .expect("portable JSON numbers are representable as f64");
2464            let bits = if number == 0.0 { 0 } else { number.to_bits() };
2465            write!(output, "d{bits:016x};").expect("writing to a String cannot fail");
2466        }
2467        Value::String(value) => {
2468            write!(output, "s{}:", value.len()).expect("writing to a String cannot fail");
2469            output.push_str(value);
2470        }
2471        Value::Array(values) => {
2472            write!(output, "a{}[", values.len()).expect("writing to a String cannot fail");
2473            for value in values {
2474                write_portable_json_fingerprint(value, output);
2475            }
2476            output.push(']');
2477        }
2478        Value::Object(values) => {
2479            let mut entries = values.iter().collect::<Vec<_>>();
2480            entries.sort_unstable_by_key(|(key, _)| *key);
2481            write!(output, "o{}{{", entries.len()).expect("writing to a String cannot fail");
2482            for (key, value) in entries {
2483                write!(output, "k{}:", key.len()).expect("writing to a String cannot fail");
2484                output.push_str(key);
2485                write_portable_json_fingerprint(value, output);
2486            }
2487            output.push('}');
2488        }
2489    }
2490}
2491
2492fn validate_wire_string(
2493    schema: &Value,
2494    value: &Value,
2495    path: &str,
2496    source_path: &Path,
2497) -> Result<(), CodegenError> {
2498    let Value::String(value) = value else {
2499        return invalid_wire_value(path, "expected a string", source_path);
2500    };
2501    validate_string_format(schema, value, path, source_path)?;
2502    if let Some(pattern) = schema.get("pattern").and_then(Value::as_str) {
2503        let Ok(pattern) = compile_portable_pattern(pattern) else {
2504            return invalid_wire_value(path, "Schema pattern could not be compiled", source_path);
2505        };
2506        if !pattern.is_match(value) {
2507            return invalid_wire_value(path, "string does not match pattern", source_path);
2508        }
2509    }
2510    let length = u64::try_from(value.chars().count()).unwrap_or(u64::MAX);
2511    if let Some(minimum) = schema
2512        .get("minLength")
2513        .and_then(non_negative_safe_schema_integer)
2514        && length < minimum
2515    {
2516        return invalid_wire_value(path, "string is shorter than minLength", source_path);
2517    }
2518    if let Some(maximum) = schema
2519        .get("maxLength")
2520        .and_then(non_negative_safe_schema_integer)
2521        && length > maximum
2522    {
2523        return invalid_wire_value(path, "string is longer than maxLength", source_path);
2524    }
2525    Ok(())
2526}
2527
2528fn validate_numeric_constraint(
2529    schema: &Value,
2530    value: &Value,
2531    path: &str,
2532    source_path: &Path,
2533) -> Result<(), CodegenError> {
2534    let Some(number) = value.as_f64() else {
2535        return Ok(());
2536    };
2537    if let Some(minimum) = schema.get("minimum").and_then(Value::as_f64)
2538        && number < minimum
2539    {
2540        return invalid_wire_value(path, "number is below minimum", source_path);
2541    }
2542    if let Some(maximum) = schema.get("maximum").and_then(Value::as_f64)
2543        && number > maximum
2544    {
2545        return invalid_wire_value(path, "number is above maximum", source_path);
2546    }
2547    if let Some(minimum) = schema.get("exclusiveMinimum").and_then(Value::as_f64)
2548        && number <= minimum
2549    {
2550        return invalid_wire_value(path, "number is not above exclusiveMinimum", source_path);
2551    }
2552    if let Some(maximum) = schema.get("exclusiveMaximum").and_then(Value::as_f64)
2553        && number >= maximum
2554    {
2555        return invalid_wire_value(path, "number is not below exclusiveMaximum", source_path);
2556    }
2557    Ok(())
2558}
2559
2560fn validate_string_format(
2561    schema: &Value,
2562    value: &str,
2563    path: &str,
2564    source_path: &Path,
2565) -> Result<(), CodegenError> {
2566    let Some(format) = schema.get("format").and_then(Value::as_str) else {
2567        return Ok(());
2568    };
2569    let valid = match format {
2570        "int64" => is_signed_decimal(value),
2571        "uint64" => is_unsigned_decimal(value),
2572        "byte" => is_base64(value),
2573        "date-time" => is_rfc3339(value),
2574        "duration" => is_iso8601_duration(value),
2575        _ => false,
2576    };
2577    if valid {
2578        Ok(())
2579    } else {
2580        invalid_wire_value(
2581            path,
2582            &format!("string does not match portable `{format}` format"),
2583            source_path,
2584        )
2585    }
2586}
2587
2588fn invalid_wire_value<T>(path: &str, detail: &str, _source_path: &Path) -> Result<T, CodegenError> {
2589    Err(CodegenError::InvalidPortableValue {
2590        path: path.to_owned(),
2591        detail: detail.to_owned(),
2592    })
2593}
2594
2595fn is_signed_decimal(value: &str) -> bool {
2596    let digits = value.strip_prefix('-').unwrap_or(value);
2597    !digits.is_empty()
2598        && (digits == "0" || !digits.starts_with('0'))
2599        && digits.chars().all(|character| character.is_ascii_digit())
2600        && value.parse::<i64>().is_ok()
2601}
2602
2603fn is_unsigned_decimal(value: &str) -> bool {
2604    !value.is_empty()
2605        && (value == "0" || !value.starts_with('0'))
2606        && value.chars().all(|character| character.is_ascii_digit())
2607        && value.parse::<u64>().is_ok()
2608}
2609
2610fn is_base64(value: &str) -> bool {
2611    let bytes = value.as_bytes();
2612    if bytes.is_empty() {
2613        return true;
2614    }
2615    if !bytes.len().is_multiple_of(4) {
2616        return false;
2617    }
2618    let padding = bytes.iter().rev().take_while(|byte| **byte == b'=').count();
2619    if padding > 2 || bytes[..bytes.len() - padding].contains(&b'=') {
2620        return false;
2621    }
2622    for byte in &bytes[..bytes.len() - padding] {
2623        if base64_digit(*byte).is_none() {
2624            return false;
2625        }
2626    }
2627    let last = &bytes[bytes.len() - 4..];
2628    if padding == 1 {
2629        base64_digit(last[2]).is_some_and(|digit| digit.trailing_zeros() >= 2)
2630    } else if padding == 2 {
2631        base64_digit(last[1]).is_some_and(|digit| digit.trailing_zeros() >= 4)
2632    } else {
2633        base64_digit(last[2]).is_some() && base64_digit(last[3]).is_some()
2634    }
2635}
2636
2637fn base64_digit(byte: u8) -> Option<u8> {
2638    match byte {
2639        b'A'..=b'Z' => Some(byte - b'A'),
2640        b'a'..=b'z' => Some(byte - b'a' + 26),
2641        b'0'..=b'9' => Some(byte - b'0' + 52),
2642        b'+' => Some(62),
2643        b'/' => Some(63),
2644        _ => None,
2645    }
2646}
2647
2648fn is_rfc3339(value: &str) -> bool {
2649    let bytes = value.as_bytes();
2650    if bytes.len() < 20
2651        || !matches!(bytes.get(4), Some(b'-'))
2652        || !matches!(bytes.get(7), Some(b'-'))
2653        || !matches!(bytes.get(10), Some(b'T' | b't'))
2654        || !matches!(bytes.get(13), Some(b':'))
2655        || !matches!(bytes.get(16), Some(b':'))
2656    {
2657        return false;
2658    }
2659    let Some(month) = fixed_digits(bytes, 5, 2) else {
2660        return false;
2661    };
2662    let Some(day) = fixed_digits(bytes, 8, 2) else {
2663        return false;
2664    };
2665    let Some(hour) = fixed_digits(bytes, 11, 2) else {
2666        return false;
2667    };
2668    let Some(minute) = fixed_digits(bytes, 14, 2) else {
2669        return false;
2670    };
2671    let Some(second) = fixed_digits(bytes, 17, 2) else {
2672        return false;
2673    };
2674    let Some(year) = fixed_digits(bytes, 0, 4) else {
2675        return false;
2676    };
2677    let max_day = match month {
2678        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2679        4 | 6 | 9 | 11 => 30,
2680        2 if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) => 29,
2681        2 => 28,
2682        _ => return false,
2683    };
2684    if day == 0 || day > max_day || hour > 23 || minute > 59 || second > 60 {
2685        return false;
2686    }
2687    let mut index = 19;
2688    if bytes.get(index) == Some(&b'.') {
2689        index += 1;
2690        let start = index;
2691        while bytes.get(index).is_some_and(u8::is_ascii_digit) {
2692            index += 1;
2693        }
2694        if index == start {
2695            return false;
2696        }
2697    }
2698    match bytes.get(index) {
2699        Some(b'Z' | b'z') => index + 1 == bytes.len(),
2700        Some(b'+' | b'-') => {
2701            index += 1;
2702            let Some(offset_hour) = fixed_digits(bytes, index, 2) else {
2703                return false;
2704            };
2705            index += 2;
2706            if bytes.get(index) != Some(&b':') {
2707                return false;
2708            }
2709            index += 1;
2710            let Some(offset_minute) = fixed_digits(bytes, index, 2) else {
2711                return false;
2712            };
2713            offset_hour <= 23 && offset_minute <= 59 && index + 2 == bytes.len()
2714        }
2715        _ => false,
2716    }
2717}
2718
2719fn fixed_digits(bytes: &[u8], start: usize, length: usize) -> Option<u32> {
2720    let slice = bytes.get(start..start + length)?;
2721    slice.iter().try_fold(0_u32, |value, digit| {
2722        digit
2723            .is_ascii_digit()
2724            .then(|| value * 10 + u32::from(digit - b'0'))
2725    })
2726}
2727
2728fn is_iso8601_duration(value: &str) -> bool {
2729    let bytes = value.as_bytes();
2730    let mut index = usize::from(bytes.first() == Some(&b'-'));
2731    if bytes.get(index) != Some(&b'P') {
2732        return false;
2733    }
2734    index += 1;
2735    let mut in_time = false;
2736    let mut saw_component = false;
2737    let mut saw_time_component = false;
2738    let mut last_date_order = None;
2739    let mut last_time_order = None;
2740    let mut saw_week = false;
2741    let mut saw_other_date_unit = false;
2742    while index < bytes.len() {
2743        if bytes[index] == b'T' {
2744            if in_time || index + 1 == bytes.len() {
2745                return false;
2746            }
2747            in_time = true;
2748            index += 1;
2749            continue;
2750        }
2751        let digit_start = index;
2752        while bytes.get(index).is_some_and(u8::is_ascii_digit) {
2753            index += 1;
2754        }
2755        if index == digit_start {
2756            return false;
2757        }
2758
2759        let mut fractional = false;
2760        if bytes.get(index) == Some(&b'.') {
2761            fractional = true;
2762            index += 1;
2763            let fraction_start = index;
2764            while bytes.get(index).is_some_and(u8::is_ascii_digit) {
2765                index += 1;
2766            }
2767            if index == fraction_start {
2768                return false;
2769            }
2770        }
2771
2772        let Some(&unit) = bytes.get(index) else {
2773            return false;
2774        };
2775        if in_time {
2776            let order = match unit {
2777                b'H' => 0,
2778                b'M' => 1,
2779                b'S' => 2,
2780                _ => return false,
2781            };
2782            if last_time_order.is_some_and(|previous| order <= previous) {
2783                return false;
2784            }
2785            last_time_order = Some(order);
2786            saw_time_component = true;
2787        } else {
2788            let order = match unit {
2789                b'Y' => 0,
2790                b'M' => 1,
2791                b'W' => 2,
2792                b'D' => 3,
2793                _ => return false,
2794            };
2795            if last_date_order.is_some_and(|previous| order <= previous) {
2796                return false;
2797            }
2798            last_date_order = Some(order);
2799            if unit == b'W' {
2800                if saw_other_date_unit || index + 1 != bytes.len() {
2801                    return false;
2802                }
2803                saw_week = true;
2804            } else {
2805                if saw_week {
2806                    return false;
2807                }
2808                saw_other_date_unit = true;
2809            }
2810        }
2811        saw_component = true;
2812        index += 1;
2813        if fractional && index != bytes.len() {
2814            return false;
2815        }
2816    }
2817    saw_component && (!in_time || saw_time_component)
2818}
2819
2820fn validate_portable_value(value: &Value, path: String) -> Result<(), CodegenError> {
2821    match value {
2822        Value::Number(number) => {
2823            if !is_safe_json_number(number) {
2824                return Err(CodegenError::InvalidPortableValue {
2825                    path,
2826                    detail: "wide integers must use an explicit decimal-string format".to_owned(),
2827                });
2828            }
2829        }
2830        Value::Array(values) => {
2831            for (index, value) in values.iter().enumerate() {
2832                validate_portable_value(value, format!("{path}[{index}]"))?;
2833            }
2834        }
2835        Value::Object(values) => {
2836            for (key, value) in values {
2837                validate_portable_value(value, format!("{path}.{key}"))?;
2838            }
2839        }
2840        Value::Null | Value::Bool(_) | Value::String(_) => {}
2841    }
2842    Ok(())
2843}
2844
2845fn write_artifact(path: &Path, contents: &str) -> Result<(), CodegenError> {
2846    if let Some(parent) = path.parent() {
2847        fs::create_dir_all(parent).map_err(|source| CodegenError::Io {
2848            path: parent.to_path_buf(),
2849            source,
2850        })?;
2851    }
2852    fs::write(path, contents).map_err(|source| CodegenError::Io {
2853        path: path.to_path_buf(),
2854        source,
2855    })
2856}
2857
2858fn check_artifact(path: &Path, expected: &str) -> Result<(), CodegenError> {
2859    let actual = fs::read_to_string(path).map_err(|source| CodegenError::Io {
2860        path: path.to_path_buf(),
2861        source,
2862    })?;
2863    if actual == expected {
2864        Ok(())
2865    } else {
2866        Err(CodegenError::GeneratedArtifactDrift {
2867            path: path.to_path_buf(),
2868        })
2869    }
2870}
2871
2872/// Checks whether `new_path` is an additive, compatible evolution of
2873/// `old_path`. `true` means the same Capability series remains compatible.
2874pub fn lint_compatibility(old_path: &Path, new_path: &Path) -> Result<bool, CompatibilityError> {
2875    let old = load_descriptor(old_path).map_err(|error| CompatibilityError::BreakingChanges {
2876        changes: vec![error.to_string()],
2877    })?;
2878    let new = load_descriptor(new_path).map_err(|error| CompatibilityError::BreakingChanges {
2879        changes: vec![error.to_string()],
2880    })?;
2881    if old.capability_id != new.capability_id {
2882        return Err(CompatibilityError::IdentityChanged {
2883            from: old.capability_id,
2884            to: new.capability_id,
2885        });
2886    }
2887    if new.parsed_version <= old.parsed_version {
2888        return Err(CompatibilityError::VersionNotAdvanced {
2889            from: old.version,
2890            to: new.version,
2891        });
2892    }
2893    if new.parsed_version.major() != old.parsed_version.major() {
2894        return Err(CompatibilityError::BreakingChanges {
2895            changes: vec!["a breaking change must use a new Capability @major identity".to_owned()],
2896        });
2897    }
2898
2899    let mut changes = Vec::new();
2900    if old.portable != new.portable {
2901        changes.push("Descriptor portability changed".to_owned());
2902    }
2903    if old.cross_lane_transfer && !new.cross_lane_transfer {
2904        changes.push("Descriptor cross-lane transfer support was removed".to_owned());
2905    }
2906    let old_operations: BTreeMap<_, _> = old
2907        .operations
2908        .iter()
2909        .map(|operation| (operation.name.as_str(), operation))
2910        .collect();
2911    let new_operations: BTreeMap<_, _> = new
2912        .operations
2913        .iter()
2914        .map(|operation| (operation.name.as_str(), operation))
2915        .collect();
2916
2917    for (name, old_operation) in &old_operations {
2918        let Some(new_operation) = new_operations.get(name) else {
2919            changes.push(format!("Operation `{name}` was removed"));
2920            continue;
2921        };
2922        if old_operation.interaction != new_operation.interaction {
2923            changes.push(format!("Operation `{name}` changed interaction"));
2924        }
2925        compare_schema(
2926            &old_operation.request_schema,
2927            &new_operation.request_schema,
2928            &format!("Operation `{name}` request"),
2929            &mut changes,
2930        );
2931        compare_schema(
2932            &old_operation.response_schema,
2933            &new_operation.response_schema,
2934            &format!("Operation `{name}` response"),
2935            &mut changes,
2936        );
2937        compare_error_schema(
2938            &old_operation.domain_error_schema,
2939            &new_operation.domain_error_schema,
2940            &format!("Operation `{name}` Domain Error"),
2941            &mut changes,
2942        );
2943    }
2944
2945    if new.parsed_version.minor() == old.parsed_version.minor() {
2946        if contract_signature(&old) != contract_signature(&new) {
2947            changes.push("a patch release changed the observable contract".to_owned());
2948        }
2949        for name in new_operations
2950            .keys()
2951            .filter(|name| !old_operations.contains_key(**name))
2952        {
2953            changes.push(format!("Operation `{name}` was added in a patch release"));
2954        }
2955    }
2956    if new.parsed_version.minor() < old.parsed_version.minor() {
2957        changes.push("Descriptor minor version moved backwards".to_owned());
2958    }
2959
2960    if changes.is_empty() {
2961        Ok(true)
2962    } else {
2963        Err(CompatibilityError::BreakingChanges { changes })
2964    }
2965}
2966
2967fn contract_signature(descriptor: &Descriptor) -> String {
2968    let operations = descriptor
2969        .operations
2970        .iter()
2971        .map(|operation| {
2972            serde_json::json!({
2973                "name": operation.name,
2974                "interaction": operation.interaction,
2975                "request": operation.request_schema,
2976                "response": operation.response_schema,
2977                "domain_error": canonical_domain_error_schema(&operation.domain_error_schema),
2978            })
2979        })
2980        .collect::<Vec<_>>();
2981    canonical_json(&Value::Array(operations))
2982}
2983
2984fn canonical_domain_error_schema(schema: &Value) -> Value {
2985    let Some(variants) = schema.get("oneOf").and_then(Value::as_array) else {
2986        return schema.clone();
2987    };
2988    let mut variants = variants.clone();
2989    variants.sort_by(|left, right| {
2990        error_variant_code(left)
2991            .unwrap_or_default()
2992            .cmp(error_variant_code(right).unwrap_or_default())
2993    });
2994    let mut schema = schema.as_object().cloned().unwrap_or_default();
2995    schema.insert("oneOf".to_owned(), Value::Array(variants));
2996    Value::Object(schema)
2997}
2998
2999fn error_variant_code(variant: &Value) -> Option<&str> {
3000    variant.get("const").and_then(Value::as_str).or_else(|| {
3001        variant
3002            .get("properties")
3003            .and_then(Value::as_object)
3004            .and_then(|properties| properties.get("code"))
3005            .and_then(|code| code.get("const"))
3006            .and_then(Value::as_str)
3007    })
3008}
3009
3010fn compare_schema(old: &Value, new: &Value, location: &str, changes: &mut Vec<String>) {
3011    let old_nullable = is_nullable(old);
3012    let new_nullable = is_nullable(new);
3013    if old_nullable != new_nullable {
3014        changes.push(format!("{location} changed nullability"));
3015        return;
3016    }
3017    let old_type = schema_type_name(old);
3018    let new_type = schema_type_name(new);
3019    if old_type != new_type {
3020        changes.push(format!("{location} changed type"));
3021        return;
3022    }
3023    match old_type {
3024        Some("object") => {
3025            let old_properties = old.get("properties").and_then(Value::as_object);
3026            let new_properties = new.get("properties").and_then(Value::as_object);
3027            let (Some(old_properties), Some(new_properties)) = (old_properties, new_properties)
3028            else {
3029                if canonical_json(old) != canonical_json(new) {
3030                    changes.push(format!("{location} changed object shape"));
3031                }
3032                return;
3033            };
3034            let old_required = required_fields(old);
3035            let new_required = required_fields(new);
3036            for name in old_properties.keys() {
3037                let Some(new_schema) = new_properties.get(name) else {
3038                    changes.push(format!("{location} field `{name}` was removed"));
3039                    continue;
3040                };
3041                compare_schema(
3042                    &old_properties[name],
3043                    new_schema,
3044                    &format!("{location} field `{name}`"),
3045                    changes,
3046                );
3047            }
3048            for name in new_required.difference(&old_required) {
3049                changes.push(format!("{location} field `{name}` became required"));
3050            }
3051            for name in old_required.difference(&new_required) {
3052                changes.push(format!("{location} field `{name}` became optional"));
3053            }
3054            if old.get("additionalProperties") != new.get("additionalProperties") {
3055                changes.push(format!("{location} changed additional-properties policy"));
3056            }
3057            if schema_constraints_signature(old) != schema_constraints_signature(new) {
3058                changes.push(format!("{location} changed constraints"));
3059            }
3060        }
3061        Some("array") => {
3062            if let (Some(old_items), Some(new_items)) = (old.get("items"), new.get("items")) {
3063                compare_schema(old_items, new_items, &format!("{location} items"), changes);
3064            }
3065            if schema_constraints_signature(old) != schema_constraints_signature(new) {
3066                changes.push(format!("{location} changed constraints"));
3067            }
3068        }
3069        _ => {
3070            if canonical_json(old) != canonical_json(new) {
3071                changes.push(format!("{location} changed format or constraints"));
3072            }
3073        }
3074    }
3075}
3076
3077fn compare_error_schema(old: &Value, new: &Value, location: &str, changes: &mut Vec<String>) {
3078    let old_variants = error_variant_values(old);
3079    let new_variants = error_variant_values(new);
3080    if old_variants.is_empty() || new_variants.is_empty() {
3081        if canonical_json(old) != canonical_json(new) {
3082            changes.push(format!("{location} changed shape"));
3083        }
3084        return;
3085    }
3086    for (code, schema) in old_variants {
3087        match new_variants.get(&code) {
3088            None => changes.push(format!("{location} variant `{code}` was removed")),
3089            Some(new_schema) => compare_schema(
3090                &schema,
3091                new_schema,
3092                &format!("{location} variant `{code}`"),
3093                changes,
3094            ),
3095        }
3096    }
3097}
3098
3099fn error_variant_ir_definitions(schema: &Value) -> Vec<ErrorVariantIr> {
3100    let mut variants = schema
3101        .get("oneOf")
3102        .and_then(Value::as_array)
3103        .into_iter()
3104        .flatten()
3105        .filter_map(|variant| {
3106            if let Some(code) = variant.get("const").and_then(Value::as_str) {
3107                return Some(ErrorVariantIr {
3108                    code: code.to_owned(),
3109                    name: pascal_case(code),
3110                    structured: false,
3111                    payload: None,
3112                    payload_required: false,
3113                });
3114            }
3115            let object = variant.as_object()?;
3116            let code = object
3117                .get("properties")
3118                .and_then(Value::as_object)
3119                .and_then(|properties| properties.get("code"))
3120                .and_then(|code| code.get("const"))
3121                .and_then(Value::as_str)?;
3122            Some(ErrorVariantIr {
3123                code: code.to_owned(),
3124                name: pascal_case(code),
3125                structured: true,
3126                payload: object
3127                    .get("properties")
3128                    .and_then(Value::as_object)
3129                    .and_then(|properties| properties.get("payload"))
3130                    .map(type_ir_from_schema),
3131                payload_required: required_fields(variant).contains("payload"),
3132            })
3133        })
3134        .collect::<Vec<_>>();
3135    variants.sort_by(|left, right| left.code.cmp(&right.code));
3136    variants
3137}
3138
3139fn error_variant_values(schema: &Value) -> BTreeMap<String, Value> {
3140    let variants = schema
3141        .get("oneOf")
3142        .and_then(Value::as_array)
3143        .into_iter()
3144        .flatten();
3145    variants
3146        .filter_map(|variant| {
3147            let code = if let Some(code) = variant.get("const").and_then(Value::as_str) {
3148                code.to_owned()
3149            } else {
3150                variant
3151                    .get("properties")
3152                    .and_then(Value::as_object)
3153                    .and_then(|properties| properties.get("code"))
3154                    .and_then(|code| code.get("const"))
3155                    .and_then(Value::as_str)
3156                    .map(ToOwned::to_owned)?
3157            };
3158            Some((code, variant.clone()))
3159        })
3160        .collect()
3161}
3162
3163fn required_fields(schema: &Value) -> BTreeSet<String> {
3164    schema
3165        .get("required")
3166        .and_then(Value::as_array)
3167        .into_iter()
3168        .flatten()
3169        .filter_map(Value::as_str)
3170        .map(ToOwned::to_owned)
3171        .collect()
3172}
3173
3174fn is_nullable(schema: &Value) -> bool {
3175    schema
3176        .get("type")
3177        .and_then(Value::as_array)
3178        .is_some_and(|types| types.iter().any(|value| value == "null"))
3179        || schema
3180            .get("anyOf")
3181            .and_then(Value::as_array)
3182            .is_some_and(|schemas| schemas.iter().any(is_null_schema))
3183        || schema
3184            .get("oneOf")
3185            .and_then(Value::as_array)
3186            .is_some_and(|schemas| schemas.iter().any(is_null_schema))
3187}
3188
3189fn is_null_schema(schema: &Value) -> bool {
3190    schema.get("type").is_some_and(|value| value == "null")
3191        || schema.get("const").is_some_and(Value::is_null)
3192}
3193
3194fn schema_type_name(schema: &Value) -> Option<&str> {
3195    schema.get("type").and_then(|value| match value {
3196        Value::String(value) => Some(value.as_str()),
3197        Value::Array(values) => values
3198            .iter()
3199            .find_map(Value::as_str)
3200            .filter(|value| *value != "null"),
3201        _ => None,
3202    })
3203}
3204
3205fn canonical_json(value: &Value) -> String {
3206    serde_json::to_string(value).expect("JSON values are serializable")
3207}
3208
3209fn schema_constraints_signature(schema: &Value) -> String {
3210    let mut schema = schema.as_object().cloned().unwrap_or_default();
3211    for key in [
3212        "type",
3213        "properties",
3214        "required",
3215        "additionalProperties",
3216        "items",
3217    ] {
3218        schema.remove(key);
3219    }
3220    canonical_json(&Value::Object(schema))
3221}
3222
3223fn is_identifier(value: &str) -> bool {
3224    let mut characters = value.chars();
3225    characters
3226        .next()
3227        .is_some_and(|character| character.is_ascii_alphabetic() || character == '_')
3228        && characters
3229            .all(|character| character.is_ascii_alphanumeric() || matches!(character, '_' | '-'))
3230}
3231
3232fn is_rust_member_name(value: &str) -> bool {
3233    !matches!(snake_case(value).as_str(), "self" | "super" | "crate")
3234}
3235
3236fn is_rust_type_identifier(value: &str) -> bool {
3237    let mut characters = value.chars();
3238    let valid = characters
3239        .next()
3240        .is_some_and(|character| character.is_ascii_alphabetic() || character == '_')
3241        && characters.all(|character| character.is_ascii_alphanumeric() || character == '_');
3242    valid && value != "Self"
3243}
3244
3245fn is_generated_type_name(value: &str) -> bool {
3246    is_rust_type_identifier(value)
3247        && !matches!(
3248            value,
3249            "Self"
3250                | "String"
3251                | "Int64"
3252                | "Uint64"
3253                | "Bytes"
3254                | "Timestamp"
3255                | "Duration"
3256                | "OptionalValue"
3257                | "UnknownDomainError"
3258                | "InvocationContext"
3259                | "PluginDependencies"
3260                | "NativeRequestEndpoint"
3261                | "NativeRequestHandle"
3262                | "RequestCapability"
3263                | "RuntimeFailure"
3264                | "LocalBoxFuture"
3265        )
3266}
3267
3268fn is_generated_enum_variant(value: &str) -> bool {
3269    is_rust_type_identifier(value)
3270}
3271
3272fn pascal_case(value: &str) -> String {
3273    let mut output = String::new();
3274    for part in value.split(|character: char| !character.is_ascii_alphanumeric()) {
3275        if part.is_empty() || part.chars().all(char::is_numeric) {
3276            continue;
3277        }
3278        let mut chars = part.chars();
3279        if let Some(first) = chars.next() {
3280            output.extend(first.to_uppercase());
3281            output.push_str(chars.as_str());
3282        }
3283    }
3284    if output.is_empty() {
3285        "Value".to_owned()
3286    } else if output
3287        .chars()
3288        .next()
3289        .is_some_and(|character| character.is_ascii_digit())
3290    {
3291        format!("Value{output}")
3292    } else {
3293        output
3294    }
3295}
3296
3297fn snake_case(value: &str) -> String {
3298    let mut output = String::new();
3299    for (index, character) in value.chars().enumerate() {
3300        if character.is_ascii_alphanumeric() {
3301            if character.is_ascii_uppercase() && index != 0 {
3302                output.push('_');
3303            }
3304            output.push(character.to_ascii_lowercase());
3305        } else if !output.ends_with('_') {
3306            output.push('_');
3307        }
3308    }
3309    let output = output.trim_matches('_').to_owned();
3310    if output.is_empty() {
3311        "value".to_owned()
3312    } else {
3313        output
3314    }
3315}
3316
3317fn screaming_snake_case(value: &str) -> String {
3318    snake_case(value).to_ascii_uppercase()
3319}
3320
3321fn rust_field_name(value: &str) -> String {
3322    let name = snake_case(value);
3323    if matches!(name.as_str(), "self" | "super" | "crate") {
3324        return format!("{name}_");
3325    }
3326    if matches!(
3327        name.as_str(),
3328        "as" | "break"
3329            | "const"
3330            | "continue"
3331            | "crate"
3332            | "else"
3333            | "enum"
3334            | "extern"
3335            | "false"
3336            | "fn"
3337            | "for"
3338            | "if"
3339            | "impl"
3340            | "in"
3341            | "let"
3342            | "loop"
3343            | "match"
3344            | "mod"
3345            | "move"
3346            | "mut"
3347            | "pub"
3348            | "ref"
3349            | "return"
3350            | "self"
3351            | "Self"
3352            | "static"
3353            | "struct"
3354            | "super"
3355            | "trait"
3356            | "true"
3357            | "type"
3358            | "unsafe"
3359            | "use"
3360            | "where"
3361            | "while"
3362            | "async"
3363            | "await"
3364            | "dyn"
3365    ) {
3366        format!("r#{name}")
3367    } else {
3368        name
3369    }
3370}
3371
3372fn quote_string(value: &str) -> String {
3373    serde_json::to_string(value).expect("string is serializable")
3374}
3375
3376fn typescript_property_name(value: &str) -> String {
3377    let mut characters = value.chars();
3378    let valid_identifier = characters
3379        .next()
3380        .is_some_and(|character| character.is_ascii_alphabetic() || matches!(character, '_' | '$'))
3381        && characters
3382            .all(|character| character.is_ascii_alphanumeric() || matches!(character, '_' | '$'));
3383    if valid_identifier {
3384        value.to_owned()
3385    } else {
3386        quote_string(value)
3387    }
3388}
3389
3390struct RustTypes {
3391    declarations: Vec<String>,
3392    declared: BTreeSet<String>,
3393}
3394
3395impl RustTypes {
3396    fn new() -> Self {
3397        Self {
3398            declarations: Vec::new(),
3399            declared: BTreeSet::new(),
3400        }
3401    }
3402
3403    fn object(&mut self, name: &str, fields: &[FieldIr]) -> String {
3404        if !self.declared.insert(name.to_owned()) {
3405            return name.to_owned();
3406        }
3407        let placeholder = self.declarations.len();
3408        self.declarations.push(String::new());
3409        let mut rendered_fields = Vec::new();
3410        for field in fields {
3411            let type_name = self.type_for_non_null(
3412                field.ty.non_null(),
3413                &format!("{name}{}", pascal_case(&field.name)),
3414            );
3415            let field_type = if field.required {
3416                if field.ty.is_nullable() {
3417                    format!("Option<{type_name}>")
3418                } else {
3419                    type_name
3420                }
3421            } else if field.ty.is_nullable() {
3422                format!("OptionalValue<{type_name}>")
3423            } else {
3424                format!("Option<{type_name}>")
3425            };
3426            let mut attributes = vec![format!(
3427                "    #[serde(rename = {})]",
3428                quote_string(&field.name)
3429            )];
3430            if !field.required {
3431                if field.ty.is_nullable() {
3432                    attributes.push("    #[serde(default)]".to_owned());
3433                    attributes
3434                        .push("    #[serde(skip_serializing_if = \"Option::is_none\")]".to_owned());
3435                    attributes.push(
3436                        "    #[serde(deserialize_with = \"lenso_contract_runtime::serde::deserialize_optional_value\")]"
3437                            .to_owned(),
3438                    );
3439                } else {
3440                    attributes
3441                        .push("    #[serde(skip_serializing_if = \"Option::is_none\")]".to_owned());
3442                }
3443            }
3444            if field.required {
3445                attributes
3446                    .push("    #[serde(deserialize_with = \"lenso_contract_runtime::serde::deserialize_required\")]".to_owned());
3447            }
3448            rendered_fields.push(format!(
3449                "{}\n    pub {}: {field_type},",
3450                attributes.join("\n"),
3451                rust_field_name(&field.name)
3452            ));
3453        }
3454        let debug_impl = rust_debug::render(
3455            name,
3456            fields.iter().map(|field| {
3457                (
3458                    field.name.as_str(),
3459                    rust_field_name(&field.name),
3460                    field.sensitive,
3461                )
3462            }),
3463        );
3464        let derives = if fields.is_empty() {
3465            "Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize"
3466        } else if debug_impl.is_some() {
3467            "Clone, PartialEq, serde::Serialize, serde::Deserialize"
3468        } else {
3469            "Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize"
3470        };
3471        self.declarations[placeholder] = format!(
3472            "#[derive({derives})]\npub struct {name} {{\n{}\n}}\n{debug_impl}",
3473            rendered_fields.join("\n"),
3474            debug_impl = debug_impl.unwrap_or_default(),
3475        );
3476        name.to_owned()
3477    }
3478
3479    fn enum_type(&mut self, name: &str, values: &[String]) -> String {
3480        if !self.declared.insert(name.to_owned()) {
3481            return name.to_owned();
3482        }
3483        let variants = values
3484            .iter()
3485            .map(|value| {
3486                format!(
3487                    "    #[serde(rename = {})]\n    {},",
3488                    quote_string(value),
3489                    pascal_case(value)
3490                )
3491            })
3492            .collect::<Vec<_>>();
3493        self.declarations.push(format!(
3494            "#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum {name} {{\n{}\n}}\n",
3495            variants.join("\n")
3496        ));
3497        name.to_owned()
3498    }
3499
3500    fn type_for(&mut self, ty: &TypeIr, nested_name: &str) -> String {
3501        let base = self.type_for_non_null(ty.non_null(), nested_name);
3502        if ty.is_nullable() {
3503            format!("Option<{base}>")
3504        } else {
3505            base
3506        }
3507    }
3508
3509    fn type_for_non_null(&mut self, ty: &TypeIr, nested_name: &str) -> String {
3510        match ty {
3511            TypeIr::Any => "serde_json::Value".to_owned(),
3512            TypeIr::String => "String".to_owned(),
3513            TypeIr::RawJson => "RawJson".to_owned(),
3514            TypeIr::Enum { name, values } => {
3515                self.enum_type(name.as_deref().unwrap_or(nested_name), values)
3516            }
3517            TypeIr::Int64 => "Int64".to_owned(),
3518            TypeIr::Uint64 => "Uint64".to_owned(),
3519            TypeIr::Bytes => "Bytes".to_owned(),
3520            TypeIr::Timestamp => "Timestamp".to_owned(),
3521            TypeIr::Duration => "Duration".to_owned(),
3522            TypeIr::Integer => "i64".to_owned(),
3523            TypeIr::Number => "f64".to_owned(),
3524            TypeIr::Boolean => "bool".to_owned(),
3525            TypeIr::Null => "()".to_owned(),
3526            TypeIr::Array(items) => {
3527                format!(
3528                    "Vec<{}>",
3529                    self.type_for(items, &format!("{nested_name}Item"))
3530                )
3531            }
3532            TypeIr::Object {
3533                name,
3534                fields,
3535                additional,
3536            } => {
3537                let nested_name = name.as_deref().unwrap_or(nested_name);
3538                if fields.is_empty() {
3539                    match additional {
3540                        ObjectAdditionalIr::Closed => self.object(nested_name, fields),
3541                        ObjectAdditionalIr::Any => {
3542                            "std::collections::BTreeMap<String, serde_json::Value>".to_owned()
3543                        }
3544                        ObjectAdditionalIr::Typed(values) => format!(
3545                            "std::collections::BTreeMap<String, {}>",
3546                            self.type_for(values, &format!("{nested_name}Value"))
3547                        ),
3548                    }
3549                } else {
3550                    self.object(nested_name, fields)
3551                }
3552            }
3553            TypeIr::Nullable(inner) => {
3554                format!("Option<{}>", self.type_for_non_null(inner, nested_name))
3555            }
3556        }
3557    }
3558}
3559
3560struct TypeScriptTypes {
3561    declarations: Vec<String>,
3562    declared: BTreeSet<String>,
3563}
3564
3565impl TypeScriptTypes {
3566    fn new() -> Self {
3567        Self {
3568            declarations: Vec::new(),
3569            declared: BTreeSet::new(),
3570        }
3571    }
3572
3573    fn object(&mut self, name: &str, fields: &[FieldIr]) -> String {
3574        if !self.declared.insert(name.to_owned()) {
3575            return name.to_owned();
3576        }
3577        let placeholder = self.declarations.len();
3578        self.declarations.push(String::new());
3579        let mut rendered_fields = Vec::new();
3580        for field in fields {
3581            let type_name = self.type_for_non_null(
3582                field.ty.non_null(),
3583                &format!("{name}{}", pascal_case(&field.name)),
3584            );
3585            let optional = if field.required { "" } else { "?" };
3586            let field_type = if field.ty.is_nullable() {
3587                format!("{type_name} | null")
3588            } else {
3589                type_name
3590            };
3591            rendered_fields.push(format!(
3592                "  {}{optional}: {field_type};",
3593                typescript_property_name(&field.name)
3594            ));
3595        }
3596        self.declarations[placeholder] = format!(
3597            "export interface {name} {{\n{}\n}}\n",
3598            rendered_fields.join("\n")
3599        );
3600        name.to_owned()
3601    }
3602
3603    fn type_for(&mut self, ty: &TypeIr, nested_name: &str) -> String {
3604        let base = self.type_for_non_null(ty.non_null(), nested_name);
3605        if ty.is_nullable() {
3606            format!("{base} | null")
3607        } else {
3608            base
3609        }
3610    }
3611
3612    fn type_for_non_null(&mut self, ty: &TypeIr, nested_name: &str) -> String {
3613        match ty {
3614            TypeIr::Any => "unknown".to_owned(),
3615            TypeIr::String | TypeIr::RawJson => "string".to_owned(),
3616            TypeIr::Int64 => "Int64".to_owned(),
3617            TypeIr::Uint64 => "Uint64".to_owned(),
3618            TypeIr::Bytes => "Bytes".to_owned(),
3619            TypeIr::Timestamp => "Timestamp".to_owned(),
3620            TypeIr::Duration => "Duration".to_owned(),
3621            TypeIr::Integer | TypeIr::Number => "number".to_owned(),
3622            TypeIr::Boolean => "boolean".to_owned(),
3623            TypeIr::Null => "null".to_owned(),
3624            TypeIr::Enum { values, .. } => values
3625                .iter()
3626                .map(|value| quote_string(value))
3627                .collect::<Vec<_>>()
3628                .join(" | "),
3629            TypeIr::Array(items) => {
3630                format!(
3631                    "Array<{}>",
3632                    self.type_for(items, &format!("{nested_name}Item"))
3633                )
3634            }
3635            TypeIr::Object {
3636                name,
3637                fields,
3638                additional,
3639            } => {
3640                let nested_name = name.as_deref().unwrap_or(nested_name);
3641                if fields.is_empty() {
3642                    match additional {
3643                        ObjectAdditionalIr::Closed => self.object(nested_name, fields),
3644                        ObjectAdditionalIr::Any => "Record<string, unknown>".to_owned(),
3645                        ObjectAdditionalIr::Typed(values) => format!(
3646                            "Record<string, {}>",
3647                            self.type_for(values, &format!("{nested_name}Value"))
3648                        ),
3649                    }
3650                } else {
3651                    self.object(nested_name, fields)
3652                }
3653            }
3654            TypeIr::Nullable(inner) => {
3655                format!("{} | null", self.type_for_non_null(inner, nested_name))
3656            }
3657        }
3658    }
3659}
3660
3661#[allow(clippy::too_many_lines)]
3662fn generate_rust(contract: &ContractIr) -> String {
3663    let capability_name = pascal_case(
3664        contract
3665            .capability_id
3666            .split('@')
3667            .next()
3668            .and_then(|identity| identity.rsplit('.').next())
3669            .unwrap_or("Capability"),
3670    );
3671    let capability_const = screaming_snake_case(&capability_name);
3672    let native_support_name = format!("__LensoNativeSupport{capability_name}");
3673    let mut types = RustTypes::new();
3674    let mut operation_rows = Vec::new();
3675    let mut stream_operation_rows = Vec::new();
3676    let mut event_operation_rows = Vec::new();
3677    let mut operation_markers = Vec::new();
3678    let mut provider_methods = Vec::new();
3679    let mut provider_lowering_methods = Vec::new();
3680    let mut object_provider_lowering_methods = Vec::new();
3681    let mut trait_object_provider_lowering_methods = Vec::new();
3682    let mut provider_result_conversions = Vec::new();
3683    let mut endpoint_arms = Vec::new();
3684    let mut stream_endpoint_arms = Vec::new();
3685    let mut event_endpoint_arms = Vec::new();
3686    let mut client_fields = Vec::new();
3687    let mut client_initializers = Vec::new();
3688    let mut many_client_initializers = Vec::new();
3689    let mut client_methods = Vec::new();
3690    let mut invocation_errors = Vec::new();
3691    let mut error_codecs = Vec::new();
3692    let mut wire_codecs = Vec::new();
3693
3694    for operation in &contract.operations {
3695        let operation_name = pascal_case(&operation.name);
3696        let request_name = format!("{operation_name}Request");
3697        let response_name = format!("{operation_name}Response");
3698        let error_name = format!("{operation_name}Error");
3699        let marker_name = if contract.operations.len() == 1 {
3700            capability_name.clone()
3701        } else {
3702            format!("{capability_name}{operation_name}")
3703        };
3704        let request_type = types.type_for(&operation.request, &request_name);
3705        let response_type = types.type_for(&operation.response, &response_name);
3706        let known_errors = &operation.domain_errors;
3707        let error_definition = if known_errors.is_empty() {
3708            format!(
3709                "#[derive(Clone, Debug, PartialEq)]\npub enum {error_name} {{\n    Unknown(UnknownDomainError),\n}}\n"
3710            )
3711        } else {
3712            let variants = known_errors
3713                .iter()
3714                .map(|variant| {
3715                    if let Some(payload) = &variant.payload {
3716                        let payload_name = format!("{error_name}{}Payload", variant.name);
3717                        let payload_type =
3718                            types.type_for_non_null(payload.non_null(), &payload_name);
3719                        let payload_type = if variant.payload_required {
3720                            if payload.is_nullable() {
3721                                format!("Option<{payload_type}>")
3722                            } else {
3723                                payload_type
3724                            }
3725                        } else if payload.is_nullable() {
3726                            format!("OptionalValue<{payload_type}>")
3727                        } else {
3728                            format!("Option<{payload_type}>")
3729                        };
3730                        format!("    {} {{ payload: {payload_type} }},", variant.name)
3731                    } else {
3732                        format!("    {},", variant.name)
3733                    }
3734                })
3735                .chain(std::iter::once(
3736                    "    Unknown(UnknownDomainError),".to_owned(),
3737                ))
3738                .collect::<Vec<_>>();
3739            format!(
3740                "#[derive(Clone, Debug, PartialEq)]\npub enum {error_name} {{\n{}\n}}\n",
3741                variants.join("\n")
3742            )
3743        };
3744        types.declarations.push(error_definition);
3745        error_codecs.push(generate_rust_error_codec(&error_name, known_errors));
3746        wire_codecs.push(generate_rust_wire_codecs(
3747            &operation.name,
3748            &request_type,
3749            &response_type,
3750            &error_name,
3751        ));
3752        if operation.interaction == "event" {
3753            wire_codecs.push(generate_rust_event_codecs(&operation.name, &request_type));
3754        }
3755        let operation_const = screaming_snake_case(&operation.name);
3756        operation_markers.push(match operation.interaction.as_str() {
3757            "request" => {
3758                let provider_method = rust_field_name(&operation.name);
3759                format!(
3760            "#[derive(Debug)]\npub struct {marker_name};\nimpl RequestCapability for {marker_name} {{\n    type Request = {request_type};\n    type Response = {response_type};\n    type DomainError = {error_name};\n    const ID: &'static str = CAPABILITY_ID;\n    const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;\n\n    fn invoke_native(endpoint: &dyn NativeRequestEndpoint, operation: &str, request: Self::Request, context: InvocationContext) -> NativeRequestFuture<Self> {{\n        if operation != {operation_const}_OPERATION {{\n            return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);\n        }}\n        let Some(typed_endpoint) = endpoint\n            .typed_endpoint()\n            .and_then(|endpoint| endpoint.downcast_ref::<{capability_name}RequestEndpoint>())\n        else {{\n            return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);\n        }};\n        Rc::clone(&typed_endpoint.provider).{provider_method}(context, request)\n    }}\n}}\n"
3761                )
3762            }
3763            "stream" => format!(
3764                "#[derive(Debug)]\npub struct {marker_name};\npub type {marker_name}Event = StreamEvent<{response_type}, {error_name}>;\nimpl StreamCapability for {marker_name} {{\n    type OpenRequest = {request_type};\n    type Message = {response_type};\n    type DomainError = {error_name};\n    const ID: &'static str = CAPABILITY_ID;\n    const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;\n}}\n"
3765            ),
3766            "event" => format!(
3767                "#[derive(Debug)]\npub struct {marker_name};\nimpl EventCapability for {marker_name} {{\n    type Event = {request_type};\n    const ID: &'static str = CAPABILITY_ID;\n    const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;\n}}\n"
3768            ),
3769            _ => unreachable!("Descriptor validation restricts interactions"),
3770        });
3771        if operation.interaction == "request" {
3772            let invocation_error_name = if contract.operations.len() == 1 {
3773                format!("{capability_name}InvocationError")
3774            } else {
3775                format!("{capability_name}{operation_name}InvocationError")
3776            };
3777            operation_rows.push(format!("        {operation_const}_OPERATION,\n"));
3778            provider_methods.push(format!(
3779                "    fn {}(&self, context: InvocationContext, request: {request_type}) -> NativeRequestFuture<{marker_name}>;",
3780                rust_field_name(&operation.name),
3781            ));
3782            let field = rust_field_name(&operation.name);
3783            let conversion = format!("__LensoInto{capability_name}{operation_name}Result");
3784            provider_result_conversions.push(format!(
3785                "#[doc(hidden)]\npub trait {conversion} {{\n    fn __lenso_into_result(self) -> Result<Result<{response_type}, {error_name}>, RuntimeFailure>;\n}}\nimpl {conversion} for Result<{response_type}, {error_name}> {{\n    fn __lenso_into_result(self) -> Result<Result<{response_type}, {error_name}>, RuntimeFailure> {{ Ok(self) }}\n}}\nimpl {conversion} for Result<Result<{response_type}, {error_name}>, RuntimeFailure> {{\n    fn __lenso_into_result(self) -> Result<Result<{response_type}, {error_name}>, RuntimeFailure> {{ self }}\n}}\nimpl {conversion} for Result<{response_type}, lenso_plugin_authoring::PluginError<{error_name}, RuntimeFailure>> {{\n    fn __lenso_into_result(self) -> Result<Result<{response_type}, {error_name}>, RuntimeFailure> {{\n        match self {{\n            Ok(value) => Ok(Ok(value)),\n            Err(lenso_plugin_authoring::PluginError::Domain(error)) => Ok(Err(error)),\n            Err(lenso_plugin_authoring::PluginError::Runtime(error)) => Err(error),\n        }}\n    }}\n}}\nimpl {conversion} for Result<{response_type}, {invocation_error_name}> {{\n    fn __lenso_into_result(self) -> Result<Result<{response_type}, {error_name}>, RuntimeFailure> {{\n        match self {{\n            Ok(value) => Ok(Ok(value)),\n            Err({invocation_error_name}::Domain(error)) => Ok(Err(error)),\n            Err({invocation_error_name}::Runtime(error)) => Err(error),\n        }}\n    }}\n}}\n"
3786            ));
3787            provider_lowering_methods.push(format!(
3788                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::NativeRequestFuture<$crate::{marker_name}> {{\n            let plugin = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let result = <$plugin>::{field}(&plugin, context, request).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3789            ));
3790            object_provider_lowering_methods.push(format!(
3791                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::NativeRequestFuture<$crate::{marker_name}> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get()?;\n                let result = <$plugin>::{field}(plugin.as_ref(), context, request).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3792            ));
3793            trait_object_provider_lowering_methods.push(format!(
3794                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::NativeRequestFuture<$crate::{marker_name}> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get()?;\n                <$plugin as $crate::{capability_name}Provider>::{field}(plugin.as_ref(), context, request).await\n            }})\n        }}"
3795            ));
3796            endpoint_arms.push(format!(
3797                "            {operation_const}_OPERATION => {{\n                let Ok(request) = request.downcast::<{request_type}>() else {{\n                    return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation {{ capability: CAPABILITY_ID }})));\n                }};\n                let invocation = Rc::clone(&self.provider).{}(context, *request);\n                Box::pin(async move {{\n                    invocation.await.map(|result| {{\n                        result\n                            .map(|value| Box::new(value) as Box<dyn std::any::Any>)\n                            .map_err(|error| Box::new(error) as Box<dyn std::any::Any>)\n                    }})\n                }})\n            }}",
3798                rust_field_name(&operation.name),
3799            ));
3800            let field = rust_field_name(&operation.name);
3801            client_fields.push(format!("    {field}: NativeRequestHandle<{marker_name}>,"));
3802            client_initializers.push(format!(
3803                "            {field}: dependencies.one::<{marker_name}>()?,"
3804            ));
3805            many_client_initializers.push(format!(
3806                "                    {field}: binding.handle().ok_or(RuntimeFailure::Unavailable {{ capability: CAPABILITY_ID }})?.typed::<{marker_name}>()?,"
3807            ));
3808            client_methods.push(format!(
3809                "    pub async fn {field}(&self, request: {request_type}) -> Result<{response_type}, {invocation_error_name}> {{\n        self.{field}.invoke({}_OPERATION, request).await\n            .map_err({invocation_error_name}::Runtime)?\n            .map_err({invocation_error_name}::Domain)\n    }}\n\n    pub async fn {field}_with_context(&self, context: InvocationContext, request: {request_type}) -> Result<{response_type}, {invocation_error_name}> {{\n        self.{field}.invoke_with_context({}_OPERATION, context, request).await\n            .map_err({invocation_error_name}::Runtime)?\n            .map_err({invocation_error_name}::Domain)\n    }}",
3810                screaming_snake_case(&operation.name),
3811                screaming_snake_case(&operation.name),
3812            ));
3813            invocation_errors.push(format!(
3814                "#[derive(Clone, Debug, PartialEq)]\npub enum {invocation_error_name} {{\n    Domain({error_name}),\n    Runtime(RuntimeFailure),\n}}\n"
3815            ));
3816        } else if operation.interaction == "stream" {
3817            let invocation_error_name = if contract.operations.len() == 1 {
3818                format!("{capability_name}InvocationError")
3819            } else {
3820                format!("{capability_name}{operation_name}InvocationError")
3821            };
3822            stream_operation_rows.push(format!("        {operation_const}_OPERATION,\n"));
3823            provider_methods.push(format!(
3824                "    fn {}(&self, context: InvocationContext, request: {request_type}) -> LocalBoxFuture<'static, Result<Box<dyn NativeStreamSession>, {invocation_error_name}>>;",
3825                rust_field_name(&operation.name)
3826            ));
3827            let field = rust_field_name(&operation.name);
3828            let conversion = format!("__LensoInto{capability_name}{operation_name}StreamResult");
3829            provider_result_conversions.push(format!(
3830                "#[doc(hidden)]\npub trait {conversion} {{\n    fn __lenso_into_result(self) -> Result<Box<dyn NativeStreamSession>, {invocation_error_name}>;\n}}\nimpl<S> {conversion} for Result<S, {error_name}>\nwhere\n    S: NativeStreamSession + 'static,\n{{\n    fn __lenso_into_result(self) -> Result<Box<dyn NativeStreamSession>, {invocation_error_name}> {{\n        self.map(|stream| Box::new(stream) as Box<dyn NativeStreamSession>)\n            .map_err({invocation_error_name}::Domain)\n    }}\n}}\nimpl<S> {conversion} for Result<S, lenso_plugin_authoring::PluginError<{error_name}, RuntimeFailure>>\nwhere\n    S: NativeStreamSession + 'static,\n{{\n    fn __lenso_into_result(self) -> Result<Box<dyn NativeStreamSession>, {invocation_error_name}> {{\n        match self {{\n            Ok(stream) => Ok(Box::new(stream) as Box<dyn NativeStreamSession>),\n            Err(lenso_plugin_authoring::PluginError::Domain(error)) => Err({invocation_error_name}::Domain(error)),\n            Err(lenso_plugin_authoring::PluginError::Runtime(error)) => Err({invocation_error_name}::Runtime(error)),\n        }}\n    }}\n}}\nimpl<S> {conversion} for Result<S, {invocation_error_name}>\nwhere\n    S: NativeStreamSession + 'static,\n{{\n    fn __lenso_into_result(self) -> Result<Box<dyn NativeStreamSession>, {invocation_error_name}> {{\n        self.map(|stream| Box::new(stream) as Box<dyn NativeStreamSession>)\n    }}\n}}\n"
3831            ));
3832            provider_lowering_methods.push(format!(
3833                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<Box<dyn {native_support_name}::NativeStreamSession>, $crate::{invocation_error_name}>> {{\n            let plugin = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let result = <$plugin>::{field}(&plugin, context, request).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3834            ));
3835            object_provider_lowering_methods.push(format!(
3836                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<Box<dyn {native_support_name}::NativeStreamSession>, $crate::{invocation_error_name}>> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get().map_err($crate::{invocation_error_name}::Runtime)?;\n                let result = <$plugin>::{field}(plugin.as_ref(), context, request).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3837            ));
3838            trait_object_provider_lowering_methods.push(format!(
3839                "        fn {field}(&self, context: {native_support_name}::InvocationContext, request: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<Box<dyn {native_support_name}::NativeStreamSession>, $crate::{invocation_error_name}>> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get().map_err($crate::{invocation_error_name}::Runtime)?;\n                <$plugin as $crate::{capability_name}Provider>::{field}(plugin.as_ref(), context, request).await\n            }})\n        }}"
3840            ));
3841            stream_endpoint_arms.push(format!(
3842                "            {operation_const}_OPERATION => {{\n                let Ok(request) = request.downcast::<{request_type}>() else {{\n                    return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation {{ capability: CAPABILITY_ID }})));\n                }};\n                let provider = Rc::clone(&self.provider);\n                Box::pin(async move {{\n                    match provider.{}(context, *request).await {{\n                        Ok(value) => Ok(Ok(value as Box<dyn NativeStreamSession>)),\n                        Err({invocation_error_name}::Domain(error)) => Ok(Err(Box::new(error) as Box<dyn std::any::Any>)),\n                        Err({invocation_error_name}::Runtime(error)) => Err(error),\n                    }}\n                }})\n            }}",
3843                rust_field_name(&operation.name),
3844            ));
3845            let field = rust_field_name(&operation.name);
3846            client_fields.push(format!("    {field}: NativeStreamHandle<{marker_name}>,"));
3847            client_initializers.push(format!(
3848                "            {field}: dependencies.one_stream::<{marker_name}>()?,"
3849            ));
3850            many_client_initializers.push(format!(
3851                "                    {field}: binding.stream_handle().ok_or(RuntimeFailure::Unavailable {{ capability: CAPABILITY_ID }})?.typed::<{marker_name}>()?,"
3852            ));
3853            client_methods.push(format!(
3854                "    pub async fn {field}(&self, request: {request_type}) -> Result<NativeStream<{marker_name}>, {invocation_error_name}> {{\n        self.{field}.open({operation_const}_OPERATION, request).await\n            .map_err({invocation_error_name}::Runtime)?\n            .map_err({invocation_error_name}::Domain)\n    }}\n\n    pub async fn {field}_with_context(&self, context: InvocationContext, request: {request_type}) -> Result<NativeStream<{marker_name}>, {invocation_error_name}> {{\n        self.{field}.open_with_context({operation_const}_OPERATION, context, request).await\n            .map_err({invocation_error_name}::Runtime)?\n            .map_err({invocation_error_name}::Domain)\n    }}"
3855            ));
3856            invocation_errors.push(format!(
3857                "#[derive(Clone, Debug, PartialEq)]\npub enum {invocation_error_name} {{\n    Domain({error_name}),\n    Runtime(RuntimeFailure),\n}}\n"
3858            ));
3859        } else {
3860            event_operation_rows.push(format!("        {operation_const}_OPERATION,\n"));
3861            provider_methods.push(format!(
3862                "    fn {}(&self, context: InvocationContext, event: {request_type}) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>;",
3863                rust_field_name(&operation.name)
3864            ));
3865            let field = rust_field_name(&operation.name);
3866            let conversion = format!("__LensoInto{capability_name}{operation_name}EventResult");
3867            provider_result_conversions.push(format!(
3868                "#[doc(hidden)]\npub trait {conversion} {{\n    fn __lenso_into_result(self) -> Result<(), RuntimeFailure>;\n}}\nimpl {conversion} for () {{\n    fn __lenso_into_result(self) -> Result<(), RuntimeFailure> {{ Ok(()) }}\n}}\nimpl {conversion} for Result<(), RuntimeFailure> {{\n    fn __lenso_into_result(self) -> Result<(), RuntimeFailure> {{ self }}\n}}\n"
3869            ));
3870            provider_lowering_methods.push(format!(
3871                "        fn {field}(&self, context: {native_support_name}::InvocationContext, event: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<(), {native_support_name}::RuntimeFailure>> {{\n            let plugin = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let result = <$plugin>::{field}(&plugin, context, event).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3872            ));
3873            object_provider_lowering_methods.push(format!(
3874                "        fn {field}(&self, context: {native_support_name}::InvocationContext, event: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<(), {native_support_name}::RuntimeFailure>> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get()?;\n                let result = <$plugin>::{field}(plugin.as_ref(), context, event).await;\n                $crate::{conversion}::__lenso_into_result(result)\n            }})\n        }}"
3875            ));
3876            trait_object_provider_lowering_methods.push(format!(
3877                "        fn {field}(&self, context: {native_support_name}::InvocationContext, event: $crate::{request_type}) -> {native_support_name}::LocalBoxFuture<'static, Result<(), {native_support_name}::RuntimeFailure>> {{\n            let object = self.clone();\n            ::std::boxed::Box::pin(async move {{\n                let plugin = object.get()?;\n                <$plugin as $crate::{capability_name}Provider>::{field}(plugin.as_ref(), context, event).await\n            }})\n        }}"
3878            ));
3879            event_endpoint_arms.push(format!(
3880                "            {operation_const}_OPERATION => {{\n                let Ok(event) = event.downcast::<{request_type}>() else {{\n                    return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation {{ capability: CAPABILITY_ID }})));\n                }};\n                Rc::clone(&self.provider).{}(context, *event)\n            }}",
3881                rust_field_name(&operation.name),
3882            ));
3883            let field = rust_field_name(&operation.name);
3884            client_fields.push(format!("    {field}: NativeEventHandle<{marker_name}>,"));
3885            client_initializers.push(format!(
3886                "            {field}: dependencies.many_event::<{marker_name}>()?,"
3887            ));
3888            many_client_initializers.push(format!(
3889                "                    {field}: binding.event_handle().ok_or(RuntimeFailure::Unavailable {{ capability: CAPABILITY_ID }})?.typed::<{marker_name}>()?,"
3890            ));
3891            client_methods.push(format!(
3892                "    pub async fn {field}(&self, event: {request_type}) -> Vec<EventPublishResult> {{\n        self.{field}.publish({operation_const}_OPERATION, event).await\n    }}\n\n    pub async fn {field}_with_context(&self, context: InvocationContext, event: {request_type}) -> Vec<EventPublishResult> {{\n        self.{field}.publish_with_context({operation_const}_OPERATION, context, event).await\n    }}"
3893            ));
3894        }
3895    }
3896
3897    let request_endpoint_impl = if operation_rows.is_empty() {
3898        String::new()
3899    } else {
3900        format!(
3901            "impl<P: {capability_name}Provider> NativeRequestEndpoint for {capability_name}Endpoint<P> {{\n    fn capability_id(&self) -> &'static str {{ CAPABILITY_ID }}\n    fn descriptor_version(&self) -> &'static str {{ DESCRIPTOR_VERSION }}\n    fn operations(&self) -> &'static [&'static str] {{ &[\n{}    ] }}\n    fn typed_endpoint(&self) -> Option<&dyn std::any::Any> {{ Some(&self.request_endpoint) }}\n    fn invoke(&self, operation: &str, request: Box<dyn std::any::Any>, context: InvocationContext) -> LocalBoxFuture<'static, Result<Result<Box<dyn std::any::Any>, Box<dyn std::any::Any>>, RuntimeFailure>> {{\n        match operation {{\n{}\n            _ => Box::pin(futures::future::ready(Err(RuntimeFailure::UnknownOperation {{ capability: CAPABILITY_ID, operation: operation.to_owned() }}))),\n        }}\n    }}\n}}\n\n",
3902            operation_rows.concat(),
3903            endpoint_arms.join(",\n")
3904        )
3905    };
3906    let stream_endpoint_impl = if stream_operation_rows.is_empty() {
3907        String::new()
3908    } else {
3909        format!(
3910            "impl<P: {capability_name}Provider> NativeStreamEndpoint for {capability_name}Endpoint<P> {{\n    fn capability_id(&self) -> &'static str {{ CAPABILITY_ID }}\n    fn descriptor_version(&self) -> &'static str {{ DESCRIPTOR_VERSION }}\n    fn operations(&self) -> &'static [&'static str] {{ &[\n{}    ] }}\n    fn open(&self, operation: &str, request: Box<dyn std::any::Any>, context: InvocationContext) -> LocalBoxFuture<'static, Result<Result<Box<dyn NativeStreamSession>, Box<dyn std::any::Any>>, RuntimeFailure>> {{\n        match operation {{\n{}\n            _ => Box::pin(futures::future::ready(Err(RuntimeFailure::UnknownOperation {{ capability: CAPABILITY_ID, operation: operation.to_owned() }}))),\n        }}\n    }}\n}}\n\n",
3911            stream_operation_rows.concat(),
3912            stream_endpoint_arms.join(",\n")
3913        )
3914    };
3915    let event_endpoint_impl = if event_operation_rows.is_empty() {
3916        String::new()
3917    } else {
3918        format!(
3919            "impl<P: {capability_name}Provider> NativeEventEndpoint for {capability_name}Endpoint<P> {{\n    fn capability_id(&self) -> &'static str {{ CAPABILITY_ID }}\n    fn descriptor_version(&self) -> &'static str {{ DESCRIPTOR_VERSION }}\n    fn operations(&self) -> &'static [&'static str] {{ &[\n{}    ] }}\n    fn publish(&self, operation: &str, event: Box<dyn std::any::Any>, context: InvocationContext) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {{\n        match operation {{\n{}\n            _ => Box::pin(futures::future::ready(Err(RuntimeFailure::UnknownOperation {{ capability: CAPABILITY_ID, operation: operation.to_owned() }}))),\n        }}\n    }}\n}}\n\n",
3920            event_operation_rows.concat(),
3921            event_endpoint_arms.join(",\n")
3922        )
3923    };
3924    let mut output = String::new();
3925    output.push_str(GENERATED_HEADER);
3926    let has_request_operations = !operation_rows.is_empty();
3927    let has_stream_operations = !stream_operation_rows.is_empty();
3928    let has_event_operations = !event_operation_rows.is_empty();
3929    let mut kernel_imports = vec!["InvocationContext", "PluginDependencies", "RuntimeFailure"];
3930    if has_request_operations {
3931        kernel_imports.extend([
3932            "NativeRequestFuture",
3933            "NativeRequestEndpoint",
3934            "NativeRequestHandle",
3935            "RequestCapability",
3936        ]);
3937    }
3938    if has_stream_operations {
3939        kernel_imports.extend([
3940            "NativeStream",
3941            "NativeStreamEndpoint",
3942            "NativeStreamHandle",
3943            "NativeStreamSession",
3944            "StreamCapability",
3945            "StreamEvent",
3946        ]);
3947    }
3948    if has_event_operations {
3949        kernel_imports.extend([
3950            "EventCapability",
3951            "EventPublishResult",
3952            "NativeEventEndpoint",
3953            "NativeEventHandle",
3954        ]);
3955    }
3956    kernel_imports.sort_unstable();
3957    writeln!(
3958        output,
3959        "use std::{{fmt, rc::Rc}};\nuse futures::future::LocalBoxFuture;\nuse lenso_kernel::{{{}}};\n",
3960        kernel_imports.join(", ")
3961    )
3962    .expect("writing to a String cannot fail");
3963    output.push_str(
3964        "use lenso_plugin_authoring::{BoundCapabilityClient, CapabilityClient, CapabilityClientMany, CapabilityReference};\n",
3965    );
3966    writeln!(
3967        output,
3968        "pub const CAPABILITY_ID: &str = {};",
3969        quote_string(&contract.capability_id)
3970    )
3971    .expect("writing to a String cannot fail");
3972    writeln!(
3973        output,
3974        "pub const DESCRIPTOR_VERSION: &str = {};",
3975        quote_string(&contract.version)
3976    )
3977    .expect("writing to a String cannot fail");
3978    writeln!(
3979        output,
3980        "pub const DESCRIPTOR_DIGEST: &str = {};",
3981        quote_string(&contract.descriptor_digest)
3982    )
3983    .expect("writing to a String cannot fail");
3984    writeln!(output, "pub const PORTABLE: bool = {};", contract.portable)
3985        .expect("writing to a String cannot fail");
3986    writeln!(
3987        output,
3988        "pub const CROSS_LANE_TRANSFER: bool = {};",
3989        contract.cross_lane_transfer
3990    )
3991    .expect("writing to a String cannot fail");
3992    writeln!(
3993        output,
3994        "pub const {capability_const}_CAPABILITY_ID: &str = CAPABILITY_ID;"
3995    )
3996    .expect("writing to a String cannot fail");
3997    write!(
3998        output,
3999        "pub const {capability_const}_DESCRIPTOR_VERSION: &str = DESCRIPTOR_VERSION;\npub const {capability_const}_DESCRIPTOR_DIGEST: &str = DESCRIPTOR_DIGEST;\npub const {capability_const}_CONTRACT: CapabilityReference<{capability_name}Client> = CapabilityReference::new(CAPABILITY_ID, DESCRIPTOR_VERSION, DESCRIPTOR_DIGEST);\n\n"
4000    )
4001    .expect("writing to a String cannot fail");
4002
4003    let capability_macro_name = snake_case(&capability_name);
4004    let client_macro_name = snake_case(&format!("{capability_name}Client"));
4005    let operations = contract
4006        .operations
4007        .iter()
4008        .map(|operation| Value::String(operation.name.clone()))
4009        .collect::<Vec<_>>();
4010    let operation_kinds = contract
4011        .operations
4012        .iter()
4013        .filter(|operation| operation.interaction != "request")
4014        .map(|operation| {
4015            (
4016                operation.name.clone(),
4017                Value::String(operation.interaction.clone()),
4018            )
4019        })
4020        .collect::<Map<_, _>>();
4021    let provided_fragment = canonical_json(&serde_json::json!({
4022        "capability_id": contract.capability_id,
4023        "descriptor_version": contract.version,
4024        "operations": operations,
4025        "operation_kinds": operation_kinds,
4026        "default_admission": {
4027            "queue_capacity": 0,
4028            "max_concurrency": 1
4029        },
4030        "operation_admissions": {},
4031        "event_admission": null,
4032        "cross_lane_transfer": contract.cross_lane_transfer
4033    }));
4034    let required_fragment = canonical_json(&serde_json::json!({
4035        "capability_id": contract.capability_id,
4036        "descriptor_version": contract.version,
4037        "cardinality": "one"
4038    }));
4039    let required_many_fragment = canonical_json(&serde_json::json!({
4040        "capability_id": contract.capability_id,
4041        "descriptor_version": contract.version,
4042        "cardinality": "many"
4043    }));
4044    let named_requirement_suffix = |cardinality: &str| {
4045        format!(
4046            ",\"capability_id\":{},\"descriptor_version\":{},\"cardinality\":{}}}",
4047            quote_string(&contract.capability_id),
4048            quote_string(&contract.version),
4049            quote_string(cardinality),
4050        )
4051    };
4052    let required_named_suffix = named_requirement_suffix("one");
4053    let required_optional_named_suffix = named_requirement_suffix("optional");
4054    let required_many_named_suffix = named_requirement_suffix("many");
4055    writeln!(
4056        output,
4057        "#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_provided_{capability_macro_name} {{ () => {{ {} }}; }}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_required_{client_macro_name} {{\n    () => {{ {} }};\n    ($requirement_id:literal) => {{ concat!(\"{{\\\"requirement_id\\\":\", stringify!($requirement_id), {}) }};\n}}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_required_optional_{client_macro_name} {{\n    ($requirement_id:literal) => {{ concat!(\"{{\\\"requirement_id\\\":\", stringify!($requirement_id), {}) }};\n}}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_required_many_{client_macro_name} {{\n    () => {{ {} }};\n    ($requirement_id:literal) => {{ concat!(\"{{\\\"requirement_id\\\":\", stringify!($requirement_id), {}) }};\n}}\n",
4058        quote_string(&provided_fragment),
4059        quote_string(&required_fragment),
4060        quote_string(&required_named_suffix),
4061        quote_string(&required_optional_named_suffix),
4062        quote_string(&required_many_fragment),
4063        quote_string(&required_many_named_suffix),
4064    )
4065    .expect("writing to a String cannot fail");
4066    for operation in &contract.operations {
4067        let operation_const = format!("{}_OPERATION", screaming_snake_case(&operation.name));
4068        writeln!(
4069            output,
4070            "pub const {operation_const}: &str = {};",
4071            quote_string(&operation.name)
4072        )
4073        .expect("writing to a String cannot fail");
4074    }
4075    writeln!(
4076        output,
4077        "\npub use lenso_contract_runtime::{{{}}};\nuse lenso_contract_runtime::{{decode_portable_json, encode_portable_json}};\n",
4078        rust_runtime_types(contract).join(", ")
4079    )
4080    .expect("writing to a String cannot fail");
4081    for declaration in types.declarations {
4082        output.push_str(&declaration);
4083        output.push('\n');
4084    }
4085    for marker in operation_markers {
4086        output.push_str(&marker);
4087        output.push('\n');
4088    }
4089    for codec in error_codecs {
4090        output.push_str(&codec);
4091        output.push('\n');
4092    }
4093    for codec in wire_codecs {
4094        output.push_str(&codec);
4095        output.push('\n');
4096    }
4097    for conversion in provider_result_conversions {
4098        output.push_str(&conversion);
4099        output.push('\n');
4100    }
4101    write!(
4102        output,
4103        "pub trait {capability_name}Provider: fmt::Debug + 'static {{\n{}\n}}\n\n",
4104        provider_methods.join("\n")
4105    )
4106    .expect("writing to a String cannot fail");
4107    writeln!(
4108        output,
4109        "#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_native_lower_{capability_macro_name} {{\n    ($plugin:ty, $support:path) => {{\n        use $support as {native_support_name};\n        impl $crate::{capability_name}Provider for $plugin {{\n{}\n        }}\n    }};\n}}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_native_lower_object_{capability_macro_name} {{\n    ($object:ty, $plugin:ty, $support:path) => {{\n        use $support as {native_support_name};\n        impl $crate::{capability_name}Provider for $object {{\n{}\n        }}\n    }};\n}}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_native_lower_trait_object_{capability_macro_name} {{\n    ($object:ty, $plugin:ty, $support:path) => {{\n        use $support as {native_support_name};\n        impl $crate::{capability_name}Provider for $object {{\n{}\n        }}\n    }};\n}}\n",
4110        provider_lowering_methods.join("\n"),
4111        object_provider_lowering_methods.join("\n"),
4112        trait_object_provider_lowering_methods.join("\n")
4113    )
4114    .expect("writing to a String cannot fail");
4115    if has_request_operations {
4116        write!(
4117            output,
4118            "#[derive(Debug)]\nstruct {capability_name}RequestEndpoint {{ provider: Rc<dyn {capability_name}Provider> }}\n\n#[derive(Debug)]\npub struct {capability_name}Endpoint<P: {capability_name}Provider> {{ provider: Rc<P>, request_endpoint: {capability_name}RequestEndpoint }}\nimpl<P: {capability_name}Provider> {capability_name}Endpoint<P> {{\n    pub fn new(provider: P) -> Self {{\n        let provider = Rc::new(provider);\n        let request_provider: Rc<dyn {capability_name}Provider> = provider.clone();\n        Self {{ provider, request_endpoint: {capability_name}RequestEndpoint {{ provider: request_provider }} }}\n    }}\n}}\n\n"
4119        )
4120        .expect("writing to a String cannot fail");
4121    } else {
4122        write!(
4123            output,
4124            "#[derive(Debug)]\npub struct {capability_name}Endpoint<P: {capability_name}Provider> {{ provider: Rc<P> }}\nimpl<P: {capability_name}Provider> {capability_name}Endpoint<P> {{\n    pub fn new(provider: P) -> Self {{ Self {{ provider: Rc::new(provider) }} }}\n}}\n\n"
4125        )
4126        .expect("writing to a String cannot fail");
4127    }
4128    output.push_str(&request_endpoint_impl);
4129    output.push_str(&stream_endpoint_impl);
4130    output.push_str(&event_endpoint_impl);
4131    let request_endpoint_value = has_request_operations.then_some(
4132        "endpoint.clone() as ::std::rc::Rc<dyn __LensoNativeSupport::NativeRequestEndpoint>",
4133    );
4134    let stream_endpoint_value = has_stream_operations.then_some(
4135        "endpoint.clone() as ::std::rc::Rc<dyn __LensoNativeSupport::NativeStreamEndpoint>",
4136    );
4137    let event_endpoint_value = has_event_operations
4138        .then_some("endpoint as ::std::rc::Rc<dyn __LensoNativeSupport::NativeEventEndpoint>");
4139    writeln!(
4140        output,
4141        "#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_native_endpoints_{capability_macro_name} {{\n    ($provider:expr, $support:path) => {{{{\n        use $support as __LensoNativeSupport;\n        let endpoint = ::std::rc::Rc::new($crate::{capability_name}Endpoint::new($provider));\n        (\n            vec![{}],\n            vec![{}],\n            vec![{}],\n        )\n    }}}};\n}}\n\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! __lenso_native_provide_{capability_macro_name} {{\n    ($provider:expr, $lifecycle:expr, $support:path) => {{{{\n        use $support as __LensoNativeSupport;\n        let (request_endpoints, stream_endpoints, event_endpoints) =\n            $crate::__lenso_native_endpoints_{capability_macro_name}!($provider, $support);\n        __LensoNativeSupport::NativePluginInstance::with_all_endpoints(\n            request_endpoints,\n            stream_endpoints,\n            event_endpoints,\n            $lifecycle,\n        )\n    }}}};\n}}\n",
4142        request_endpoint_value.unwrap_or_default(),
4143        stream_endpoint_value.unwrap_or_default(),
4144        event_endpoint_value.unwrap_or_default(),
4145    )
4146    .expect("writing to a String cannot fail");
4147    let new_method = if contract.operations.len() == 1 {
4148        let field = rust_field_name(&contract.operations[0].name);
4149        let marker = &capability_name;
4150        match contract.operations[0].interaction.as_str() {
4151            "request" => format!(
4152                "    pub fn new(handle: NativeRequestHandle<{marker}>) -> Self {{\n        Self {{ {field}: handle }}\n    }}\n\n"
4153            ),
4154            "stream" => format!(
4155                "    pub fn new(handle: NativeStreamHandle<{marker}>) -> Self {{\n        Self {{ {field}: handle }}\n    }}\n\n"
4156            ),
4157            "event" => format!(
4158                "    pub fn new(handle: NativeEventHandle<{marker}>) -> Self {{\n        Self {{ {field}: handle }}\n    }}\n\n"
4159            ),
4160            _ => unreachable!("Descriptor validation restricts interactions"),
4161        }
4162    } else {
4163        String::new()
4164    };
4165    write!(
4166        output,
4167        "#[derive(Clone, Debug)]\npub struct {capability_name}Client {{\n{}\n}}\nimpl {capability_name}Client {{\n{}    pub fn from_dependencies(dependencies: &PluginDependencies) -> Result<Self, RuntimeFailure> {{\n        <Self as CapabilityClient>::from_dependencies(dependencies)\n    }}\n\n    pub fn from_requirement(\n        dependencies: &PluginDependencies,\n        requirement_id: &str,\n    ) -> Result<Self, RuntimeFailure> {{\n        <Self as CapabilityClient>::from_requirement(dependencies, requirement_id)\n    }}\n\n{}\n}}\n\nimpl CapabilityClient for {capability_name}Client {{\n    type Dependencies = PluginDependencies;\n    type Error = RuntimeFailure;\n\n    const CAPABILITY_ID: &'static str = CAPABILITY_ID;\n    const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;\n\n    fn from_dependencies(dependencies: &PluginDependencies) -> Result<Self, RuntimeFailure> {{\n        Ok(Self {{\n{}\n        }})\n    }}\n\n    fn from_requirement(\n        dependencies: &PluginDependencies,\n        requirement_id: &str,\n    ) -> Result<Self, RuntimeFailure> {{\n        let dependencies = dependencies.requirement(requirement_id)?;\n        Self::from_dependencies(&dependencies)\n    }}\n\n    fn already_connected() -> RuntimeFailure {{\n        RuntimeFailure::PluginFailure {{\n            detail: format!(\"Capability Port {{CAPABILITY_ID}} was connected more than once\"),\n        }}\n    }}\n}}\n\nimpl CapabilityClientMany for {capability_name}Client {{\n    fn many_from_dependencies(\n        dependencies: &PluginDependencies,\n    ) -> Result<Vec<BoundCapabilityClient<Self>>, RuntimeFailure> {{\n        dependencies\n            .bindings()\n            .iter()\n            .filter(|binding| binding.capability_id() == CAPABILITY_ID)\n            .map(|binding| {{\n                Ok(BoundCapabilityClient::new(\n                    binding.provider_instance(),\n                    Self {{\n{}\n                    }},\n                ))\n            }})\n            .collect()\n    }}\n\n    fn many_from_requirement(\n        dependencies: &PluginDependencies,\n        requirement_id: &str,\n    ) -> Result<Vec<BoundCapabilityClient<Self>>, RuntimeFailure> {{\n        let dependencies = dependencies.requirement(requirement_id)?;\n        Self::many_from_dependencies(&dependencies)\n    }}\n}}\n\n",
4168        client_fields.join("\n"),
4169        new_method,
4170        client_methods.join("\n\n"),
4171        client_initializers.join("\n"),
4172        many_client_initializers.join("\n")
4173    )
4174    .expect("writing to a String cannot fail");
4175    for error in invocation_errors {
4176        output.push_str(&error);
4177    }
4178    format!("{}\n", output.trim_end())
4179}
4180
4181#[allow(clippy::too_many_lines)]
4182fn generate_rust_plugin(contract: &ContractIr) -> Result<String, CodegenError> {
4183    let capability_name = pascal_case(
4184        contract
4185            .capability_id
4186            .split('@')
4187            .next()
4188            .and_then(|identity| identity.rsplit('.').next())
4189            .unwrap_or("Capability"),
4190    );
4191    let capability_const = screaming_snake_case(&capability_name);
4192    let mut types = RustTypes::new();
4193    let mut methods = Vec::new();
4194    let mut provider_methods = Vec::new();
4195    let mut dispatch_arms = Vec::new();
4196    let mut error_codecs = Vec::new();
4197    let mut wire_codecs = Vec::new();
4198
4199    for operation in &contract.operations {
4200        if operation.interaction != "request" {
4201            return Err(CodegenError::UnsupportedInteraction {
4202                operation: operation.name.clone(),
4203                interaction: operation.interaction.clone(),
4204            });
4205        }
4206        let operation_name = pascal_case(&operation.name);
4207        let request_type = types.type_for(&operation.request, &format!("{operation_name}Request"));
4208        let response_type =
4209            types.type_for(&operation.response, &format!("{operation_name}Response"));
4210        let error_name = format!("{operation_name}Error");
4211        let known_errors = &operation.domain_errors;
4212        let error_definition = if known_errors.is_empty() {
4213            format!(
4214                "#[derive(Clone, Debug, PartialEq)]\npub enum {error_name} {{\n    Unknown(UnknownDomainError),\n}}\n"
4215            )
4216        } else {
4217            let variants = known_errors
4218                .iter()
4219                .map(|variant| {
4220                    if let Some(payload) = &variant.payload {
4221                        let payload_name = format!("{error_name}{}Payload", variant.name);
4222                        let payload_type =
4223                            types.type_for_non_null(payload.non_null(), &payload_name);
4224                        let payload_type = if variant.payload_required {
4225                            if payload.is_nullable() {
4226                                format!("Option<{payload_type}>")
4227                            } else {
4228                                payload_type
4229                            }
4230                        } else if payload.is_nullable() {
4231                            format!("OptionalValue<{payload_type}>")
4232                        } else {
4233                            format!("Option<{payload_type}>")
4234                        };
4235                        format!("    {} {{ payload: {payload_type} }},", variant.name)
4236                    } else {
4237                        format!("    {},", variant.name)
4238                    }
4239                })
4240                .chain(std::iter::once(
4241                    "    Unknown(UnknownDomainError),".to_owned(),
4242                ))
4243                .collect::<Vec<_>>();
4244            format!(
4245                "#[derive(Clone, Debug, PartialEq)]\npub enum {error_name} {{\n{}\n}}\n",
4246                variants.join("\n")
4247            )
4248        };
4249        types.declarations.push(error_definition);
4250        error_codecs.push(generate_rust_error_codec(&error_name, known_errors));
4251        wire_codecs.push(generate_rust_wire_codecs(
4252            &operation.name,
4253            &request_type,
4254            &response_type,
4255            &error_name,
4256        ));
4257        let method = rust_field_name(&operation.name);
4258        let operation_const = format!("{}_OPERATION", screaming_snake_case(&operation.name));
4259        methods.push(format!(
4260            "    pub fn {method}(\n        &self,\n        context: &lenso_plugin_sdk::Ctx,\n        request: {request_type},\n    ) -> Result<{response_type}, lenso_plugin_sdk::CallError<{error_name}>> {{\n        context.request(&self.dependency, {operation_const}, &request)\n    }}"
4261        ));
4262        provider_methods.push(format!(
4263            "    fn {method}(\n        &self,\n        context: lenso_plugin_sdk::Ctx,\n        request: {request_type},\n    ) -> Result<{response_type}, {error_name}>;"
4264        ));
4265        dispatch_arms.push(format!(
4266            "                    $crate::{operation_const} => {{\n                        let request = match lenso_plugin_sdk::__private::serde_json::from_value::<$crate::{request_type}>(request) {{\n                            Ok(request) => request,\n                            Err(_) => return lenso_plugin_sdk::InvocationOutcome::Failure(\"invalid Capability request\".to_owned()),\n                        }};\n                        match <$plugin as $crate::{capability_name}Provider>::{method}(self, context, request) {{\n                            Ok(value) => match lenso_plugin_sdk::__private::serde_json::to_value(value) {{\n                                Ok(value) => lenso_plugin_sdk::InvocationOutcome::Success(value),\n                                Err(error) => lenso_plugin_sdk::InvocationOutcome::Failure(error.to_string()),\n                            }},\n                            Err(error) => match lenso_plugin_sdk::__private::serde_json::to_value(error) {{\n                                Ok(error) => lenso_plugin_sdk::InvocationOutcome::DomainError(error),\n                                Err(error) => lenso_plugin_sdk::InvocationOutcome::Failure(error.to_string()),\n                            }},\n                        }}\n                    }}"
4267        ));
4268    }
4269
4270    let mut output = String::new();
4271    output.push_str(GENERATED_HEADER);
4272    writeln!(
4273        output,
4274        "pub const CAPABILITY_ID: &str = {};",
4275        quote_string(&contract.capability_id)
4276    )
4277    .expect("writing to a String cannot fail");
4278    writeln!(
4279        output,
4280        "pub const DESCRIPTOR_VERSION: &str = {};",
4281        quote_string(&contract.version)
4282    )
4283    .expect("writing to a String cannot fail");
4284    writeln!(
4285        output,
4286        "pub const DESCRIPTOR_DIGEST: &str = {};",
4287        quote_string(&contract.descriptor_digest)
4288    )
4289    .expect("writing to a String cannot fail");
4290    writeln!(output, "pub const PORTABLE: bool = {};", contract.portable)
4291        .expect("writing to a String cannot fail");
4292    writeln!(
4293        output,
4294        "pub const {capability_const}_CAPABILITY_ID: &str = CAPABILITY_ID;\npub const {capability_const}_DESCRIPTOR_VERSION: &str = DESCRIPTOR_VERSION;\npub const {capability_const}_DESCRIPTOR_DIGEST: &str = DESCRIPTOR_DIGEST;\n"
4295    )
4296    .expect("writing to a String cannot fail");
4297    for operation in &contract.operations {
4298        writeln!(
4299            output,
4300            "pub const {}_OPERATION: &str = {};",
4301            screaming_snake_case(&operation.name),
4302            quote_string(&operation.name)
4303        )
4304        .expect("writing to a String cannot fail");
4305    }
4306    writeln!(
4307        output,
4308        "\npub use lenso_contract_runtime::{{{}}};\nuse lenso_contract_runtime::{{decode_portable_json, encode_portable_json}};\n",
4309        rust_runtime_types(contract).join(", ")
4310    )
4311    .expect("writing to a String cannot fail");
4312    for declaration in types.declarations {
4313        output.push_str(&declaration);
4314        output.push('\n');
4315    }
4316    for codec in error_codecs {
4317        output.push_str(&codec);
4318        output.push('\n');
4319    }
4320    for codec in wire_codecs {
4321        output.push_str(&codec);
4322        output.push('\n');
4323    }
4324    write!(
4325        output,
4326        "#[derive(Clone, Debug)]\npub struct {capability_name}Client {{\n    dependency: lenso_plugin_sdk::Dependency,\n}}\n\nimpl lenso_plugin_sdk::DependencyClient for {capability_name}Client {{\n    const CAPABILITY_ID: &'static str = CAPABILITY_ID;\n    const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;\n    const DESCRIPTOR_DIGEST: &'static str = DESCRIPTOR_DIGEST;\n\n    fn from_dependency(dependency: lenso_plugin_sdk::Dependency) -> Self {{\n        Self {{ dependency }}\n    }}\n}}\n\nimpl {capability_name}Client {{\n{}\n}}\n",
4327        methods.join("\n\n")
4328    )
4329    .expect("writing to a String cannot fail");
4330    let export_macro = format!("export_{}_plugin", snake_case(&capability_name));
4331    let request_literals = contract
4332        .operations
4333        .iter()
4334        .map(|operation| quote_string(&operation.name))
4335        .collect::<Vec<_>>()
4336        .join(", ");
4337    write!(
4338        output,
4339        "\npub trait {capability_name}Provider {{\n{}\n}}\n\n#[macro_export]\nmacro_rules! {export_macro} {{\n    ($plugin:ty) => {{\n        impl lenso_plugin_sdk::JsonRequestHandler for $plugin {{\n            fn invoke_with_context(\n                &self,\n                context: lenso_plugin_sdk::Ctx,\n                capability: &str,\n                operation: &str,\n                request: lenso_plugin_sdk::__private::serde_json::Value,\n            ) -> lenso_plugin_sdk::InvocationOutcome {{\n                if capability != $crate::CAPABILITY_ID {{\n                    return lenso_plugin_sdk::InvocationOutcome::Failure(\"unknown Capability\".to_owned());\n                }}\n                match operation {{\n{}\n                    _ => lenso_plugin_sdk::InvocationOutcome::Failure(\"unknown Capability operation\".to_owned()),\n                }}\n            }}\n        }}\n\n        lenso_plugin_sdk::__export_json_request_handler! {{\n            $plugin {{\n                capability_id: {},\n                descriptor_version: {},\n                descriptor_digest: {},\n                requests: [{request_literals}],\n            }}\n        }}\n    }};\n}}\n",
4340        provider_methods.join("\n\n"),
4341        dispatch_arms.join(",\n"),
4342        quote_string(&contract.capability_id),
4343        quote_string(&contract.version),
4344        quote_string(&contract.descriptor_digest),
4345    )
4346    .expect("writing to a String cannot fail");
4347    Ok(format!("{}\n", output.trim_end()))
4348}
4349
4350fn rust_runtime_types(contract: &ContractIr) -> Vec<&'static str> {
4351    let mut types = BTreeSet::from(["UnknownDomainError"]);
4352    for operation in &contract.operations {
4353        collect_rust_runtime_types(&operation.request, &mut types);
4354        collect_rust_runtime_types(&operation.response, &mut types);
4355        for error in &operation.domain_errors {
4356            if let Some(payload) = &error.payload {
4357                if !error.payload_required && payload.is_nullable() {
4358                    types.insert("OptionalValue");
4359                }
4360                collect_rust_runtime_types(payload, &mut types);
4361            }
4362        }
4363    }
4364    types.into_iter().collect()
4365}
4366
4367fn collect_rust_runtime_types(ty: &TypeIr, types: &mut BTreeSet<&'static str>) {
4368    match ty {
4369        TypeIr::Int64 => {
4370            types.insert("Int64");
4371        }
4372        TypeIr::Uint64 => {
4373            types.insert("Uint64");
4374        }
4375        TypeIr::Bytes => {
4376            types.insert("Bytes");
4377        }
4378        TypeIr::Timestamp => {
4379            types.insert("Timestamp");
4380        }
4381        TypeIr::Duration => {
4382            types.insert("Duration");
4383        }
4384        TypeIr::RawJson => {
4385            types.insert("RawJson");
4386        }
4387        TypeIr::Array(item) | TypeIr::Nullable(item) => {
4388            collect_rust_runtime_types(item, types);
4389        }
4390        TypeIr::Object {
4391            fields, additional, ..
4392        } => {
4393            for field in fields {
4394                if !field.required && field.ty.is_nullable() {
4395                    types.insert("OptionalValue");
4396                }
4397                collect_rust_runtime_types(&field.ty, types);
4398            }
4399            if let ObjectAdditionalIr::Typed(value) = additional {
4400                collect_rust_runtime_types(value, types);
4401            }
4402        }
4403        TypeIr::Any
4404        | TypeIr::String
4405        | TypeIr::Integer
4406        | TypeIr::Number
4407        | TypeIr::Boolean
4408        | TypeIr::Null
4409        | TypeIr::Enum { .. } => {}
4410    }
4411}
4412
4413#[allow(clippy::too_many_lines)]
4414fn generate_rust_error_codec(error_name: &str, variants: &[ErrorVariantIr]) -> String {
4415    let mut output = String::new();
4416    writeln!(output, "impl serde::Serialize for {error_name} {{").expect("String cannot fail");
4417    output.push_str(
4418        "    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n",
4419    );
4420    output.push_str("        use serde::ser::SerializeMap;\n");
4421    output.push_str("        match self {\n");
4422    for variant in variants {
4423        if variant.structured {
4424            if variant.payload.is_some() {
4425                if variant.payload_required {
4426                    writeln!(
4427                        output,
4428                        "            Self::{} {{ payload }} => {{\n                let mut map = serializer.serialize_map(Some(2))?;\n                map.serialize_entry(\"code\", {})?;\n                map.serialize_entry(\"payload\", payload)?;\n                map.end()\n            }},",
4429                        variant.name,
4430                        quote_string(&variant.code)
4431                    )
4432                    .expect("String cannot fail");
4433                } else {
4434                    writeln!(
4435                        output,
4436                        "            Self::{} {{ payload }} => {{\n                let mut map = serializer.serialize_map(Some(if payload.is_some() {{ 2 }} else {{ 1 }}))?;\n                map.serialize_entry(\"code\", {})?;\n                if let Some(payload) = payload {{\n                    map.serialize_entry(\"payload\", payload)?;\n                }}\n                map.end()\n            }},",
4437                        variant.name,
4438                        quote_string(&variant.code)
4439                    )
4440                    .expect("String cannot fail");
4441                }
4442            } else {
4443                writeln!(
4444                    output,
4445                    "            Self::{} => {{\n                let mut map = serializer.serialize_map(Some(1))?;\n                map.serialize_entry(\"code\", {})?;\n                map.end()\n            }},",
4446                    variant.name,
4447                    quote_string(&variant.code)
4448                )
4449                .expect("String cannot fail");
4450            }
4451        } else {
4452            writeln!(
4453                output,
4454                "            Self::{} => serializer.serialize_str({}),",
4455                variant.name,
4456                quote_string(&variant.code)
4457            )
4458            .expect("String cannot fail");
4459        }
4460    }
4461    output.push_str("            Self::Unknown(value) => {\n                let mut map = serializer.serialize_map(Some(1 + usize::from(value.payload.is_some()) + value.extra.len()))?;\n                map.serialize_entry(\"code\", &value.code)?;\n                if let Some(payload) = &value.payload {\n                    map.serialize_entry(\"payload\", payload)?;\n                }\n                for (key, extra) in &value.extra {\n                    map.serialize_entry(key, extra)?;\n                }\n                map.end()\n            },\n        }\n    }\n}\n\n");
4462    writeln!(
4463        output,
4464        "impl<'de> serde::Deserialize<'de> for {error_name} {{"
4465    )
4466    .expect("String cannot fail");
4467    output.push_str(
4468        "    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;\n        match value {\n            serde_json::Value::String(code) => match code.as_str() {\n",
4469    );
4470    for variant in variants.iter().filter(|variant| !variant.structured) {
4471        writeln!(
4472            output,
4473            "                {} => Ok(Self::{}),",
4474            quote_string(&variant.code),
4475            variant.name
4476        )
4477        .expect("String cannot fail");
4478    }
4479    output.push_str(
4480        "                _ => Ok(Self::Unknown(UnknownDomainError { code, payload: None, extra: std::collections::BTreeMap::new() })),\n            },\n            serde_json::Value::Object(mut object) => {\n                let Some(code) = object.remove(\"code\").and_then(|value| value.as_str().map(ToOwned::to_owned)) else {\n                    return Err(serde::de::Error::custom(\"Domain Error object is missing a string code\"));\n                };\n",
4481    );
4482    if variants.iter().any(|variant| variant.structured) {
4483        output.push_str("                match code.as_str() {\n");
4484        for variant in variants.iter().filter(|variant| variant.structured) {
4485            if variant.payload.is_some() {
4486                if variant.payload_required {
4487                    writeln!(
4488                        output,
4489                        "                    {} => {{\n                        let payload = object.remove(\"payload\").ok_or_else(|| serde::de::Error::custom(\"structured Domain Error is missing a payload\"))?;\n                        let payload = serde_json::from_value(payload).map_err(serde::de::Error::custom)?;\n                        Ok(Self::{} {{ payload }})\n                    }},",
4490                        quote_string(&variant.code),
4491                        variant.name
4492                    )
4493                    .expect("String cannot fail");
4494                } else {
4495                    writeln!(
4496                        output,
4497                        "                    {} => {{\n                        let payload = match object.remove(\"payload\") {{\n                            Some(payload) => Some(serde_json::from_value(payload).map_err(serde::de::Error::custom)?),\n                            None => None,\n                        }};\n                        Ok(Self::{} {{ payload }})\n                    }},",
4498                        quote_string(&variant.code),
4499                        variant.name
4500                    )
4501                    .expect("String cannot fail");
4502                }
4503            } else {
4504                writeln!(
4505                    output,
4506                    "                    {} => Ok(Self::{}),",
4507                    quote_string(&variant.code),
4508                    variant.name
4509                )
4510                .expect("String cannot fail");
4511            }
4512        }
4513        output.push_str(
4514            "                    _ => {\n                        let payload = object.remove(\"payload\");\n                        let extra = object.into_iter().collect::<std::collections::BTreeMap<_, _>>();\n                        Ok(Self::Unknown(UnknownDomainError { code, payload, extra }))\n                    }\n                }\n            }\n",
4515        );
4516    } else {
4517        output.push_str(
4518            "                let payload = object.remove(\"payload\");\n                let extra = object.into_iter().collect::<std::collections::BTreeMap<_, _>>();\n                Ok(Self::Unknown(UnknownDomainError { code, payload, extra }))\n            }\n",
4519        );
4520    }
4521    output.push_str(
4522        "            other => Err(serde::de::Error::custom(format!(\"Domain Error must be a string or object, got {other}\"))),\n        }\n    }\n}\n",
4523    );
4524    output
4525}
4526
4527#[allow(clippy::too_many_lines, clippy::unnecessary_wraps)]
4528fn generate_rust_runtime(contract: &ContractIr) -> Result<String, CodegenError> {
4529    let capability_name = pascal_case(
4530        contract
4531            .capability_id
4532            .split('@')
4533            .next()
4534            .and_then(|identity| identity.rsplit('.').next())
4535            .unwrap_or("Capability"),
4536    );
4537    let codec_name = format!("{capability_name}JsonCodec");
4538    let guest_client_name = format!("{capability_name}GuestClient");
4539    let mut types = RustTypes::new();
4540    let mut request_operations = Vec::new();
4541    let mut stream_operations = Vec::new();
4542    let mut event_operations = Vec::new();
4543    let mut encode_arms = Vec::new();
4544    let mut response_arms = Vec::new();
4545    let mut error_arms = Vec::new();
4546    let mut host_request_arms = Vec::new();
4547    let mut stream_open_arms = Vec::new();
4548    let mut stream_encode_arms = Vec::new();
4549    let mut stream_decode_arms = Vec::new();
4550    let mut stream_error_arms = Vec::new();
4551    let mut host_stream_arms = Vec::new();
4552    let mut event_encode_arms = Vec::new();
4553    let mut host_event_arms = Vec::new();
4554    let mut guest_methods = Vec::new();
4555
4556    for operation in &contract.operations {
4557        let operation_name = pascal_case(&operation.name);
4558        let marker_name = if contract.operations.len() == 1 {
4559            capability_name.clone()
4560        } else {
4561            format!("{capability_name}{operation_name}")
4562        };
4563        let request_type = types.type_for(&operation.request, &format!("{operation_name}Request"));
4564        let response_type =
4565            types.type_for(&operation.response, &format!("{operation_name}Response"));
4566        let error_type = format!("{operation_name}Error");
4567        let operation_const = format!("{}_OPERATION", screaming_snake_case(&operation.name));
4568        let method_name = rust_field_name(&operation.name);
4569        match operation.interaction.as_str() {
4570            "request" => {
4571                request_operations.push(operation_const.clone());
4572                encode_arms.push(format!(
4573                    "            {operation_const} => {{\n                let value = request.downcast_ref::<{request_type}>().ok_or_else(runtime_codec_protocol_failure)?;\n                serde_json::to_value(value).map_err(|_| runtime_codec_protocol_failure())\n            }}"
4574                ));
4575                response_arms.push(format!(
4576                    "            {operation_const} => serde_json::from_value::<{response_type}>(value)\n                .map(|value| Box::new(value) as Box<dyn std::any::Any>)\n                .map_err(|_| runtime_codec_protocol_failure()),"
4577                ));
4578                error_arms.push(format!(
4579                    "            {operation_const} => serde_json::from_value::<{error_type}>(value)\n                .map(|value| Box::new(value) as Box<dyn std::any::Any>)\n                .map_err(|_| runtime_codec_protocol_failure()),"
4580                ));
4581                host_request_arms.push(format!(
4582                    "            {operation_const} => {{\n                let request = serde_json::from_value::<{request_type}>(request).map_err(|_| runtime_codec_protocol_failure());\n                Box::pin(async move {{\n                    let request = request?;\n                    let handle = dependency.typed::<{marker_name}>()?;\n                    match handle.invoke_with_context({operation_const}, context, request).await? {{\n                        Ok(response) => serde_json::to_value(response)\n                            .map(lenso_runtime_codec::JsonInvocationOutcome::Success)\n                            .map_err(|_| runtime_codec_protocol_failure()),\n                        Err(error) => serde_json::to_value(error)\n                            .map(lenso_runtime_codec::JsonInvocationOutcome::DomainError)\n                            .map_err(|_| runtime_codec_protocol_failure()),\n                    }}\n                }})\n            }}"
4583                ));
4584                guest_methods.push(format!(
4585                    "    pub fn {method_name}(&self, request: &{request_type}) -> Result<{response_type}, lenso_guest_sdk::GuestError<{error_type}>> {{\n        self.capability.request({operation_const}, request)\n    }}"
4586                ));
4587            }
4588            "stream" => {
4589                stream_operations.push(operation_const.clone());
4590                stream_open_arms.push(format!(
4591                    "            {operation_const} => {{\n                let value = request.downcast_ref::<{request_type}>().ok_or_else(runtime_codec_protocol_failure)?;\n                serde_json::to_value(value).map_err(|_| runtime_codec_protocol_failure())\n            }}"
4592                ));
4593                stream_encode_arms.push(format!(
4594                    "            {operation_const} => {{\n                let value = message.downcast_ref::<{response_type}>().ok_or_else(runtime_codec_protocol_failure)?;\n                serde_json::to_value(value).map_err(|_| runtime_codec_protocol_failure())\n            }}"
4595                ));
4596                stream_decode_arms.push(format!(
4597                    "            {operation_const} => serde_json::from_value::<{response_type}>(value)\n                .map(|value| Box::new(value) as Box<dyn std::any::Any>)\n                .map_err(|_| runtime_codec_protocol_failure()),"
4598                ));
4599                stream_error_arms.push(format!(
4600                    "            {operation_const} => serde_json::from_value::<{error_type}>(value)\n                .map(|value| Box::new(value) as Box<dyn std::any::Any>)\n                .map_err(|_| runtime_codec_protocol_failure()),"
4601                ));
4602                host_stream_arms.push(format!(
4603                    "            {operation_const} => {{\n                let request = serde_json::from_value::<{request_type}>(request).map_err(|_| runtime_codec_protocol_failure());\n                Box::pin(async move {{\n                    let request = request?;\n                    let handle = dependency.typed::<{marker_name}>()?;\n                    match handle.open_with_context({operation_const}, context, request).await? {{\n                        Ok(stream) => Ok(Ok(lenso_runtime_codec::json_host_stream::<{marker_name}>(\n                            stream,\n                            |value| serde_json::from_value::<{response_type}>(value).map_err(|_| runtime_codec_protocol_failure()),\n                            |message| serde_json::to_value(message).map_err(|_| runtime_codec_protocol_failure()),\n                            |error| serde_json::to_value(error).map_err(|_| runtime_codec_protocol_failure()),\n                        ))),\n                        Err(error) => serde_json::to_value(error)\n                            .map(Err)\n                            .map_err(|_| runtime_codec_protocol_failure()),\n                    }}\n                }})\n            }}"
4604                ));
4605                guest_methods.push(format!(
4606                    "    pub fn {method_name}(&self, request: &{request_type}) -> Result<lenso_guest_sdk::GuestStream<H, {response_type}, {error_type}>, lenso_guest_sdk::GuestError<{error_type}>> {{\n        self.capability.open_stream({operation_const}, request)\n    }}"
4607                ));
4608            }
4609            "event" => {
4610                event_operations.push(operation_const.clone());
4611                event_encode_arms.push(format!(
4612                    "            {operation_const} => {{\n                let value = event.downcast_ref::<{request_type}>().ok_or_else(runtime_codec_protocol_failure)?;\n                serde_json::to_value(value).map_err(|_| runtime_codec_protocol_failure())\n            }}"
4613                ));
4614                host_event_arms.push(format!(
4615                    "            {operation_const} => {{\n                let event = serde_json::from_value::<{request_type}>(event).map_err(|_| runtime_codec_protocol_failure());\n                Box::pin(async move {{\n                    let event = event?;\n                    let handle = dependency.typed::<{marker_name}>()?;\n                    let results = handle.publish_with_context({operation_const}, context, event).await;\n                    let [result] = results.as_slice() else {{ return Err(runtime_codec_protocol_failure()); }};\n                    match result.admission() {{\n                        lenso_kernel::EventAdmission::Accepted => Ok(()),\n                        lenso_kernel::EventAdmission::Unavailable => Err(RuntimeFailure::Unavailable {{ capability: CAPABILITY_ID }}),\n                        lenso_kernel::EventAdmission::Exhausted => Err(RuntimeFailure::ResourceExhausted {{ capability: CAPABILITY_ID, operation: {operation_const}.to_owned() }}),\n                    }}\n                }})\n            }}"
4616                ));
4617                guest_methods.push(format!(
4618                    "    pub fn {method_name}(&self, event: &{request_type}) -> Result<(), lenso_guest_sdk::GuestError<serde_json::Value>> {{\n        self.capability.publish_event({operation_const}, event)\n    }}"
4619                ));
4620            }
4621            _ => unreachable!("Descriptor validation restricts interactions"),
4622        }
4623    }
4624
4625    let encode_dispatch = runtime_codec_dispatch(&encode_arms);
4626    let response_dispatch = runtime_codec_dispatch(&response_arms);
4627    let error_dispatch = runtime_codec_dispatch(&error_arms);
4628    let host_request_dispatch = runtime_codec_host_dispatch(&host_request_arms);
4629    let stream_open_dispatch = runtime_codec_dispatch(&stream_open_arms);
4630    let stream_encode_dispatch = runtime_codec_dispatch(&stream_encode_arms);
4631    let stream_decode_dispatch = runtime_codec_dispatch(&stream_decode_arms);
4632    let stream_error_dispatch = runtime_codec_dispatch(&stream_error_arms);
4633    let host_stream_dispatch = runtime_codec_host_dispatch(&host_stream_arms);
4634    let event_encode_dispatch = runtime_codec_dispatch(&event_encode_arms);
4635    let host_event_dispatch = runtime_codec_host_dispatch(&host_event_arms);
4636    let request_parameter = if request_operations.is_empty() {
4637        "_request"
4638    } else {
4639        "request"
4640    };
4641    let request_value_parameter = if request_operations.is_empty() {
4642        "_value"
4643    } else {
4644        "value"
4645    };
4646    let stream_request_parameter = if stream_operations.is_empty() {
4647        "_request"
4648    } else {
4649        "request"
4650    };
4651    let stream_message_parameter = if stream_operations.is_empty() {
4652        "_message"
4653    } else {
4654        "message"
4655    };
4656    let stream_value_parameter = if stream_operations.is_empty() {
4657        "_value"
4658    } else {
4659        "value"
4660    };
4661    let event_parameter = if event_operations.is_empty() {
4662        "_event"
4663    } else {
4664        "event"
4665    };
4666    let (host_request_dependency_parameter, host_request_parameter, host_request_context_parameter) =
4667        if request_operations.is_empty() {
4668            ("_dependency", "_request", "_context")
4669        } else {
4670            ("dependency", "request", "context")
4671        };
4672    let (host_stream_dependency_parameter, host_stream_parameter, host_stream_context_parameter) =
4673        if stream_operations.is_empty() {
4674            ("_dependency", "_request", "_context")
4675        } else {
4676            ("dependency", "request", "context")
4677        };
4678    let (host_event_dependency_parameter, host_event_parameter, host_event_context_parameter) =
4679        if event_operations.is_empty() {
4680            ("_dependency", "_event", "_context")
4681        } else {
4682            ("dependency", "event", "context")
4683        };
4684
4685    let mut output = generate_rust(contract);
4686    write!(
4687        output,
4688        "\n#[derive(Clone, Copy, Debug)]\npub struct {guest_client_name}<'a, H: lenso_guest_sdk::HostImports> {{\n    capability: lenso_guest_sdk::GuestCapability<'a, H>,\n}}\n\nimpl<'a, H: lenso_guest_sdk::HostImports> {guest_client_name}<'a, H> {{\n    pub fn from_context(context: &'a lenso_guest_sdk::GuestContext<H>) -> Result<Self, lenso_guest_sdk::GuestError<serde_json::Value>> {{\n        context\n            .require(CAPABILITY_ID, DESCRIPTOR_VERSION, &[{}], &[{}], &[{}])\n            .map(|capability| Self {{ capability }})\n    }}\n\n{}\n}}\n",
4689        request_operations.join(", "),
4690        stream_operations.join(", "),
4691        event_operations.join(", "),
4692        guest_methods.join("\n\n"),
4693    )
4694    .expect("writing generated Rust to a String cannot fail");
4695    write!(
4696        output,
4697        "\n#[derive(Debug, Default)]\npub struct {codec_name};\n\nimpl lenso_runtime_codec::JsonCapabilityCodec for {codec_name} {{\n    fn capability_id(&self) -> &'static str {{ CAPABILITY_ID }}\n\n    fn descriptor_version(&self) -> &'static str {{ DESCRIPTOR_VERSION }}\n\n    fn descriptor_digest(&self) -> &'static str {{ DESCRIPTOR_DIGEST }}\n\n    fn request_operations(&self) -> &'static [&'static str] {{ &[{}] }}\n    fn stream_operations(&self) -> &'static [&'static str] {{ &[{}] }}\n    fn event_operations(&self) -> &'static [&'static str] {{ &[{}] }}\n\n    fn encode_request(&self, operation: &str, {request_parameter}: &dyn std::any::Any) -> Result<serde_json::Value, RuntimeFailure> {{\n{encode_dispatch}\n    }}\n\n    fn decode_response(&self, operation: &str, {request_value_parameter}: serde_json::Value) -> Result<Box<dyn std::any::Any>, RuntimeFailure> {{\n{response_dispatch}\n    }}\n\n    fn decode_domain_error(&self, operation: &str, {request_value_parameter}: serde_json::Value) -> Result<Box<dyn std::any::Any>, RuntimeFailure> {{\n{error_dispatch}\n    }}\n\n    fn encode_stream_open(&self, operation: &str, {stream_request_parameter}: &dyn std::any::Any) -> Result<serde_json::Value, RuntimeFailure> {{\n{stream_open_dispatch}\n    }}\n\n    fn encode_stream_message(&self, operation: &str, {stream_message_parameter}: &dyn std::any::Any) -> Result<serde_json::Value, RuntimeFailure> {{\n{stream_encode_dispatch}\n    }}\n\n    fn decode_stream_message(&self, operation: &str, {stream_value_parameter}: serde_json::Value) -> Result<Box<dyn std::any::Any>, RuntimeFailure> {{\n{stream_decode_dispatch}\n    }}\n\n    fn decode_stream_domain_error(&self, operation: &str, {stream_value_parameter}: serde_json::Value) -> Result<Box<dyn std::any::Any>, RuntimeFailure> {{\n{stream_error_dispatch}\n    }}\n\n    fn encode_event(&self, operation: &str, {event_parameter}: &dyn std::any::Any) -> Result<serde_json::Value, RuntimeFailure> {{\n{event_encode_dispatch}\n    }}\n\n    fn invoke_host_request(&self, {host_request_dependency_parameter}: lenso_kernel::PluginDependencyHandle, operation: String, {host_request_parameter}: serde_json::Value, {host_request_context_parameter}: InvocationContext) -> lenso_runtime_codec::JsonHostRequestFuture {{\n{host_request_dispatch}\n    }}\n\n    fn open_host_stream(&self, {host_stream_dependency_parameter}: lenso_kernel::PluginStreamDependencyHandle, operation: String, {host_stream_parameter}: serde_json::Value, {host_stream_context_parameter}: InvocationContext) -> lenso_runtime_codec::JsonHostStreamOpenFuture {{\n{host_stream_dispatch}\n    }}\n\n    fn publish_host_event(&self, {host_event_dependency_parameter}: lenso_kernel::PluginEventDependencyHandle, operation: String, {host_event_parameter}: serde_json::Value, {host_event_context_parameter}: InvocationContext) -> futures::future::LocalBoxFuture<'static, Result<(), RuntimeFailure>> {{\n{host_event_dispatch}\n    }}\n}}\n\nfn runtime_codec_protocol_failure() -> RuntimeFailure {{ RuntimeFailure::ProtocolViolation {{ capability: CAPABILITY_ID }} }}\n\nfn runtime_codec_unknown_operation(operation: &str) -> RuntimeFailure {{\n    RuntimeFailure::UnknownOperation {{ capability: CAPABILITY_ID, operation: operation.to_owned() }}\n}}\n",
4698        request_operations.join(", "),
4699        stream_operations.join(", "),
4700        event_operations.join(", "),
4701    )
4702    .expect("writing generated Rust to a String cannot fail");
4703    Ok(output)
4704}
4705
4706fn runtime_codec_host_dispatch(arms: &[String]) -> String {
4707    if arms.is_empty() {
4708        "        Box::pin(std::future::ready(Err(runtime_codec_unknown_operation(&operation))))"
4709            .to_owned()
4710    } else {
4711        let arms = arms
4712            .iter()
4713            .map(|arm| arm.trim_end_matches(','))
4714            .collect::<Vec<_>>()
4715            .join(",\n");
4716        format!(
4717            "        match operation.as_str() {{\n{arms},\n            _ => Box::pin(std::future::ready(Err(runtime_codec_unknown_operation(&operation)))),\n        }}"
4718        )
4719    }
4720}
4721
4722fn runtime_codec_dispatch(arms: &[String]) -> String {
4723    if arms.is_empty() {
4724        "        Err(runtime_codec_unknown_operation(operation))".to_owned()
4725    } else {
4726        let arms = arms
4727            .iter()
4728            .map(|arm| arm.trim_end_matches(','))
4729            .collect::<Vec<_>>()
4730            .join(",\n");
4731        format!(
4732            "        match operation {{\n{arms},\n            _ => Err(runtime_codec_unknown_operation(operation)),\n        }}"
4733        )
4734    }
4735}
4736
4737fn generate_rust_wire_codecs(
4738    operation: &str,
4739    request_type: &str,
4740    response_type: &str,
4741    error_type: &str,
4742) -> String {
4743    let stem = snake_case(operation);
4744    format!(
4745        "pub fn encode_{stem}_request(value: &{request_type}) -> Result<String, serde_json::Error> {{ encode_portable_json(value) }}\npub fn decode_{stem}_request(wire: &str) -> Result<{request_type}, serde_json::Error> {{ decode_portable_json(wire) }}\npub fn encode_{stem}_response(value: &{response_type}) -> Result<String, serde_json::Error> {{ encode_portable_json(value) }}\npub fn decode_{stem}_response(wire: &str) -> Result<{response_type}, serde_json::Error> {{ decode_portable_json(wire) }}\npub fn encode_{stem}_error(value: &{error_type}) -> Result<String, serde_json::Error> {{ encode_portable_json(value) }}\npub fn decode_{stem}_error(wire: &str) -> Result<{error_type}, serde_json::Error> {{ decode_portable_json(wire) }}\n"
4746    )
4747}
4748
4749fn generate_rust_event_codecs(operation: &str, event_type: &str) -> String {
4750    let stem = snake_case(operation);
4751    format!(
4752        "pub fn encode_{stem}_event(value: &{event_type}) -> Result<String, serde_json::Error> {{ encode_portable_json(value) }}\npub fn decode_{stem}_event(wire: &str) -> Result<{event_type}, serde_json::Error> {{ decode_portable_json(wire) }}\n"
4753    )
4754}
4755
4756fn generate_typescript_codecs(
4757    operation: &str,
4758    request_type: &str,
4759    response_type: &str,
4760    error_type: &str,
4761    variants: &[ErrorVariantIr],
4762) -> String {
4763    let stem = pascal_case(operation);
4764    let known_strings = variants
4765        .iter()
4766        .filter(|variant| !variant.structured)
4767        .map(|variant| quote_string(&variant.code))
4768        .collect::<Vec<_>>()
4769        .join(", ");
4770    format!(
4771        "export function encode{stem}Request(value: {request_type}): string {{ return lensoContractRuntime.encodePortableJson(value, \"request\"); }}\nexport function decode{stem}Request(wire: string): {request_type} {{ return lensoContractRuntime.decodePortableJson<{request_type}>(wire); }}\nexport function encode{stem}Response(value: {response_type}): string {{ return lensoContractRuntime.encodePortableJson(value, \"response\"); }}\nexport function decode{stem}Response(wire: string): {response_type} {{ return lensoContractRuntime.decodePortableJson<{response_type}>(wire); }}\nexport function encode{stem}Error(value: {error_type}): string {{ return lensoContractRuntime.encodePortableJson(value, \"Domain Error\"); }}\nexport function decode{stem}Error(wire: string): {error_type} {{ return lensoContractRuntime.decodeDomainError<{error_type}>(wire, [{known_strings}]); }}\n"
4772    )
4773}
4774
4775fn generate_typescript_event_codecs(operation: &str, event_type: &str) -> String {
4776    let stem = pascal_case(operation);
4777    format!(
4778        "export function encode{stem}Event(value: {event_type}): string {{ return lensoContractRuntime.encodePortableJson(value, \"event\"); }}\nexport function decode{stem}Event(wire: string): {event_type} {{ return lensoContractRuntime.decodePortableJson<{event_type}>(wire); }}\n"
4779    )
4780}
4781
4782#[allow(clippy::too_many_lines)]
4783fn generate_typescript(contract: &ContractIr) -> String {
4784    let capability_name = pascal_case(
4785        contract
4786            .capability_id
4787            .split('@')
4788            .next()
4789            .and_then(|identity| identity.rsplit('.').next())
4790            .unwrap_or("Capability"),
4791    );
4792    let capability_const = screaming_snake_case(&capability_name);
4793    let mut types = TypeScriptTypes::new();
4794    let mut clients = Vec::new();
4795    let mut providers = Vec::new();
4796    let mut errors = Vec::new();
4797    let mut codecs = Vec::new();
4798    let mut request_dispatch_arms = Vec::new();
4799    let mut stream_dispatch_arms = Vec::new();
4800    let mut event_dispatch_arms = Vec::new();
4801    let mut dependency_methods = Vec::new();
4802    let operation_names = contract
4803        .operations
4804        .iter()
4805        .map(|operation| quote_string(&operation.name))
4806        .collect::<Vec<_>>()
4807        .join(", ");
4808    let stream_operation_names = contract
4809        .operations
4810        .iter()
4811        .filter(|operation| operation.interaction == "stream")
4812        .map(|operation| quote_string(&operation.name))
4813        .collect::<Vec<_>>()
4814        .join(", ");
4815    let event_operation_names = contract
4816        .operations
4817        .iter()
4818        .filter(|operation| operation.interaction == "event")
4819        .map(|operation| quote_string(&operation.name))
4820        .collect::<Vec<_>>()
4821        .join(", ");
4822    let has_stream_operations = contract
4823        .operations
4824        .iter()
4825        .any(|operation| operation.interaction == "stream");
4826    let has_event_operations = contract
4827        .operations
4828        .iter()
4829        .any(|operation| operation.interaction == "event");
4830    let dependency_invoker_type = if has_stream_operations || has_event_operations {
4831        "InteractionDependencyInvoker"
4832    } else {
4833        "DependencyInvoker"
4834    };
4835    for operation in &contract.operations {
4836        let operation_name = pascal_case(&operation.name);
4837        let request_name = format!("{operation_name}Request");
4838        let response_name = format!("{operation_name}Response");
4839        let error_name = format!("{operation_name}Error");
4840        let request_type = types.type_for(&operation.request, &request_name);
4841        let response_type = types.type_for(&operation.response, &response_name);
4842        let variants = &operation.domain_errors;
4843        let error_type = if variants.is_empty() {
4844            "UnknownDomainError".to_owned()
4845        } else {
4846            let mut values = variants
4847                .iter()
4848                .map(|variant| {
4849                    if let Some(payload) = &variant.payload {
4850                        let payload_name = format!("{error_name}{}Payload", variant.name);
4851                        let payload_type =
4852                            types.type_for_non_null(payload.non_null(), &payload_name);
4853                        let payload_type = if payload.is_nullable() {
4854                            format!("{payload_type} | null")
4855                        } else {
4856                            payload_type
4857                        };
4858                        let payload_optional = if variant.payload_required { "" } else { "?" };
4859                        format!(
4860                            "{{ readonly code: {}; readonly payload{payload_optional}: {payload_type} }}",
4861                            quote_string(&variant.code),
4862                        )
4863                    } else if variant.structured {
4864                        format!("{{ readonly code: {} }}", quote_string(&variant.code))
4865                    } else {
4866                        quote_string(&variant.code)
4867                    }
4868                })
4869                .collect::<Vec<_>>();
4870            values.push("UnknownDomainError".to_owned());
4871            values.join(" | ")
4872        };
4873        let invocation_error_name = format!("{operation_name}InvocationError");
4874        let result_name = format!("{operation_name}Result");
4875        let provider_result_name = if operation.interaction == "stream" {
4876            format!("{operation_name}ProviderResult")
4877        } else {
4878            result_name.clone()
4879        };
4880        let provider_return_type = if operation.interaction == "stream" {
4881            format!("{operation_name}ProviderOutput")
4882        } else {
4883            format!("Promise<{provider_result_name}>")
4884        };
4885        let result_value_type = match operation.interaction.as_str() {
4886            "stream" => format!("StreamSession<{response_type}, {error_name}>"),
4887            "event" => "ReadonlyArray<EventPublishResult>".to_owned(),
4888            _ => response_type.clone(),
4889        };
4890        if operation.interaction == "event" {
4891            errors.push(format!(
4892                "export type {error_name} = {error_type};\nexport type {result_name} = ReadonlyArray<EventPublishResult>;"
4893            ));
4894        } else if operation.interaction == "stream" {
4895            errors.push(format!(
4896                "export type {error_name} = {error_type};\nexport type {invocation_error_name} = {{ readonly kind: \"domain\"; readonly error: {error_name} }} | {{ readonly kind: \"runtime\"; readonly error: RuntimeFailure }};\nexport type {result_name} = {{ readonly ok: true; readonly value: {result_value_type} }} | {{ readonly ok: false; readonly error: {invocation_error_name} }};\nexport type {provider_result_name} = {{ readonly ok: true; readonly value: ProviderStream<{response_type}, {error_name}> }} | {{ readonly ok: false; readonly error: {invocation_error_name} }};\nexport type {operation_name}ProviderOutput = AsyncIterable<{response_type}> | {provider_result_name} | Promise<{provider_result_name}>;"
4897            ));
4898        } else {
4899            errors.push(format!(
4900                "export type {error_name} = {error_type};\nexport type {invocation_error_name} = {{ readonly kind: \"domain\"; readonly error: {error_name} }} | {{ readonly kind: \"runtime\"; readonly error: RuntimeFailure }};\nexport type {result_name} = {{ readonly ok: true; readonly value: {result_value_type} }} | {{ readonly ok: false; readonly error: {invocation_error_name} }};"
4901            ));
4902        }
4903        codecs.push(generate_typescript_codecs(
4904            &operation.name,
4905            &request_type,
4906            &response_type,
4907            &error_name,
4908            variants,
4909        ));
4910        if operation.interaction == "event" {
4911            codecs.push(generate_typescript_event_codecs(
4912                &operation.name,
4913                &request_type,
4914            ));
4915        }
4916        if matches!(operation.interaction.as_str(), "request" | "stream") {
4917            clients.push(format!(
4918                "  {}(request: {request_type}, context?: InvocationContext): Promise<{result_name}>;",
4919                typescript_property_name(&snake_case(&operation.name)),
4920            ));
4921            providers.push(format!(
4922                "  {}(context: InvocationContext, request: {request_type}): {provider_return_type};",
4923                typescript_property_name(&snake_case(&operation.name)),
4924            ));
4925        } else {
4926            clients.push(format!(
4927                "  {}(event: {request_type}, context?: InvocationContext): Promise<{result_name}>;",
4928                typescript_property_name(&snake_case(&operation.name)),
4929            ));
4930            providers.push(format!(
4931                "  {}(context: InvocationContext, event: {request_type}): void | Promise<void>;",
4932                typescript_property_name(&snake_case(&operation.name)),
4933            ));
4934        }
4935        if operation.interaction == "request" {
4936            let provider_method = typescript_property_name(&snake_case(&operation.name));
4937            request_dispatch_arms.push(format!(
4938                "      case {}: {{\n        let request: {request_type};\n        try {{\n          request = decode{operation_name}Request(lensoContractRuntime.encodePortableJson(payload, \"request\"));\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"protocol_violation\", detail: providerErrorMessage(error) }} }};\n        }}\n        try {{\n          const result = await provider.{provider_method}(context, request);\n          if (result.ok) {{\n            return {{ kind: \"success\", value: JSON.parse(encode{operation_name}Response(result.value)) as unknown }};\n          }}\n          if (result.error.kind === \"domain\") {{\n            return {{ kind: \"domain\", value: JSON.parse(encode{operation_name}Error(result.error.error)) as unknown }};\n          }}\n          return {{ kind: \"runtime\", failure: result.error.error }};\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n        }}\n      }}",
4939                quote_string(&operation.name),
4940            ));
4941            dependency_methods.push(format!(
4942                "      async {provider_method}(request, context) {{\n        let payload: unknown;\n        try {{\n          payload = JSON.parse(encode{operation_name}Request(request)) as unknown;\n        }} catch (error) {{\n          return {{ ok: false, error: {{ kind: \"runtime\", error: {{ kind: \"protocol_violation\", detail: dependencyErrorMessage(error) }} }} }};\n        }}\n        const call = context ?? {{ requestId: \"0\" as Uint64, cancelled: false }};\n        try {{\n          const outcome = await invoke({}, call, payload);\n          if (outcome.kind === \"success\") {{\n            return {{ ok: true, value: decode{operation_name}Response(JSON.stringify(outcome.value)) }};\n          }}\n          if (outcome.kind === \"domain\") {{\n            return {{ ok: false, error: {{ kind: \"domain\", error: decode{operation_name}Error(JSON.stringify(outcome.value)) }} }};\n          }}\n          return {{ ok: false, error: {{ kind: \"runtime\", error: outcome.failure }} }};\n        }} catch (error) {{\n          return {{ ok: false, error: {{ kind: \"runtime\", error: {{ kind: \"plugin_failure\", detail: dependencyErrorMessage(error) }} }} }};\n        }}\n      }},",
4943                quote_string(&operation.name),
4944            ));
4945        } else if operation.interaction == "stream" {
4946            let provider_method = typescript_property_name(&snake_case(&operation.name));
4947            stream_dispatch_arms.push(format!(
4948                "      case {}: {{\n        let request: {request_type};\n        try {{\n          request = decode{operation_name}Request(lensoContractRuntime.encodePortableJson(payload, \"stream open request\"));\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"protocol_violation\", detail: providerErrorMessage(error) }} }};\n        }}\n        try {{\n          const provided = provider.{provider_method}(context, request);\n          const result = isAsyncIterable<{response_type}>(provided)\n            ? {{ ok: true as const, value: provided }}\n            : await provided;\n          if (!result.ok) {{\n            if (result.error.kind === \"domain\") {{\n              return {{ kind: \"domain\", value: JSON.parse(encode{operation_name}Error(result.error.error)) as unknown }};\n            }}\n            return {{ kind: \"runtime\", failure: result.error.error }};\n          }}\n          const stream = lowerProviderStream(result.value);\n          const binding: ProviderStreamSessionBinding = {{\n            async send(message) {{\n              let decoded: {response_type};\n              try {{\n                decoded = decode{operation_name}Response(lensoContractRuntime.encodePortableJson(message, \"stream message\"));\n              }} catch (error) {{\n                return {{ kind: \"runtime\", failure: {{ kind: \"protocol_violation\", detail: providerErrorMessage(error) }} }};\n              }}\n              try {{\n                await stream.send(decoded);\n                return {{ kind: \"accepted\" }};\n              }} catch (error) {{\n                return {{ kind: \"runtime\", failure: error instanceof ServerOutputStreamInputError\n                  ? {{ kind: \"protocol_violation\", detail: providerErrorMessage(error) }}\n                  : {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n              }}\n            }},\n            async receive() {{\n              try {{\n                const event = await stream.receive();\n                if (event.kind === \"message\") {{\n                  return {{ kind: \"message\", value: JSON.parse(encode{operation_name}Response(event.message)) as unknown }};\n                }}\n                if (event.kind === \"peer_half_closed\") return {{ kind: \"peer_half_closed\" }};\n                if (event.outcome.ok) return {{ kind: \"terminal_success\" }};\n                return {{ kind: \"terminal_domain\", value: JSON.parse(encode{operation_name}Error(event.outcome.error)) as unknown }};\n              }} catch (error) {{\n                return {{ kind: \"runtime\", failure: {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n              }}\n            }},\n            async closeSend() {{\n              try {{\n                await stream.closeSend();\n                return {{ kind: \"accepted\" }};\n              }} catch (error) {{\n                return {{ kind: \"runtime\", failure: {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n              }}\n            }},\n            cancel() {{ stream.cancel(); }},\n          }};\n          return {{ kind: \"opened\", stream: binding }};\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n        }}\n      }}",
4949                quote_string(&operation.name),
4950            ));
4951            dependency_methods.push(format!(
4952                "      async {provider_method}(request, context) {{\n        let payload: unknown;\n        try {{\n          payload = JSON.parse(encode{operation_name}Request(request)) as unknown;\n        }} catch (error) {{\n          return {{ ok: false, error: {{ kind: \"runtime\", error: {{ kind: \"protocol_violation\", detail: dependencyErrorMessage(error) }} }} }};\n        }}\n        const call = context ?? {{ requestId: \"0\" as Uint64, cancelled: false }};\n        try {{\n          const outcome = await invoke.openStream({}, call, payload);\n          if (outcome.kind === \"domain\") {{\n            return {{ ok: false, error: {{ kind: \"domain\", error: decode{operation_name}Error(JSON.stringify(outcome.value)) }} }};\n          }}\n          if (outcome.kind === \"runtime\") {{\n            return {{ ok: false, error: {{ kind: \"runtime\", error: outcome.failure }} }};\n          }}\n          const stream = outcome.stream;\n          return {{ ok: true, value: {{\n            async send(message) {{\n              const encoded = JSON.parse(encode{operation_name}Response(message)) as unknown;\n              const result = await stream.send(encoded);\n              if (result.kind === \"runtime\") throw dependencyRuntimeError(result.failure);\n            }},\n            async receive() {{\n              const result = await stream.receive();\n              if (result.kind === \"message\") return {{ kind: \"message\", message: decode{operation_name}Response(JSON.stringify(result.value)) }};\n              if (result.kind === \"peer_half_closed\") return {{ kind: \"peer_half_closed\" }};\n              if (result.kind === \"terminal_success\") return {{ kind: \"terminal\", outcome: {{ ok: true }} }};\n              if (result.kind === \"terminal_domain\") return {{ kind: \"terminal\", outcome: {{ ok: false, error: decode{operation_name}Error(JSON.stringify(result.value)) }} }};\n              throw dependencyRuntimeError(result.failure);\n            }},\n            async closeSend() {{\n              const result = await stream.closeSend();\n              if (result.kind === \"runtime\") throw dependencyRuntimeError(result.failure);\n            }},\n            cancel() {{ stream.cancel(); }},\n          }} }};\n        }} catch (error) {{\n          return {{ ok: false, error: {{ kind: \"runtime\", error: dependencyFailure(error) }} }};\n        }}\n      }},",
4953                quote_string(&operation.name),
4954            ));
4955        } else if operation.interaction == "event" {
4956            let provider_method = typescript_property_name(&snake_case(&operation.name));
4957            event_dispatch_arms.push(format!(
4958                "      case {}: {{\n        let event: {request_type};\n        try {{\n          event = decode{operation_name}Event(lensoContractRuntime.encodePortableJson(payload, \"event\"));\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"protocol_violation\", detail: providerErrorMessage(error) }} }};\n        }}\n        try {{\n          await provider.{provider_method}(context, event);\n          return {{ kind: \"accepted\" }};\n        }} catch (error) {{\n          return {{ kind: \"runtime\", failure: {{ kind: \"plugin_failure\", detail: providerErrorMessage(error) }} }};\n        }}\n      }}",
4959                quote_string(&operation.name),
4960            ));
4961            dependency_methods.push(format!(
4962                "      async {provider_method}(event, context) {{\n        let payload: unknown;\n        try {{\n          payload = JSON.parse(encode{operation_name}Event(event)) as unknown;\n        }} catch {{\n          return [{{ subscriberInstance: invoke.providerInstance, admission: \"unavailable\" }}];\n        }}\n        const call = context ?? {{ requestId: \"0\" as Uint64, cancelled: false }};\n        try {{\n          const outcome = await invoke.publishEvent({}, call, payload);\n          return [{{ subscriberInstance: invoke.providerInstance, admission: outcome.kind === \"accepted\" ? \"accepted\" : outcome.failure.kind === \"resource_exhausted\" ? \"exhausted\" : \"unavailable\" }}];\n        }} catch {{\n          return [{{ subscriberInstance: invoke.providerInstance, admission: \"unavailable\" }}];\n        }}\n      }},",
4963                quote_string(&operation.name),
4964            ));
4965        }
4966    }
4967    let mut output = String::new();
4968    output.push_str(TYPESCRIPT_HEADER);
4969    output.push_str("import * as lensoContractRuntime from \"@lenso/contract-runtime\";\n\n");
4970    writeln!(
4971        output,
4972        "export const CAPABILITY_ID = {};",
4973        quote_string(&contract.capability_id)
4974    )
4975    .expect("writing to a String cannot fail");
4976    writeln!(
4977        output,
4978        "export const DESCRIPTOR_VERSION = {};",
4979        quote_string(&contract.version)
4980    )
4981    .expect("writing to a String cannot fail");
4982    writeln!(
4983        output,
4984        "export const DESCRIPTOR_DIGEST = {};",
4985        quote_string(&contract.descriptor_digest)
4986    )
4987    .expect("writing to a String cannot fail");
4988    writeln!(output, "export const PORTABLE = {};", contract.portable)
4989        .expect("writing to a String cannot fail");
4990    write!(
4991        output,
4992        "export const CROSS_LANE_TRANSFER = {};\n\n",
4993        contract.cross_lane_transfer
4994    )
4995    .expect("writing to a String cannot fail");
4996    output.push_str("export type Int64 = lensoContractRuntime.Int64;\nexport type Uint64 = lensoContractRuntime.Uint64;\nexport type Bytes = lensoContractRuntime.Bytes;\nexport type Timestamp = lensoContractRuntime.Timestamp;\nexport type Duration = lensoContractRuntime.Duration;\nexport type OptionalValue<T> = lensoContractRuntime.OptionalValue<T>;\nexport type InvocationContext = lensoContractRuntime.InvocationContext;\nexport type RuntimeFailure = lensoContractRuntime.RuntimeFailure;\nexport type UnknownDomainError = lensoContractRuntime.UnknownDomainError;\nexport type StreamEvent<Message, DomainError> = lensoContractRuntime.StreamEvent<Message, DomainError>;\nexport type StreamSession<Message, DomainError> = lensoContractRuntime.StreamSession<Message, DomainError>;\n\n");
4997    if has_stream_operations {
4998        output.push_str("export type ProviderStream<Message, DomainError> = StreamSession<Message, DomainError> | AsyncIterable<Message>;\n\nclass ServerOutputStreamInputError extends Error {}\n\nfunction isAsyncIterable<Message>(value: unknown): value is AsyncIterable<Message> {\n  return typeof value === \"object\" && value !== null && Symbol.asyncIterator in value;\n}\n\nfunction lowerProviderStream<Message, DomainError>(stream: ProviderStream<Message, DomainError>): StreamSession<Message, DomainError> {\n  if (!isAsyncIterable<Message>(stream)) return stream;\n  const iterator = stream[Symbol.asyncIterator]();\n  let cancelled = false;\n  return {\n    async send() { throw new ServerOutputStreamInputError(\"server-output stream does not accept inbound messages\"); },\n    async receive() {\n      if (cancelled) return { kind: \"terminal\", outcome: { ok: true } };\n      const next = await iterator.next();\n      return next.done\n        ? { kind: \"terminal\", outcome: { ok: true } }\n        : { kind: \"message\", message: next.value };\n    },\n    async closeSend() {},\n    cancel() {\n      cancelled = true;\n      const closing = iterator.return?.();\n      if (closing !== undefined) void Promise.resolve(closing).catch(() => undefined);\n    },\n  };\n}\n\n");
4999    }
5000    output.push_str("export interface CapabilityContractReference<Client, Provider extends object, Runtime extends DependencyInvoker = DependencyInvoker> extends CapabilityDependencyBinding<Client, Runtime> {\n  readonly kind: \"lenso.capability\";\n  readonly capability_id: string;\n  readonly descriptor_version: string;\n  readonly descriptor_digest: string;\n  readonly generated_client: string;\n  readonly descriptor: CapabilityProviderDescriptor;\n  bindProvider(provider: Provider): CapabilityProviderBinding;\n  required(id?: string): CapabilityDependencyDeclaration<Client, \"one\", Runtime>;\n  optional(id?: string): CapabilityDependencyDeclaration<Client, \"optional\", Runtime>;\n  many(id?: string): CapabilityDependencyDeclaration<Client, \"many\", Runtime>;\n  readonly __client?: Client;\n  readonly __provider?: Provider;\n}\n\n");
5001    if has_event_operations {
5002        output.push_str("export type EventAdmission = lensoContractRuntime.EventAdmission;\nexport type EventPublishResult = lensoContractRuntime.EventPublishResult;\n\n");
5003    }
5004    for declaration in types.declarations {
5005        output.push_str(&declaration);
5006        output.push('\n');
5007    }
5008    for error in errors {
5009        output.push_str(&error);
5010        output.push('\n');
5011    }
5012    for codec in codecs {
5013        output.push_str(&codec);
5014        output.push('\n');
5015    }
5016    write!(
5017        output,
5018        "\nexport interface {capability_name}Client {{\n{}\n}}\n\nexport interface {capability_name}Provider {{\n{}\n}}\n",
5019        clients.join("\n"),
5020        providers.join("\n")
5021    )
5022    .expect("writing to a String cannot fail");
5023    let dependency_binding = format!("...bind{capability_name}Dependency(), ");
5024    let dependency_declarations = "required(id) { return { kind: \"lenso.dependency\", ...(id === undefined ? {} : { id }), contract: this, cardinality: \"one\" }; }, optional(id) { return { kind: \"lenso.dependency\", ...(id === undefined ? {} : { id }), contract: this, cardinality: \"optional\" }; }, many(id) { return { kind: \"lenso.dependency\", ...(id === undefined ? {} : { id }), contract: this, cardinality: \"many\" }; }, ".to_owned();
5025    writeln!(
5026        output,
5027        "\nexport const {capability_name}: CapabilityContractReference<{capability_name}Client, {capability_name}Provider, {dependency_invoker_type}> = {{ kind: \"lenso.capability\", {dependency_binding}capability_id: CAPABILITY_ID, descriptor_version: DESCRIPTOR_VERSION, descriptor_digest: DESCRIPTOR_DIGEST, generated_client: {}, descriptor: {{ capability_id: CAPABILITY_ID, descriptor_version: DESCRIPTOR_VERSION, operations: [{operation_names}], stream_operations: [{stream_operation_names}], event_operations: [{event_operation_names}] }}, bindProvider: bind{capability_name}Provider, {dependency_declarations}}};\nexport const {capability_const}_CONTRACT = {capability_name};",
5028        quote_string(&format!("{capability_name}Client")),
5029    )
5030    .expect("writing to a String cannot fail");
5031    write!(
5032        output,
5033        "\nexport type ProviderDispatchOutcome =\n  | {{ readonly kind: \"success\"; readonly value: unknown }}\n  | {{ readonly kind: \"domain\"; readonly value: unknown }}\n  | {{ readonly kind: \"runtime\"; readonly failure: RuntimeFailure }};\nexport type ProviderStreamActionOutcome =\n  | {{ readonly kind: \"accepted\" }}\n  | {{ readonly kind: \"runtime\"; readonly failure: RuntimeFailure }};\nexport type ProviderStreamReceiveOutcome =\n  | {{ readonly kind: \"message\"; readonly value: unknown }}\n  | {{ readonly kind: \"peer_half_closed\" }}\n  | {{ readonly kind: \"terminal_success\" }}\n  | {{ readonly kind: \"terminal_domain\"; readonly value: unknown }}\n  | {{ readonly kind: \"runtime\"; readonly failure: RuntimeFailure }};\n/** @internal Runtime lowering seam. */\nexport interface ProviderStreamSessionBinding {{\n  send(message: unknown): Promise<ProviderStreamActionOutcome>;\n  receive(): Promise<ProviderStreamReceiveOutcome>;\n  closeSend(): Promise<ProviderStreamActionOutcome>;\n  cancel(): void;\n}}\nexport type ProviderStreamOpenOutcome =\n  | {{ readonly kind: \"opened\"; readonly stream: ProviderStreamSessionBinding }}\n  | {{ readonly kind: \"domain\"; readonly value: unknown }}\n  | {{ readonly kind: \"runtime\"; readonly failure: RuntimeFailure }};\nexport type ProviderEventPublishOutcome =\n  | {{ readonly kind: \"accepted\" }}\n  | {{ readonly kind: \"runtime\"; readonly failure: RuntimeFailure }};\n\nexport interface CapabilityProviderDescriptor {{\n  readonly capability_id: string;\n  readonly descriptor_version: string;\n  readonly operations: ReadonlyArray<string>;\n  readonly stream_operations: ReadonlyArray<string>;\n  readonly event_operations: ReadonlyArray<string>;\n}}\n\n/** @internal Runtime lowering seam. */\nexport interface CapabilityProviderBinding {{\n  readonly descriptor: CapabilityProviderDescriptor;\n  invokeRequest(\n    operation: string,\n    context: InvocationContext,\n    payload: unknown,\n  ): Promise<ProviderDispatchOutcome>;\n  openStream(\n    operation: string,\n    context: InvocationContext,\n    payload: unknown,\n  ): Promise<ProviderStreamOpenOutcome>;\n  publishEvent(\n    operation: string,\n    context: InvocationContext,\n    payload: unknown,\n  ): Promise<ProviderEventPublishOutcome>;\n}}\n\nfunction providerErrorMessage(error: unknown): string {{\n  return error instanceof Error ? error.message : String(error);\n}}\n\nexport function bind{capability_name}Provider(\n  provider: {capability_name}Provider,\n): CapabilityProviderBinding {{\n  return {{\n    descriptor: {{\n      capability_id: CAPABILITY_ID,\n      descriptor_version: DESCRIPTOR_VERSION,\n      operations: [{operation_names}],\n      stream_operations: [{stream_operation_names}],\n      event_operations: [{event_operation_names}],\n    }},\n    async invokeRequest(operation, context, payload) {{\n      switch (operation) {{\n{}\n        default:\n          return {{ kind: \"runtime\", failure: {{ kind: \"unknown_operation\", operation }} }};\n      }}\n    }},\n    async openStream(operation, context, payload) {{\n      switch (operation) {{\n{}\n        default:\n          return {{ kind: \"runtime\", failure: {{ kind: \"unknown_operation\", operation }} }};\n      }}\n    }},\n    async publishEvent(operation, context, payload) {{\n      switch (operation) {{\n{}\n        default:\n          return {{ kind: \"runtime\", failure: {{ kind: \"unknown_operation\", operation }} }};\n      }}\n    }},\n  }};\n}}\n\nexport type Provider = {capability_name}Provider;\nexport const bindProvider = bind{capability_name}Provider;\n",
5034        request_dispatch_arms.join("\n"),
5035        stream_dispatch_arms.join("\n"),
5036        event_dispatch_arms.join("\n"),
5037    )
5038    .expect("writing to a String cannot fail");
5039    write!(
5040            output,
5041            "\nexport type DependencyInvoker = (\n  operation: string,\n  context: InvocationContext,\n  payload: unknown,\n) => Promise<ProviderDispatchOutcome>;\n\nexport type InteractionDependencyInvoker = DependencyInvoker & {{\n  readonly providerInstance: string;\n  openStream(operation: string, context: InvocationContext, payload: unknown): Promise<ProviderStreamOpenOutcome>;\n  publishEvent(operation: string, context: InvocationContext, payload: unknown): Promise<ProviderEventPublishOutcome>;\n}};\n\nexport interface CapabilityDependencyBinding<Client, Runtime extends DependencyInvoker = DependencyInvoker> {{\n  readonly descriptor: CapabilityProviderDescriptor;\n  createClient(invoke: Runtime): Client;\n}}\n\nexport interface CapabilityDependencyDeclaration<Client, Cardinality extends \"one\" | \"optional\" | \"many\", Runtime extends DependencyInvoker = DependencyInvoker> {{\n  readonly kind: \"lenso.dependency\";\n  readonly id?: string;\n  readonly contract: CapabilityDependencyBinding<Client, Runtime>;\n  readonly cardinality: Cardinality;\n}}\n\nfunction dependencyErrorMessage(error: unknown): string {{\n  return error instanceof Error ? error.message : String(error);\n}}\n\nfunction dependencyRuntimeError(failure: RuntimeFailure): Error {{\n  return Object.assign(new Error(`Capability dependency failed: ${{failure.kind}}`), {{ failure }});\n}}\n\nfunction dependencyFailure(error: unknown): RuntimeFailure {{\n  if (typeof error === \"object\" && error !== null && \"failure\" in error) return (error as {{ failure: RuntimeFailure }}).failure;\n  return {{ kind: \"plugin_failure\", detail: dependencyErrorMessage(error) }};\n}}\n\nexport function bind{capability_name}Dependency(): CapabilityDependencyBinding<{capability_name}Client, {dependency_invoker_type}> {{\n  return {{\n    descriptor: {{\n      capability_id: CAPABILITY_ID,\n      descriptor_version: DESCRIPTOR_VERSION,\n      operations: [{operation_names}],\n      stream_operations: [{stream_operation_names}],\n      event_operations: [{event_operation_names}],\n    }},\n    createClient(invoke) {{\n      return {{\n{}\n      }};\n    }},\n  }};\n}}\n\nexport const bindDependency = bind{capability_name}Dependency;\n",
5042            dependency_methods.join("\n"),
5043        )
5044        .expect("writing to a String cannot fail");
5045    output.push_str(
5046        "\nexport const portableValueProfile = lensoContractRuntime.portableValueProfile;\n",
5047    );
5048    output
5049}