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