From c3e9a09a3f1e07a89ebad66dce87347e8940497f Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 11:52:24 -0700 Subject: [PATCH 01/10] Reject a root type name that collides with a `$defs` name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A file whose root schema derived the name of one of its own `$defs` entries silently collapsed the two into a single type: the external-type map is keyed by the model's identity (the derived name), so the second insert was dropped by `or_insert_with`. The root's shape won, the `$defs` shape's members vanished, and every `$ref` at the lost entry retargeted at the root — compilable but wrong output in all four languages (`thing.yaml` with `$defs.Thing` emitted one self-referencing `Thing`). The coincidence is now a load reject for every target (P15), naming the identifier and both origins with the renames that resolve it; an `x--name` override cannot, because it moves the emitted identifier and not the identity. A name synthesized for an inline shape is held to the same rule and reported at the position the shape was written in, for which hoisted definitions now carry their authored origin. Behind both, `insert_json_external_type` rejects any two distinct schemas that reach one identity rather than dropping one. --- CHANGELOG.md | 20 ++ specs/json-schema/features/properties.md | 6 +- specs/json-schema/features/ref.md | 18 ++ src/parser/json_schema.rs | 301 ++++++++++++++++++++--- 4 files changed, 314 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1910828..ec6c2b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- JSON Schema: A file's **root type and a same-file `$defs` entry of the same + name** silently collapsed into one type. `thing.yaml` declaring a root object + plus `$defs.Thing` — `Thing` being the name the root derives from the file name + — emitted a single `Thing` carrying the *root's* shape in all four languages, + with the `$defs` entry's members gone and every reference to it retargeted at + the root: Go emitted `Nested *Thing`, TypeScript `nested?: Thing`, Python + `nested: Thing | None`, Java `@Nullable Thing nested`, all pointing at a + self-reference the schema never declared. The coincidence is now a load-time + rejection for every target, naming the identifier and both origins (the root + schema's file-name derivation and the `$defs` entry) with the two renames that + resolve it — the `$defs` key, or the file the root name derives from. A name + **synthesized** for an inline shape that lands on the root type's name is + rejected the same way, reported at the position the shape was written in + (`$defs.User.properties.profile`). This is a new rejection: a schema that hit + the collapse used to generate and now fails to load. An `x--name` + override does not resolve it — the override moves one target's emitted + identifier, while the derived name is the model's identity. Any other route to + one identity is rejected too, rather than dropping a shape: two files whose + root types derive the same name in a module-less in-process load, for + instance. - JSON Schema: A **non-object `oneOf` branch's own constraints** were dropped in three of four languages: only Go carried them, in the synthesized `` variant's `Validate`. TypeScript cast the narrowed value diff --git a/specs/json-schema/features/properties.md b/specs/json-schema/features/properties.md index b82e1c87..806b2559 100644 --- a/specs/json-schema/features/properties.md +++ b/specs/json-schema/features/properties.md @@ -271,8 +271,10 @@ Rules that follow from "the name belongs to the position": describe the object that is now a type; the member falls back to its synthesized doc line — again identical to the `$defs` + `$ref` form. - **P15 is the backstop.** A synthesized name that collides with a declared - `$defs` entry or another synthesized name is a load reject with a fix-it - diagnostic, never auto-mangled. + `$defs` entry, with another synthesized name, or with the **file-root + type**'s name ([[ref]] type-name derivation) is a load reject with a fix-it + diagnostic naming the position the shape was written in, never + auto-mangled. ### Documented limitation diff --git a/specs/json-schema/features/ref.md b/specs/json-schema/features/ref.md index 7c0f8567..6ee6488f 100644 --- a/specs/json-schema/features/ref.md +++ b/specs/json-schema/features/ref.md @@ -156,6 +156,23 @@ widened from per-object to per-package). Consistent with [[properties]], accepted for a Go-only run and rejected for a Java run, because normalization differs per language. +**The derived name is the model's identity.** The one collision that is +*not* per-target is a file-root type and a same-file `$defs` entry that +derive the **same** name (`thing.yaml` with a root type plus +`$defs.Thing`): the derived name is the identity every `$ref` resolves +through and every target emits one type for, so the two schemas would +otherwise collapse into one — the loser's shape dropped and every +reference to it silently retargeted at the winner. It is rejected for +**every** target, and the fix-it is a rename of the `$defs` key or of the +file the root name derives from; an `x--name` override does not +resolve it, because it moves one target's *emitted identifier* and leaves +both schemas on the one identity. A name **synthesized** for an inline +shape ([[properties]]) is held to the same rule — a hoisted `$defs` entry +whose name equals the root type's is rejected where the shape is hoisted, +with a diagnostic naming the position it was written in. A file with no +root type (a definitions-only file or a Nexus document — see +[[input-files]]) has no root name to collide with. + ## Output layout The full package structure (file names, the shared `definitions` file, @@ -276,6 +293,7 @@ helper is emitted — the named-type machinery already in place | Unresolvable | missing file or missing `$defs` entry | | Unsatisfiable cycle | every edge required + non-nullable + single-valued | | Type-name collision | two targets → same identifier in an emitted language (per-target, P15) | +| Root/`$defs` name coincidence | a file-root type and a same-file `$defs` entry — authored or synthesized for an inline shape — derive the same name: one identity, two schemas (all targets, P15) | | Module-name collision | two inputs flatten to the same module name ([[generated-file-layout]]) | ### Runtime fixtures (validator) diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index 3749adab..d4367bf9 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, btree_map}; use std::fs; use std::path::{Path, PathBuf}; @@ -522,7 +522,27 @@ fn parse_json_documents( } } if root_is_schema_shaped(&doc.root) && !doc.root.is_bare_ref() { - let model_name = root_type_name(path).to_upper_camel_case(); + let model_name = root_model_name(path); + // The root type and the file's `$defs` share one namespace (P15), and + // the root's derived name *is* its model identity — the key every + // `$ref` resolves through and every target emits one type for. A + // `$defs` entry of that name is therefore a second schema under one + // identity, which no `x--name` override can separate (an + // override moves the emitted identifier, not the identity), so the + // only fixes are renames. Reject rather than let one shape win. + if doc + .defs + .as_ref() + .is_some_and(|defs| defs.contains_key(&model_name)) + { + return Err(Error::InvalidJsonSchema { + path: path.to_path_buf(), + reason: format!( + "the root schema derives the type name `{model_name}` from the file name `{}`, and the same file declares `$defs.{model_name}`; the two are different schemas that would emit one type. Rename the `$defs` entry (and the `$ref`s that point at it), or rename the file so the root schema derives a different name — an `x--name` override cannot separate them, because the derived name is the model's identity and not just its emitted identifier (P15 — the generator never auto-mangles)", + root_file_name(path), + ), + }); + } models.insert( TypeKey::Root(canonical_path.clone()), JsonModel { @@ -2977,12 +2997,18 @@ fn hoist_inline_object_shapes( docs: &mut IndexMap, ) -> Result<()> { for (path, doc) in docs.values_mut() { + // The type name the file's root schema derives from its file name, when + // the file has a root type at all (a Nexus-document envelope and a + // definitions-only file have none). A synthesized name that coincides + // with it is a P15 collision, checked where the shape is inserted below. + let root_model = (root_is_schema_shaped(&doc.root) && !doc.root.is_bare_ref()) + .then(|| root_model_name(path)); // Fixpoint: a hoisted definition is walked on the next pass, so a union // nested in a hoisted branch's property is hoisted too. Each pass // replaces at least one inline branch with a `$ref` (and never // introduces one), so the walk terminates. loop { - let mut hoisted: Vec<(String, Schema)> = Vec::new(); + let mut hoisted: Vec = Vec::new(); if let Some(defs) = doc.defs.as_mut() { for (name, schema) in defs.iter_mut() { hoist_model_inline_shapes( @@ -2995,12 +3021,11 @@ fn hoist_inline_object_shapes( )?; } } - if root_is_schema_shaped(&doc.root) && !doc.root.is_bare_ref() { - let model_name = root_type_name(path).to_upper_camel_case(); + if let Some(model_name) = &root_model { hoist_model_inline_shapes( language, path, - &model_name, + model_name, "root schema", &mut doc.root, &mut hoisted, @@ -3038,12 +3063,27 @@ fn hoist_inline_object_shapes( break; } let defs = doc.defs.get_or_insert_with(IndexMap::new); - for (name, schema) in hoisted { + for HoistedDef { + name, + origin, + schema, + } in hoisted + { + if root_model.as_deref() == Some(name.as_str()) { + return Err(Error::InvalidJsonSchema { + path: path.to_path_buf(), + reason: format!( + "the name `{name}` synthesized for the inline shape at `{origin}` is the type name the root schema derives from the file name `{}`; the two are different schemas that would emit one type. Name the inline shape with an `{}` override where it takes one (a `oneOf` branch, an array element, a map member), move it into `$defs` under a name of your own and `$ref` it, or rename the file so the root schema derives a different name (P15 — the generator never auto-mangles)", + root_file_name(path), + lang_name_keyword(language).unwrap_or("x--name"), + ), + }); + } if defs.contains_key(&name) { return Err(Error::InvalidJsonSchema { path: path.to_path_buf(), reason: format!( - "the name `{name}` synthesized for an inline shape is already declared in `$defs`; rename either one, name the inline shape with an `{}` override where it takes one (a `oneOf` branch, an array element, a map member), or move it into `$defs` under a name of your own and `$ref` it (P15 — the generator never auto-mangles)", + "the name `{name}` synthesized for the inline shape at `{origin}` is already declared in `$defs`; rename either one, name the inline shape with an `{}` override where it takes one (a `oneOf` branch, an array element, a map member), or move it into `$defs` under a name of your own and `$ref` it (P15 — the generator never auto-mangles)", lang_name_keyword(language).unwrap_or("x--name"), ), }); @@ -3055,6 +3095,22 @@ fn hoist_inline_object_shapes( Ok(()) } +/// One inline shape queued for insertion into `$defs` by +/// [`hoist_inline_object_shapes`]: the name synthesized for it, the authored +/// position it was written in, and the shape itself. The origin travels with the +/// name so a collision diagnostic can say *where* the synthesized name came from +/// — the author never wrote the name itself, so naming only the identifier would +/// leave them hunting for the shape that produced it. +struct HoistedDef { + /// The synthesized `$defs` key (or the shape's own `x--name`). + name: String, + /// The authored position, as a keyword breadcrumb — for example + /// `$defs.User.properties.profile` or `root schema.items`. + origin: String, + /// The shape moved out of that position. + schema: Schema, +} + /// Hoists the inline shapes a model declares that need a name: the object /// branches of its unions — its own (a named `$defs` union) and each property's /// (an anonymous union, named `` — the [[properties]] @@ -3067,7 +3123,7 @@ fn hoist_model_inline_shapes( model_name: &str, context: &str, schema: &mut Schema, - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result<()> { if let Some(branches) = schema.one_of.as_mut() { // The model *is* the union, so the union carries its own name and its @@ -3136,7 +3192,7 @@ fn hoist_property_shape( property_name: &str, context: &str, property: &mut Schema, - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result<()> { if is_sum_type_union(property) { let branches = property.one_of.as_mut().expect("a union has branches"); @@ -3171,7 +3227,11 @@ fn hoist_property_shape( property.extra.insert(keyword.to_string(), value); } } - hoisted.push((property_name.to_string(), shape)); + hoisted.push(HoistedDef { + name: property_name.to_string(), + origin: context.to_string(), + schema: shape, + }); Ok(()) } @@ -3197,7 +3257,7 @@ fn hoist_subschema_shapes( base_name: &str, context: &str, schema: &mut Schema, - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result<()> { if let Some(items) = schema.items.as_mut() { hoist_subschema_shape( @@ -3246,11 +3306,11 @@ fn hoist_subschema_shape( name: &str, context: &str, slot: &mut Schema, - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result<()> { if is_sum_type_union(slot) || is_inline_object_shape(slot) { let name = resolve_shape_name(language, name, slot, context)?; - move_into_defs(slot, name, hoisted); + move_into_defs(slot, name, context.to_string(), hoisted); return Ok(()); } if hoist_nullable_object_branch(language, name, context, slot, hoisted)? { @@ -3270,7 +3330,7 @@ fn hoist_nullable_object_branch( derived: &str, context: &str, slot: &mut Schema, - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result { if is_sum_type_union(slot) { return Ok(false); @@ -3283,7 +3343,7 @@ fn hoist_nullable_object_branch( return Ok(false); }; let name = resolve_shape_name(language, derived, branch, context)?; - move_into_defs(branch, name, hoisted); + move_into_defs(branch, name, context.to_string(), hoisted); Ok(true) } @@ -3311,13 +3371,17 @@ fn resolve_shape_name( /// Replaces a schema position with a `$ref` at `name` and queues the shape that /// was written there for insertion into `$defs`. -fn move_into_defs(slot: &mut Schema, name: String, hoisted: &mut Vec<(String, Schema)>) { +fn move_into_defs(slot: &mut Schema, name: String, origin: String, hoisted: &mut Vec) { let shape = std::mem::take(slot); *slot = Schema { reference: Some(format!("#/$defs/{name}")), ..Schema::default() }; - hoisted.push((name, shape)); + hoisted.push(HoistedDef { + name, + origin, + schema: shape, + }); } /// True when a `oneOf` node is a **sum type** — two or more non-`null` branches @@ -3351,7 +3415,7 @@ fn hoist_union_object_branches( derived: &str, context: &str, branches: &mut [Schema], - hoisted: &mut Vec<(String, Schema)>, + hoisted: &mut Vec, ) -> Result<()> { let inline: Vec = branches .iter() @@ -3390,7 +3454,12 @@ fn hoist_union_object_branches( }); } }; - move_into_defs(&mut branches[index], name, hoisted); + move_into_defs( + &mut branches[index], + name, + format!("{context}.oneOf[{index}]"), + hoisted, + ); } Ok(()) } @@ -4847,15 +4916,42 @@ fn insert_json_external_type( module_paths: Option<&BTreeMap>, ) -> Result<()> { let type_spec = json_model_spec(model, docs, models, module_paths)?; - external_types - .entry(type_spec.name.as_str().to_string()) - .or_insert_with(|| ExternalTypeBindingSpec { - external_type: ExternalTypeSpec::Json(type_spec), - reference: LanguageStringSpec::default(), - type_name: language_string(Some(model.model_name.clone())), - replacement: None, - authored_type: None, - }); + // The map is keyed by the model's identity, and one model is reached from + // several positions (its own collection pass, each `$ref` at it, an + // operation's I/O), so re-inserting the *same* model is an ordinary no-op. + // Two *different* schemas arriving under one identity would collapse into a + // single emitted type — the loser's shape gone, every reference to it + // silently retargeted at the winner — so reject instead (P7.1/P15). The + // in-file cases are caught earlier with a fix-it that names the authored + // positions; this is the backstop that keeps any other path from collapsing + // silently. + match external_types.entry(type_spec.name.as_str().to_string()) { + btree_map::Entry::Occupied(existing) => { + if let ExternalTypeSpec::Json(previous) = &existing.get().external_type + && (previous.model_name != type_spec.model_name + || previous.schema != type_spec.schema) + { + return Err(Error::InvalidJsonSchema { + path: model.canonical_path.clone(), + reason: format!( + "two different JSON schemas share the model identity `{}` (emitted as `{}` and `{}`); rename one of them so each schema has an identity of its own (P15 — the generator never auto-mangles)", + type_spec.name.as_str(), + previous.model_name, + type_spec.model_name, + ), + }); + } + } + btree_map::Entry::Vacant(slot) => { + slot.insert(ExternalTypeBindingSpec { + external_type: ExternalTypeSpec::Json(type_spec), + reference: LanguageStringSpec::default(), + type_name: language_string(Some(model.model_name.clone())), + replacement: None, + authored_type: None, + }); + } + } Ok(()) } @@ -5035,6 +5131,22 @@ fn root_type_name(path: &Path) -> String { .unwrap_or_else(|| "Root".to_string()) } +/// The type name a file's root schema derives: its base name, recased (see +/// `specs/json-schema/features/ref.md` §"Type-name derivation"). The single +/// source of the root model's identity — model collection, the hoist collision +/// check, and the root-vs-`$defs` collision check all read it from here. +fn root_model_name(path: &Path) -> String { + root_type_name(path).to_upper_camel_case() +} + +/// The input file's name as authored, for a diagnostic that has to explain that a +/// name was derived from it. +fn root_file_name(path: &Path) -> String { + path.file_name() + .map(|value| value.to_string_lossy().to_string()) + .unwrap_or_else(|| path.display().to_string()) +} + fn canonical(path: &Path) -> PathBuf { fs::canonicalize(path).unwrap_or_else(|_| normalize(path)) } @@ -8972,6 +9084,137 @@ $defs: ); } + #[test] + fn rejects_root_type_name_collision_with_defs_entry() { + // `thing.yaml`'s root schema derives the type name `Thing`, and the same + // file declares `$defs.Thing` — two different schemas under one model + // identity, which is a P15 collision in every target's namespace. The + // diagnostic names the identifier and both origins (the root schema's + // file-name derivation and the `$defs` entry), and the fix-it is a rename: + // an `x--name` moves the emitted identifier, not the identity. + let input = r##" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + rootOnlyField: { type: string } + nested: { $ref: "#/$defs/Thing" } +$defs: + Thing: + type: object + properties: { defOnlyField: { type: integer } } +"##; + for language in [ + Language::Go, + Language::TypeScript, + Language::Python, + Language::Java, + ] { + let error = parse_api_spec_from_json_schema_for_language( + language, + input, + PathBuf::from("thing.yaml"), + ) + .expect_err("a root/`$defs` name collision is a load reject") + .to_string(); + assert!( + error.contains("`Thing`") + && error.contains("file name `thing.yaml`") + && error.contains("`$defs.Thing`") + && error.contains("Rename the `$defs` entry") + && error.contains("rename the file") + && error.contains("`x--name` override cannot separate them"), + "{language:?}: {error}" + ); + } + + // The collision is the *root type's* name, so a definitions-only file of + // the same base name (no file-root type) keeps loading. + let definitions_only = r##" +$schema: https://json-schema.org/draft/2020-12/schema +$defs: + Thing: + type: object + properties: { defOnlyField: { type: integer } } +"##; + parse_api_spec_from_json_schema_for_language( + Language::Go, + definitions_only, + PathBuf::from("thing.yaml"), + ) + .expect("a definitions-only file emits no root type, so nothing collides"); + } + + #[test] + fn rejects_hoisted_shape_name_collision_with_root_type_name() { + // The inline object at `$defs.User.properties.profile` is named + // `UserProfile`, which is also the type name `userProfile.yaml`'s root + // schema derives — so the synthesized name collides with the root type. + let error = parse_api_spec_from_json_schema_for_language( + Language::TypeScript, + r##" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + user: { $ref: "#/$defs/User" } +$defs: + User: + type: object + properties: + profile: + type: object + properties: { nickname: { type: string } } +"##, + PathBuf::from("userProfile.yaml"), + ) + .expect_err("a synthesized name that collides with the root type is a load reject") + .to_string(); + assert!( + error.contains("`UserProfile`") + && error.contains("`$defs.User.properties.profile`") + && error.contains("file name `userProfile.yaml`") + && error.contains("`x-ts-name`") + && error.contains("rename the file"), + "{error}" + ); + } + + #[test] + fn rejects_two_schemas_sharing_one_model_identity() { + // The backstop behind the two rejects above: whatever route two different + // schemas take to one model identity, they never collapse into a single + // emitted type. Here two root types derive `User` in a flat (module-less) + // load of both files. + let error = api_spec_from_json_schema_sources( + Language::Python, + vec![ + ( + PathBuf::from("a/user.yaml"), + r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: { first: { type: string } } +"# + .to_string(), + ), + ( + PathBuf::from("b/user.yaml"), + r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: { second: { type: string } } +"# + .to_string(), + ), + ], + ) + .expect_err("two schemas under one identity is a load reject") + .to_string(); + assert!( + error.contains("model identity `User`") && error.contains("rename"), + "{error}" + ); + } + #[test] fn rejects_invalid_and_reserved_overrides() { // A leading-digit override is not a legal identifier. From 052132be7949211ea6c3bf995c0ee1e26bbf0046 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Thu, 13 Aug 2026 16:51:59 -0700 Subject: [PATCH 02/10] Emit TypeScript transfer type converters and operation type info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the TypeScript output's mapper concept with nexus-rpc's TransferTypeConverter contract (nexus-rpc/sdk-typescript#40) and wire it into every generated operation. A model's companion is now an exported instance rather than a class: `class Mapper` with fromIntermediate/toIntermediate becomes `export const TransferTypeConverter = new class implements TransferTypeConverter { … }()` with fromTransferType/toTransferType. Call sites drop the construction. The identifier is the model's resolved type identifier lower-camel-cased, read back through the name manifest wherever a reference is emitted — the plan hands operations their own type copies, which carry the derived name, not the resolved one — so an x-ts-name override moves the type, its converter, and the operation that names them together. Method bodies are unchanged — TransferTypeConverter defaults its transfer type to `unknown`, which already matched the emitted signatures. Each non-void side of nexus.operation now carries inputType/outputType = { transferTypeConverter: … } naming the I/O model's converter, so a protocol integration can apply the conversion without the caller wiring it by hand. A void side carries neither field: there is no value to convert, and an empty TypeInfo would assert a conversion that does not exist. Cross-module I/O imports the converter as a value from its declaring module. JSON-Schema input only — the WIT tier has no converters and its output is byte-identical. Since TransferTypeConverter is imported into every models.ts, it joins TypeScript's reserved runtime identifiers: a $defs type of that name now rejects at load with a fix-it instead of emitting an import/local conflict. The derived converter identifiers enter that same per-module namespace, because lower-camel-casing folds names the type namespace keeps apart — HTTPError and HttpError both yield httpErrorTransferTypeConverter. A fold, or a service whose x-ts-name lands on a converter name, is now a P15 load reject instead of a module that declares one const twice. The parser owns the derivation, as it owns the rest of the per-language naming policy, so the collision pass and the emitters cannot drift apart. The published nexus-rpc 0.0.2 predates the type-info API and upstream has no prepare script, so a git dependency installs unbuilt. Runtime is unaffected (operation() spreads its options through untouched), so both sample projects carry a types-only augmentation under shims/ to bridge typechecking until the release lands. --- CHANGELOG.md | 29 +- .../typescript/json_schema/api/chat/models.ts | 682 ++++++++--------- .../json_schema/api/chat/services.ts | 14 +- .../api/kb/content/block/models.ts | 349 ++++----- .../json_schema/api/kb/kb/models.ts | 208 +++--- .../json_schema/api/kb/kb/services.ts | 22 +- .../json_schema/api/showcase/services.ts | 10 +- .../json_schema/api/temporal-date/models.ts | 462 ++++++------ .../api/temporal-temporal/models.ts | 482 ++++++------ .../json_schema/api/temporal/models.ts | 456 ++++++------ .../typescript/shims/nexus-rpc-type-info.d.ts | 37 + advanced/samples/typescript/tsconfig.json | 2 +- samples/typescript/README.md | 4 +- samples/typescript/chat/models.ts | 686 +++++++++--------- samples/typescript/chat/services.ts | 14 +- samples/typescript/kb/content/block/models.ts | 351 ++++----- samples/typescript/kb/kb/models.ts | 208 +++--- samples/typescript/kb/kb/services.ts | 22 +- .../typescript/shims/nexus-rpc-type-info.d.ts | 37 + samples/typescript/showcase/services.ts | 10 +- samples/typescript/temporal-date/models.ts | 462 ++++++------ .../typescript/temporal-temporal/models.ts | 482 ++++++------ samples/typescript/temporal/models.ts | 456 ++++++------ .../typescript/tests/json-converter-helper.ts | 37 +- .../typescript/tests/json-schema-chat.test.ts | 44 +- .../tests/json-schema-kb-nexus.test.ts | 62 +- .../typescript/tests/json-schema-kb.test.ts | 40 +- .../tests/json-schema-showcase.test.ts | 429 ++++++----- .../tests/json-schema-temporal.test.ts | 31 +- .../tests/workflows/json-schema-kb.ts | 37 +- samples/typescript/tsconfig.json | 1 + specs/json-schema/PRINCIPLES.md | 4 +- specs/json-schema/features/const.md | 2 +- specs/json-schema/features/contentEncoding.md | 4 +- specs/json-schema/features/default.md | 2 +- specs/json-schema/features/items.md | 2 +- specs/json-schema/features/maxProperties.md | 2 +- specs/json-schema/features/minProperties.md | 2 +- specs/json-schema/features/oneOf.md | 19 +- specs/json-schema/features/properties.md | 2 +- specs/json-schema/features/ref.md | 4 +- specs/json-schema/features/required.md | 2 +- specs/json-schema/generated-file-layout.md | 12 +- specs/json-schema/nullability.md | 2 +- specs/json-schema/services.md | 61 +- src/generator/json_schema/typescript.rs | 185 +++-- src/generator/typescript.rs | 71 +- src/parser/json_schema.rs | 164 ++++- src/parser/mod.rs | 5 +- tests/generate_typescript.rs | 198 ++++- 50 files changed, 3864 insertions(+), 3045 deletions(-) create mode 100644 advanced/samples/typescript/shims/nexus-rpc-type-info.d.ts create mode 100644 samples/typescript/shims/nexus-rpc-type-info.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ec6c2b50..cbd7a1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 — the one sibling keyword treated this way, because it asserts nothing about the value, and the only way to rename a member whose type is a `$ref` (a member named `class` was otherwise unfixable in Python and Java). +- TypeScript: A model's companion converter is now an exported + `TransferTypeConverter` **instance** instead of a class. `class Mapper` + with `fromIntermediate`/`toIntermediate` becomes + `export const TransferTypeConverter = new class implements + TransferTypeConverter { … }()` with `fromTransferType`/`toTransferType`, + implementing the contract from + [nexus-rpc/sdk-typescript#40](https://github.com/nexus-rpc/sdk-typescript/pull/40). + Call sites drop the construction: `new UserMapper().fromIntermediate(raw)` + becomes `userTransferTypeConverter.fromTransferType(raw)`. The converter + identifier is the model's resolved type name lower-camel-cased, so an + `x-ts-name` override moves it. `models.ts` now carries a type-only + `import type { TransferTypeConverter } from "nexus-rpc"`. Because the + identifier is derived by lower-camel-casing, it takes part in the load-time + identifier-collision check: two models in one module whose converter names + coincide (for example `HTTPError` and `HttpError`) are now rejected with a + fix-it, as is a service whose `x-ts-name` lands on a converter name. +- TypeScript: Generated operations now carry operation type info. Each + non-void side of `nexus.operation` emits + `inputType`/`outputType` = `{ transferTypeConverter: … }` naming the I/O + model's converter, so a protocol integration can apply the conversion without + the caller wiring it by hand; a void side carries neither field. Applies to + JSON-Schema input only — WIT-input operations are unchanged. Requires a + `nexus-rpc` release that includes the type-info API. - Generating into an existing `--output` directory no longer deletes it first. The directory is written into instead, so pre-existing files and subdirectories are preserved; generated files are still overwritten in place. @@ -219,8 +242,8 @@ array"` at runtime, though `items.md` accepts them. Both now decode elementwise, comments. - JSON Schema: A `oneOf` with an inline object branch generated uncompilable Go (a marker method on an undeclared `Object` type) and uncompilable - TypeScript (`new RecordMapper()`); Java bound the branch to - `null` without a violation. + TypeScript (a converter named after the anonymous `Record` + branch type); Java bound the branch to `null` without a violation. - Java: An object branch of a union written inline on a property was silently dropped — the branch's class implemented nothing, and the parse arm for the object token was empty. The branch class now implements the nested union @@ -244,7 +267,7 @@ array"` at runtime, though `items.md` accepts them. Both now decode elementwise, - JSON Schema: TypeScript serialized an object member of a property-level union by copying the in-memory value, so the model's `additionalProperties` member reached the wire as a literal key and its extras were never spread back out. - The union now serializes through the branch's mapper. + The union now serializes through the branch's converter. - JSON Schema: TypeScript's serializer for a mixed-kind union returned the lone object branch unconditionally, making the scalar/array branches unreachable; the object branch is now guarded by the object token, matching the parse side. diff --git a/advanced/samples/typescript/json_schema/api/chat/models.ts b/advanced/samples/typescript/json_schema/api/chat/models.ts index 00fe5791..bd1b1482 100644 --- a/advanced/samples/typescript/json_schema/api/chat/models.ts +++ b/advanced/samples/typescript/json_schema/api/chat/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; export function requiredField( @@ -73,412 +74,421 @@ export interface SendMessageOutput { messageId: string; } -export class GetRoomInputMapper { - public fromIntermediate(raw: unknown): GetRoomInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const getRoomInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetRoomInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); } else { - roomId = raw.roomId; + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "roomId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "roomId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetRoomInput = { roomId }; + return out; } - const out: GetRoomInput = { roomId }; - return out; - } - public toIntermediate(value: GetRoomInput): unknown { - const out: Record = {}; - out.roomId = value.roomId; - return out; - } -} - -export class LabelsMapper { - public fromIntermediate(raw: unknown): Labels { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetRoomInput): unknown { + const out: Record = {}; + out.roomId = value.roomId; + return out; } + })(); - const keys = Object.keys(raw); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; - } - if (entry !== undefined) { - additionalProperties[key] = entry; +export const labelsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Labels { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Labels): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + const keys = Object.keys(raw); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); + } + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - return out; - } -} -export class MessageMapper { - public fromIntermediate(raw: unknown): Message { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + public toTransferType(value: Labels): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; + } + })(); + +export const messageTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Message { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "text" = undefined as unknown as "text"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "text"` }); + let kind: "text" = undefined as unknown as "text"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "text"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "text"` }); + } else { + kind = raw.kind as "text"; + } } - } - let body: string = undefined as unknown as string; - if (raw.body === undefined || raw.body === null) { - violations.push({ path: "body", reason: "required" }); - } else { - if (typeof raw.body !== "string") { - violations.push({ path: "body", reason: "expected string" }); + let body: string = undefined as unknown as string; + if (raw.body === undefined || raw.body === null) { + violations.push({ path: "body", reason: "required" }); } else { - body = raw.body; + if (typeof raw.body !== "string") { + violations.push({ path: "body", reason: "expected string" }); + } else { + body = raw.body; + } } - } - let replyToId: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.replyToId !== undefined) { - if (raw.replyToId === null) { - replyToId = null; - } else { - if (typeof raw.replyToId !== "string") { - violations.push({ path: "replyToId", reason: "expected string" }); + let replyToId: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.replyToId !== undefined) { + if (raw.replyToId === null) { + replyToId = null; } else { - replyToId = raw.replyToId; + if (typeof raw.replyToId !== "string") { + violations.push({ path: "replyToId", reason: "expected string" }); + } else { + replyToId = raw.replyToId; + } } } - } - let priority: number | undefined = undefined as unknown as number | undefined; - if (raw.priority === null) { - violations.push({ path: "priority", reason: "explicit null not allowed" }); - } else if (raw.priority !== undefined) { - if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { - violations.push({ path: "priority", reason: "expected integer" }); - } else { - priority = raw.priority; + let priority: number | undefined = undefined as unknown as number | undefined; + if (raw.priority === null) { + violations.push({ path: "priority", reason: "explicit null not allowed" }); + } else if (raw.priority !== undefined) { + if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { + violations.push({ path: "priority", reason: "expected integer" }); + } else { + priority = raw.priority; + } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "kind" && - key !== "body" && - key !== "replyToId" && - key !== "priority" - ) { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if ( + key !== "kind" && + key !== "body" && + key !== "replyToId" && + key !== "priority" + ) { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Message = { kind, body }; - if (replyToId !== undefined) { - out.replyToId = replyToId; - } - if (priority !== undefined) { - out.priority = priority; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Message = { kind, body }; + if (replyToId !== undefined) { + out.replyToId = replyToId; + } + if (priority !== undefined) { + out.priority = priority; + } + return out; } - return out; - } - public toIntermediate(value: Message): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "text") { - violations.push({ path: "kind", reason: `must equal "text"` }); - } - out.kind = value.kind; - out.body = value.body; - if (value.replyToId !== undefined) { - out.replyToId = value.replyToId; - } - if (value.priority !== undefined) { - out.priority = value.priority; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Message): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "text") { + violations.push({ path: "kind", reason: `must equal "text"` }); + } + out.kind = value.kind; + out.body = value.body; + if (value.replyToId !== undefined) { + out.replyToId = value.replyToId; + } + if (value.priority !== undefined) { + out.priority = value.priority; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const ROOM_DECLARED = new Set(["roomId", "displayName", "topic", "members", "labels"]); -export class RoomMapper { - public fromIntermediate(raw: unknown): Room { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); - } else { - roomId = raw.roomId; +export const roomTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Room { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let displayName: string = undefined as unknown as string; - if (raw.displayName === undefined || raw.displayName === null) { - violations.push({ path: "displayName", reason: "required" }); - } else { - if (typeof raw.displayName !== "string") { - violations.push({ path: "displayName", reason: "expected string" }); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); } else { - displayName = raw.displayName; + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - let topic: string | null = undefined as unknown as string | null; - if (raw.topic === undefined) { - violations.push({ path: "topic", reason: "required" }); - } else { - if (raw.topic === null) { - topic = null; + let displayName: string = undefined as unknown as string; + if (raw.displayName === undefined || raw.displayName === null) { + violations.push({ path: "displayName", reason: "required" }); } else { - if (typeof raw.topic !== "string") { - violations.push({ path: "topic", reason: "expected string" }); + if (typeof raw.displayName !== "string") { + violations.push({ path: "displayName", reason: "expected string" }); } else { - topic = raw.topic; + displayName = raw.displayName; } } - } - let members: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.members === null) { - violations.push({ path: "members", reason: "explicit null not allowed" }); - } else if (raw.members !== undefined) { - if (!Array.isArray(raw.members)) { - violations.push({ path: "members", reason: "expected array" }); + let topic: string | null = undefined as unknown as string | null; + if (raw.topic === undefined) { + violations.push({ path: "topic", reason: "required" }); } else { - members = []; - raw.members.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `members[${index}]`, reason: "expected element" }); + if (raw.topic === null) { + topic = null; + } else { + if (typeof raw.topic !== "string") { + violations.push({ path: "topic", reason: "expected string" }); } else { - item = element; + topic = raw.topic; } - if (item !== undefined) { - members!.push(item); - } - }); + } } - } - let labels: Labels | undefined = undefined as unknown as Labels | undefined; - if (raw.labels === null) { - violations.push({ path: "labels", reason: "explicit null not allowed" }); - } else if (raw.labels !== undefined) { - try { - labels = new LabelsMapper().fromIntermediate(raw.labels); - } catch (error) { - __nexgenDefinitions.collect(violations, "labels", error); + let members: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.members === null) { + violations.push({ path: "members", reason: "explicit null not allowed" }); + } else if (raw.members !== undefined) { + if (!Array.isArray(raw.members)) { + violations.push({ path: "members", reason: "expected array" }); + } else { + members = []; + raw.members.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ + path: `members[${index}]`, + reason: "expected element", + }); + } else { + item = element; + } + if (item !== undefined) { + members!.push(item); + } + }); + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!ROOM_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + let labels: Labels | undefined = undefined as unknown as Labels | undefined; + if (raw.labels === null) { + violations.push({ path: "labels", reason: "explicit null not allowed" }); + } else if (raw.labels !== undefined) { + try { + labels = labelsTransferTypeConverter.fromTransferType(raw.labels); + } catch (error) { + __nexgenDefinitions.collect(violations, "labels", error); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Room = { roomId, displayName, topic, additionalProperties }; - if (members !== undefined) { - out.members = members; - } - if (labels !== undefined) { - out.labels = labels; - } - return out; - } + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!ROOM_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } + } - public toIntermediate(value: Room): unknown { - const out: Record = {}; - out.roomId = value.roomId; - out.displayName = value.displayName; - out.topic = value.topic; - if (value.members !== undefined) { - out.members = value.members; - } - if (value.labels !== undefined) { - out.labels = new LabelsMapper().toIntermediate(value.labels); - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Room = { roomId, displayName, topic, additionalProperties }; + if (members !== undefined) { + out.members = members; + } + if (labels !== undefined) { + out.labels = labels; + } + return out; } - return out; - } -} -export class SendMessageInputMapper { - public fromIntermediate(raw: unknown): SendMessageInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + public toTransferType(value: Room): unknown { + const out: Record = {}; + out.roomId = value.roomId; + out.displayName = value.displayName; + out.topic = value.topic; + if (value.members !== undefined) { + out.members = value.members; + } + if (value.labels !== undefined) { + out.labels = labelsTransferTypeConverter.toTransferType(value.labels); + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; + } + })(); + +export const sendMessageInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): SendMessageInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); } else { - roomId = raw.roomId; + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - let message: Message = undefined as unknown as Message; - if (raw.message === undefined || raw.message === null) { - violations.push({ path: "message", reason: "required" }); - } else { - try { - message = new MessageMapper().fromIntermediate(raw.message); - } catch (error) { - __nexgenDefinitions.collect(violations, "message", error); + let message: Message = undefined as unknown as Message; + if (raw.message === undefined || raw.message === null) { + violations.push({ path: "message", reason: "required" }); + } else { + try { + message = messageTransferTypeConverter.fromTransferType(raw.message); + } catch (error) { + __nexgenDefinitions.collect(violations, "message", error); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "roomId" && key !== "message") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "roomId" && key !== "message") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: SendMessageInput = { roomId, message }; - return out; - } - public toIntermediate(value: SendMessageInput): unknown { - const out: Record = {}; - out.roomId = value.roomId; - out.message = new MessageMapper().toIntermediate(value.message); - return out; - } -} - -export class SendMessageOutputMapper { - public fromIntermediate(raw: unknown): SendMessageOutput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: SendMessageInput = { roomId, message }; + return out; + } + + public toTransferType(value: SendMessageInput): unknown { + const out: Record = {}; + out.roomId = value.roomId; + out.message = messageTransferTypeConverter.toTransferType(value.message); + return out; + } + })(); + +export const sendMessageOutputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): SendMessageOutput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let messageId: string = undefined as unknown as string; - if (raw.messageId === undefined || raw.messageId === null) { - violations.push({ path: "messageId", reason: "required" }); - } else { - if (typeof raw.messageId !== "string") { - violations.push({ path: "messageId", reason: "expected string" }); + let messageId: string = undefined as unknown as string; + if (raw.messageId === undefined || raw.messageId === null) { + violations.push({ path: "messageId", reason: "required" }); } else { - messageId = raw.messageId; + if (typeof raw.messageId !== "string") { + violations.push({ path: "messageId", reason: "expected string" }); + } else { + messageId = raw.messageId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "messageId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "messageId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: SendMessageOutput = { messageId }; + return out; } - const out: SendMessageOutput = { messageId }; - return out; - } - public toIntermediate(value: SendMessageOutput): unknown { - const out: Record = {}; - out.messageId = value.messageId; - return out; - } -} + public toTransferType(value: SendMessageOutput): unknown { + const out: Record = {}; + out.messageId = value.messageId; + return out; + } + })(); diff --git a/advanced/samples/typescript/json_schema/api/chat/services.ts b/advanced/samples/typescript/json_schema/api/chat/services.ts index 45b73cb9..d48b47e3 100644 --- a/advanced/samples/typescript/json_schema/api/chat/services.ts +++ b/advanced/samples/typescript/json_schema/api/chat/services.ts @@ -2,6 +2,12 @@ import * as nexus from "nexus-rpc"; import * as workflow from "@temporalio/workflow"; +import { + getRoomInputTransferTypeConverter, + roomTransferTypeConverter, + sendMessageInputTransferTypeConverter, + sendMessageOutputTransferTypeConverter, +} from "./models"; import type { GetRoomInput, Room, SendMessageInput, SendMessageOutput } from "./models"; /** @@ -13,11 +19,17 @@ export const chatService = nexus.service("example.chat.v1.ChatService", { */ sendMessage: nexus.operation({ name: "SendMessage", + inputType: { transferTypeConverter: sendMessageInputTransferTypeConverter }, + outputType: { transferTypeConverter: sendMessageOutputTransferTypeConverter }, }), /** * Look up a room by id. */ - getRoom: nexus.operation({ name: "GetRoom" }), + getRoom: nexus.operation({ + name: "GetRoom", + inputType: { transferTypeConverter: getRoomInputTransferTypeConverter }, + outputType: { transferTypeConverter: roomTransferTypeConverter }, + }), /** * Liveness probe. */ diff --git a/advanced/samples/typescript/json_schema/api/kb/content/block/models.ts b/advanced/samples/typescript/json_schema/api/kb/content/block/models.ts index 20d75947..f2cc02b2 100644 --- a/advanced/samples/typescript/json_schema/api/kb/content/block/models.ts +++ b/advanced/samples/typescript/json_schema/api/kb/content/block/models.ts @@ -1,7 +1,8 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; -import { PageMapper } from "../page/models"; +import { pageTransferTypeConverter } from "../page/models"; import type { Page } from "../page/models"; export function requiredField( @@ -40,202 +41,214 @@ export interface BlockStyle { indent?: number; } -export class BlockMapper { - public fromIntermediate(raw: unknown): Block { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let blockId: string = undefined as unknown as string; - if (raw.blockId === undefined || raw.blockId === null) { - violations.push({ path: "blockId", reason: "required" }); - } else { - if (typeof raw.blockId !== "string") { - violations.push({ path: "blockId", reason: "expected string" }); - } else { - blockId = raw.blockId; +export const blockTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Block { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let order: number = undefined as unknown as number; - if (raw.order === undefined || raw.order === null) { - violations.push({ path: "order", reason: "required" }); - } else { - if (typeof raw.order !== "number" || !Number.isSafeInteger(raw.order)) { - violations.push({ path: "order", reason: "expected integer" }); + let blockId: string = undefined as unknown as string; + if (raw.blockId === undefined || raw.blockId === null) { + violations.push({ path: "blockId", reason: "required" }); } else { - order = raw.order; - if (raw.order < 0) { - violations.push({ path: "order", reason: `must be >= 0, got ${raw.order}` }); + if (typeof raw.blockId !== "string") { + violations.push({ path: "blockId", reason: "expected string" }); + } else { + blockId = raw.blockId; } } - } - let text: string | undefined = undefined as unknown as string | undefined; - if (raw.text === null) { - violations.push({ path: "text", reason: "explicit null not allowed" }); - } else if (raw.text !== undefined) { - if (typeof raw.text !== "string") { - violations.push({ path: "text", reason: "expected string" }); + let order: number = undefined as unknown as number; + if (raw.order === undefined || raw.order === null) { + violations.push({ path: "order", reason: "required" }); } else { - text = raw.text; + if (typeof raw.order !== "number" || !Number.isSafeInteger(raw.order)) { + violations.push({ path: "order", reason: "expected integer" }); + } else { + order = raw.order; + if (raw.order < 0) { + violations.push({ + path: "order", + reason: `must be >= 0, got ${raw.order}`, + }); + } + } } - } - let style: BlockStyle | undefined = undefined as unknown as BlockStyle | undefined; - if (raw.style === null) { - violations.push({ path: "style", reason: "explicit null not allowed" }); - } else if (raw.style !== undefined) { - try { - style = new BlockStyleMapper().fromIntermediate(raw.style); - } catch (error) { - __nexgenDefinitions.collect(violations, "style", error); + let text: string | undefined = undefined as unknown as string | undefined; + if (raw.text === null) { + violations.push({ path: "text", reason: "explicit null not allowed" }); + } else if (raw.text !== undefined) { + if (typeof raw.text !== "string") { + violations.push({ path: "text", reason: "expected string" }); + } else { + text = raw.text; + } } - } - let page: Page | null | undefined = undefined as unknown as Page | null | undefined; - if (raw.page !== undefined) { - if (raw.page === null) { - page = null; - } else { + let style: BlockStyle | undefined = undefined as unknown as + | BlockStyle + | undefined; + if (raw.style === null) { + violations.push({ path: "style", reason: "explicit null not allowed" }); + } else if (raw.style !== undefined) { try { - page = new PageMapper().fromIntermediate(raw.page); + style = blockStyleTransferTypeConverter.fromTransferType(raw.style); } catch (error) { - __nexgenDefinitions.collect(violations, "page", error); + __nexgenDefinitions.collect(violations, "style", error); } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "blockId" && - key !== "order" && - key !== "text" && - key !== "style" && - key !== "page" - ) { - violations.push({ path: key, reason: "unknown field" }); + let page: Page | null | undefined = undefined as unknown as + | Page + | null + | undefined; + if (raw.page !== undefined) { + if (raw.page === null) { + page = null; + } else { + try { + page = pageTransferTypeConverter.fromTransferType(raw.page); + } catch (error) { + __nexgenDefinitions.collect(violations, "page", error); + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Block = { blockId, order }; - if (text !== undefined) { - out.text = text; - } - if (style !== undefined) { - out.style = style; - } - if (page !== undefined) { - out.page = page; - } - return out; - } + for (const key of Object.keys(raw)) { + if ( + key !== "blockId" && + key !== "order" && + key !== "text" && + key !== "style" && + key !== "page" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } - public toIntermediate(value: Block): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.blockId = value.blockId; - if (value.order < 0) { - violations.push({ path: "order", reason: `must be >= 0, got ${value.order}` }); - } - out.order = value.order; - if (value.text !== undefined) { - out.text = value.text; - } - if (value.style !== undefined) { - out.style = new BlockStyleMapper().toIntermediate(value.style); - } - if (value.page !== undefined) { - out.page = - value.page === null ? null : new PageMapper().toIntermediate(value.page); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Block = { blockId, order }; + if (text !== undefined) { + out.text = text; + } + if (style !== undefined) { + out.style = style; + } + if (page !== undefined) { + out.page = page; + } + return out; } - return out; - } -} -export class BlockStyleMapper { - public fromIntermediate(raw: unknown): BlockStyle { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + public toTransferType(value: Block): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.blockId = value.blockId; + if (value.order < 0) { + violations.push({ path: "order", reason: `must be >= 0, got ${value.order}` }); + } + out.order = value.order; + if (value.text !== undefined) { + out.text = value.text; + } + if (value.style !== undefined) { + out.style = blockStyleTransferTypeConverter.toTransferType(value.style); + } + if (value.page !== undefined) { + out.page = + value.page === null + ? null + : pageTransferTypeConverter.toTransferType(value.page); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; + } + })(); + +export const blockStyleTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): BlockStyle { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let bold: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.bold === null) { - violations.push({ path: "bold", reason: "explicit null not allowed" }); - } else if (raw.bold !== undefined) { - if (typeof raw.bold !== "boolean") { - violations.push({ path: "bold", reason: "expected boolean" }); - } else { - bold = raw.bold; + let bold: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.bold === null) { + violations.push({ path: "bold", reason: "explicit null not allowed" }); + } else if (raw.bold !== undefined) { + if (typeof raw.bold !== "boolean") { + violations.push({ path: "bold", reason: "expected boolean" }); + } else { + bold = raw.bold; + } } - } - let indent: number | undefined = undefined as unknown as number | undefined; - if (raw.indent === null) { - violations.push({ path: "indent", reason: "explicit null not allowed" }); - } else if (raw.indent !== undefined) { - if (typeof raw.indent !== "number" || !Number.isSafeInteger(raw.indent)) { - violations.push({ path: "indent", reason: "expected integer" }); - } else { - indent = raw.indent; - if (raw.indent < 0) { - violations.push({ - path: "indent", - reason: `must be >= 0, got ${raw.indent}`, - }); + let indent: number | undefined = undefined as unknown as number | undefined; + if (raw.indent === null) { + violations.push({ path: "indent", reason: "explicit null not allowed" }); + } else if (raw.indent !== undefined) { + if (typeof raw.indent !== "number" || !Number.isSafeInteger(raw.indent)) { + violations.push({ path: "indent", reason: "expected integer" }); + } else { + indent = raw.indent; + if (raw.indent < 0) { + violations.push({ + path: "indent", + reason: `must be >= 0, got ${raw.indent}`, + }); + } } } - } - for (const key of Object.keys(raw)) { - if (key !== "bold" && key !== "indent") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "bold" && key !== "indent") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: BlockStyle = {}; - if (bold !== undefined) { - out.bold = bold; - } - if (indent !== undefined) { - out.indent = indent; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: BlockStyle = {}; + if (bold !== undefined) { + out.bold = bold; + } + if (indent !== undefined) { + out.indent = indent; + } + return out; } - return out; - } - public toIntermediate(value: BlockStyle): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.bold !== undefined) { - out.bold = value.bold; - } - if (value.indent !== undefined) { - if (value.indent < 0) { - violations.push({ - path: "indent", - reason: `must be >= 0, got ${value.indent}`, - }); - } - out.indent = value.indent; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: BlockStyle): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.bold !== undefined) { + out.bold = value.bold; + } + if (value.indent !== undefined) { + if (value.indent < 0) { + violations.push({ + path: "indent", + reason: `must be >= 0, got ${value.indent}`, + }); + } + out.indent = value.indent; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/advanced/samples/typescript/json_schema/api/kb/kb/models.ts b/advanced/samples/typescript/json_schema/api/kb/kb/models.ts index 615fa0ad..b236b788 100644 --- a/advanced/samples/typescript/json_schema/api/kb/kb/models.ts +++ b/advanced/samples/typescript/json_schema/api/kb/kb/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../definitions"; export function requiredField( @@ -26,134 +27,137 @@ export interface PutBlockOutput { revision: number; } -export class GetCategoryTreeInputMapper { - public fromIntermediate(raw: unknown): GetCategoryTreeInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const getCategoryTreeInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetCategoryTreeInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let rootId: string = undefined as unknown as string; - if (raw.rootId === undefined || raw.rootId === null) { - violations.push({ path: "rootId", reason: "required" }); - } else { - if (typeof raw.rootId !== "string") { - violations.push({ path: "rootId", reason: "expected string" }); + let rootId: string = undefined as unknown as string; + if (raw.rootId === undefined || raw.rootId === null) { + violations.push({ path: "rootId", reason: "required" }); } else { - rootId = raw.rootId; + if (typeof raw.rootId !== "string") { + violations.push({ path: "rootId", reason: "expected string" }); + } else { + rootId = raw.rootId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "rootId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "rootId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetCategoryTreeInput = { rootId }; + return out; } - const out: GetCategoryTreeInput = { rootId }; - return out; - } - public toIntermediate(value: GetCategoryTreeInput): unknown { - const out: Record = {}; - out.rootId = value.rootId; - return out; - } -} - -export class GetPageInputMapper { - public fromIntermediate(raw: unknown): GetPageInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetCategoryTreeInput): unknown { + const out: Record = {}; + out.rootId = value.rootId; + return out; } + })(); + +export const getPageInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetPageInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let pageId: string = undefined as unknown as string; - if (raw.pageId === undefined || raw.pageId === null) { - violations.push({ path: "pageId", reason: "required" }); - } else { - if (typeof raw.pageId !== "string") { - violations.push({ path: "pageId", reason: "expected string" }); + let pageId: string = undefined as unknown as string; + if (raw.pageId === undefined || raw.pageId === null) { + violations.push({ path: "pageId", reason: "required" }); } else { - pageId = raw.pageId; + if (typeof raw.pageId !== "string") { + violations.push({ path: "pageId", reason: "expected string" }); + } else { + pageId = raw.pageId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "pageId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "pageId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetPageInput = { pageId }; + return out; } - const out: GetPageInput = { pageId }; - return out; - } - - public toIntermediate(value: GetPageInput): unknown { - const out: Record = {}; - out.pageId = value.pageId; - return out; - } -} -export class PutBlockOutputMapper { - public fromIntermediate(raw: unknown): PutBlockOutput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetPageInput): unknown { + const out: Record = {}; + out.pageId = value.pageId; + return out; } + })(); + +export const putBlockOutputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): PutBlockOutput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let blockId: string = undefined as unknown as string; - if (raw.blockId === undefined || raw.blockId === null) { - violations.push({ path: "blockId", reason: "required" }); - } else { - if (typeof raw.blockId !== "string") { - violations.push({ path: "blockId", reason: "expected string" }); + let blockId: string = undefined as unknown as string; + if (raw.blockId === undefined || raw.blockId === null) { + violations.push({ path: "blockId", reason: "required" }); } else { - blockId = raw.blockId; + if (typeof raw.blockId !== "string") { + violations.push({ path: "blockId", reason: "expected string" }); + } else { + blockId = raw.blockId; + } } - } - let revision: number = undefined as unknown as number; - if (raw.revision === undefined || raw.revision === null) { - violations.push({ path: "revision", reason: "required" }); - } else { - if (typeof raw.revision !== "number" || !Number.isSafeInteger(raw.revision)) { - violations.push({ path: "revision", reason: "expected integer" }); + let revision: number = undefined as unknown as number; + if (raw.revision === undefined || raw.revision === null) { + violations.push({ path: "revision", reason: "required" }); } else { - revision = raw.revision; + if (typeof raw.revision !== "number" || !Number.isSafeInteger(raw.revision)) { + violations.push({ path: "revision", reason: "expected integer" }); + } else { + revision = raw.revision; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "blockId" && key !== "revision") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "blockId" && key !== "revision") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: PutBlockOutput = { blockId, revision }; + return out; } - const out: PutBlockOutput = { blockId, revision }; - return out; - } - public toIntermediate(value: PutBlockOutput): unknown { - const out: Record = {}; - out.blockId = value.blockId; - out.revision = value.revision; - return out; - } -} + public toTransferType(value: PutBlockOutput): unknown { + const out: Record = {}; + out.blockId = value.blockId; + out.revision = value.revision; + return out; + } + })(); diff --git a/advanced/samples/typescript/json_schema/api/kb/kb/services.ts b/advanced/samples/typescript/json_schema/api/kb/kb/services.ts index 2b449255..bb9bf164 100644 --- a/advanced/samples/typescript/json_schema/api/kb/kb/services.ts +++ b/advanced/samples/typescript/json_schema/api/kb/kb/services.ts @@ -2,7 +2,15 @@ import * as nexus from "nexus-rpc"; import * as workflow from "@temporalio/workflow"; +import { + getCategoryTreeInputTransferTypeConverter, + getPageInputTransferTypeConverter, + putBlockOutputTransferTypeConverter, +} from "./models"; import type { GetCategoryTreeInput, GetPageInput, PutBlockOutput } from "./models"; +import { blockTransferTypeConverter } from "../content/block/models"; +import { pageTransferTypeConverter } from "../content/page/models"; +import { categoryTransferTypeConverter } from "../tree/category/models"; import type { Block } from "../content/block/models"; import type { Page } from "../content/page/models"; import type { Category } from "../tree/category/models"; @@ -16,16 +24,26 @@ export const knowledgeBaseService = nexus.service( /** * Fetch a page by id. */ - getPage: nexus.operation({ name: "GetPage" }), + getPage: nexus.operation({ + name: "GetPage", + inputType: { transferTypeConverter: getPageInputTransferTypeConverter }, + outputType: { transferTypeConverter: pageTransferTypeConverter }, + }), /** * Create or update a content block. */ - putBlock: nexus.operation({ name: "PutBlock" }), + putBlock: nexus.operation({ + name: "PutBlock", + inputType: { transferTypeConverter: blockTransferTypeConverter }, + outputType: { transferTypeConverter: putBlockOutputTransferTypeConverter }, + }), /** * Fetch the category tree rooted at a category. */ getCategoryTree: nexus.operation({ name: "GetCategoryTree", + inputType: { transferTypeConverter: getCategoryTreeInputTransferTypeConverter }, + outputType: { transferTypeConverter: categoryTransferTypeConverter }, }), }, ); diff --git a/advanced/samples/typescript/json_schema/api/showcase/services.ts b/advanced/samples/typescript/json_schema/api/showcase/services.ts index 14eee3fd..dcae41ab 100644 --- a/advanced/samples/typescript/json_schema/api/showcase/services.ts +++ b/advanced/samples/typescript/json_schema/api/showcase/services.ts @@ -2,6 +2,10 @@ import * as nexus from "nexus-rpc"; import * as workflow from "@temporalio/workflow"; +import { + getShowcaseInputTransferTypeConverter, + showcaseTransferTypeConverter, +} from "./models"; import type { GetShowcaseInput, Showcase } from "./models"; /** @@ -21,7 +25,11 @@ export const showcaseServiceTs = nexus.service("example.showcase.v1.ShowcaseServ * `GetShowcase` and the synthesized I/O type stays `GetShowcaseInput` (derived from * the operation key, not the override). */ - getShowcaseTs: nexus.operation({ name: "GetShowcase" }), + getShowcaseTs: nexus.operation({ + name: "GetShowcase", + inputType: { transferTypeConverter: getShowcaseInputTransferTypeConverter }, + outputType: { transferTypeConverter: showcaseTransferTypeConverter }, + }), }); /** diff --git a/advanced/samples/typescript/json_schema/api/temporal-date/models.ts b/advanced/samples/typescript/json_schema/api/temporal-date/models.ts index 46a30710..34aa102b 100644 --- a/advanced/samples/typescript/json_schema/api/temporal-date/models.ts +++ b/advanced/samples/typescript/json_schema/api/temporal-date/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; export function requiredField( @@ -60,278 +61,279 @@ export interface Temporal { archivedOn?: string | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: Date = undefined as unknown as Date; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let birthday: string = undefined as unknown as string; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); + let createdAt: Date = undefined as unknown as Date; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; - } - } - } - - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let timeout: string = undefined as unknown as string; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let birthday: string = undefined as unknown as string; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let updatedAt: Date | undefined = undefined as unknown as Date | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: string | undefined = undefined as unknown as string | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: string = undefined as unknown as string; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: Date | undefined = undefined as unknown as Date | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: string | undefined = undefined as unknown as string | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: string | undefined = undefined as unknown as string | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: Date | null | undefined = undefined as unknown as - | Date - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: string | undefined = undefined as unknown as string | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: Date | null | undefined = undefined as unknown as + | Date + | null + | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); - out.birthday = value.birthday; - out.alarm = value.alarm; - out.timeout = value.timeout; - if (value.updatedAt !== undefined) { - out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); - } - if (value.expiresOn !== undefined) { - out.expiresOn = value.expiresOn; - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = value.retryDelay; - } - if (value.deletedAt !== undefined) { - out.deletedAt = - value.deletedAt === null - ? null - : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); - } - if (value.archivedOn !== undefined) { - out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); + out.birthday = value.birthday; + out.alarm = value.alarm; + out.timeout = value.timeout; + if (value.updatedAt !== undefined) { + out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); + } + if (value.expiresOn !== undefined) { + out.expiresOn = value.expiresOn; + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = value.retryDelay; + } + if (value.deletedAt !== undefined) { + out.deletedAt = + value.deletedAt === null + ? null + : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); + } + if (value.archivedOn !== undefined) { + out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/advanced/samples/typescript/json_schema/api/temporal-temporal/models.ts b/advanced/samples/typescript/json_schema/api/temporal-temporal/models.ts index 6b2c0bae..42a9b6b5 100644 --- a/advanced/samples/typescript/json_schema/api/temporal-temporal/models.ts +++ b/advanced/samples/typescript/json_schema/api/temporal-temporal/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; export function requiredField( @@ -60,288 +61,289 @@ export interface Temporal { archivedOn?: Temporal.PlainDate | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: Temporal.ZonedDateTime = - undefined as unknown as Temporal.ZonedDateTime; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } - } - } - - let birthday: Temporal.PlainDate = undefined as unknown as Temporal.PlainDate; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); + let createdAt: Temporal.ZonedDateTime = + undefined as unknown as Temporal.ZonedDateTime; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let timeout: Temporal.Duration = undefined as unknown as Temporal.Duration; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let birthday: Temporal.PlainDate = undefined as unknown as Temporal.PlainDate; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let updatedAt: Temporal.ZonedDateTime | undefined = undefined as unknown as - | Temporal.ZonedDateTime - | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: Temporal.PlainDate | undefined = undefined as unknown as - | Temporal.PlainDate - | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: Temporal.Duration = undefined as unknown as Temporal.Duration; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: Temporal.ZonedDateTime | undefined = undefined as unknown as + | Temporal.ZonedDateTime + | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: Temporal.Duration | undefined = undefined as unknown as - | Temporal.Duration - | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: Temporal.PlainDate | undefined = undefined as unknown as + | Temporal.PlainDate + | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: Temporal.ZonedDateTime | null | undefined = undefined as unknown as - | Temporal.ZonedDateTime - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: Temporal.PlainDate | null | undefined = undefined as unknown as - | Temporal.PlainDate - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: Temporal.Duration | undefined = undefined as unknown as + | Temporal.Duration + | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: Temporal.ZonedDateTime | null | undefined = + undefined as unknown as Temporal.ZonedDateTime | null | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: Temporal.PlainDate | null | undefined = undefined as unknown as + | Temporal.PlainDate + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); - out.birthday = __nexgenDefinitions.serializeTemporalDate(value.birthday); - out.alarm = value.alarm; - out.timeout = __nexgenDefinitions.serializeTemporalDuration(value.timeout); - if (value.updatedAt !== undefined) { - out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); - } - if (value.expiresOn !== undefined) { - out.expiresOn = __nexgenDefinitions.serializeTemporalDate(value.expiresOn); - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = __nexgenDefinitions.serializeTemporalDuration(value.retryDelay); - } - if (value.deletedAt !== undefined) { - out.deletedAt = - value.deletedAt === null - ? null - : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); - } - if (value.archivedOn !== undefined) { - out.archivedOn = - value.archivedOn === null - ? null - : __nexgenDefinitions.serializeTemporalDate(value.archivedOn); + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); + out.birthday = __nexgenDefinitions.serializeTemporalDate(value.birthday); + out.alarm = value.alarm; + out.timeout = __nexgenDefinitions.serializeTemporalDuration(value.timeout); + if (value.updatedAt !== undefined) { + out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); + } + if (value.expiresOn !== undefined) { + out.expiresOn = __nexgenDefinitions.serializeTemporalDate(value.expiresOn); + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = __nexgenDefinitions.serializeTemporalDuration( + value.retryDelay, + ); + } + if (value.deletedAt !== undefined) { + out.deletedAt = + value.deletedAt === null + ? null + : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); + } + if (value.archivedOn !== undefined) { + out.archivedOn = + value.archivedOn === null + ? null + : __nexgenDefinitions.serializeTemporalDate(value.archivedOn); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/advanced/samples/typescript/json_schema/api/temporal/models.ts b/advanced/samples/typescript/json_schema/api/temporal/models.ts index ef39fe4b..a807b948 100644 --- a/advanced/samples/typescript/json_schema/api/temporal/models.ts +++ b/advanced/samples/typescript/json_schema/api/temporal/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; export function requiredField( @@ -60,275 +61,276 @@ export interface Temporal { archivedOn?: string | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: string = undefined as unknown as string; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let birthday: string = undefined as unknown as string; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); + let createdAt: string = undefined as unknown as string; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; - } - } - } - - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let timeout: string = undefined as unknown as string; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let birthday: string = undefined as unknown as string; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let updatedAt: string | undefined = undefined as unknown as string | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: string | undefined = undefined as unknown as string | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: string = undefined as unknown as string; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: string | undefined = undefined as unknown as string | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: string | undefined = undefined as unknown as string | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: string | undefined = undefined as unknown as string | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: string | undefined = undefined as unknown as string | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = value.createdAt; - out.birthday = value.birthday; - out.alarm = value.alarm; - out.timeout = value.timeout; - if (value.updatedAt !== undefined) { - out.updatedAt = value.updatedAt; - } - if (value.expiresOn !== undefined) { - out.expiresOn = value.expiresOn; - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = value.retryDelay; - } - if (value.deletedAt !== undefined) { - out.deletedAt = value.deletedAt === null ? null : value.deletedAt; - } - if (value.archivedOn !== undefined) { - out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = value.createdAt; + out.birthday = value.birthday; + out.alarm = value.alarm; + out.timeout = value.timeout; + if (value.updatedAt !== undefined) { + out.updatedAt = value.updatedAt; + } + if (value.expiresOn !== undefined) { + out.expiresOn = value.expiresOn; + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = value.retryDelay; + } + if (value.deletedAt !== undefined) { + out.deletedAt = value.deletedAt === null ? null : value.deletedAt; + } + if (value.archivedOn !== undefined) { + out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/advanced/samples/typescript/shims/nexus-rpc-type-info.d.ts b/advanced/samples/typescript/shims/nexus-rpc-type-info.d.ts new file mode 100644 index 00000000..7cfdb311 --- /dev/null +++ b/advanced/samples/typescript/shims/nexus-rpc-type-info.d.ts @@ -0,0 +1,37 @@ +// TEMPORARY: the published nexus-rpc@0.0.2 predates +// https://github.com/nexus-rpc/sdk-typescript/pull/40, which added `TypeInfo` / +// `TransferTypeConverter` and the operation `inputType` / `outputType` metadata the +// generated code emits. `operation()` already spreads its options through +// untouched, so this is a typings-only gap: delete this file and bump the +// `nexus-rpc` pin once the release carrying #40 is published. +// +// The top-level `export {}` makes this file a module, so each `declare module` +// below *augments* the real package instead of shadowing it. `OperationOptions` +// and `OperationDefinition` are augmented at their declaring module paths rather +// than at the `nexus-rpc` barrel that re-exports them. +export {}; + +declare module "nexus-rpc" { + export interface TransferTypeConverter { + fromTransferType(value: D): T; + toTransferType(value: T): D; + } + + export interface TypeInfo { + transferTypeConverter?: TransferTypeConverter; + } +} + +declare module "nexus-rpc/lib/service/helpers" { + interface OperationOptions<_I, _O> { + inputType?: import("nexus-rpc").TypeInfo<_I, unknown>; + outputType?: import("nexus-rpc").TypeInfo<_O, unknown>; + } +} + +declare module "nexus-rpc/lib/service/service-definition" { + interface OperationDefinition { + inputType?: import("nexus-rpc").TypeInfo; + outputType?: import("nexus-rpc").TypeInfo; + } +} diff --git a/advanced/samples/typescript/tsconfig.json b/advanced/samples/typescript/tsconfig.json index c32fc4b6..85e13e05 100644 --- a/advanced/samples/typescript/tsconfig.json +++ b/advanced/samples/typescript/tsconfig.json @@ -10,5 +10,5 @@ "allowImportingTsExtensions": true, "esModuleInterop": true }, - "include": ["wit/**/*.ts", "json_schema/**/*.ts", "tests/**/*.ts"] + "include": ["shims/**/*.d.ts", "wit/**/*.ts", "json_schema/**/*.ts", "tests/**/*.ts"] } diff --git a/samples/typescript/README.md b/samples/typescript/README.md index 542ed43c..6dc4d0fd 100644 --- a/samples/typescript/README.md +++ b/samples/typescript/README.md @@ -9,8 +9,8 @@ service scaffolding). (`chat`, `kb`, `showcase`, `temporal`, plus the `temporal-date` / `temporal-temporal` date-time representation variants) - Vitest round-trip tests live in `samples/typescript/tests/`, driving the - generated mappers through the Temporal data converter against the canonical - wire fixtures in [`samples/wire/json_schema`](../wire/json_schema) + generated transfer type converters through the Temporal data converter against + the canonical wire fixtures in [`samples/wire/json_schema`](../wire/json_schema) - `build_outputs.mjs` is a thin wrapper around `cargo build-json-examples --lang typescript` diff --git a/samples/typescript/chat/models.ts b/samples/typescript/chat/models.ts index 652934b4..b0865ca7 100644 --- a/samples/typescript/chat/models.ts +++ b/samples/typescript/chat/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; const KIND_CONST = "text"; @@ -62,412 +63,421 @@ export interface SendMessageOutput { messageId: string; } -export class GetRoomInputMapper { - public fromIntermediate(raw: unknown): GetRoomInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const getRoomInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetRoomInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); } else { - roomId = raw.roomId; + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "roomId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "roomId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetRoomInput = { roomId }; + return out; } - const out: GetRoomInput = { roomId }; - return out; - } - - public toIntermediate(value: GetRoomInput): unknown { - const out: Record = {}; - out.roomId = value.roomId; - return out; - } -} -export class LabelsMapper { - public fromIntermediate(raw: unknown): Labels { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetRoomInput): unknown { + const out: Record = {}; + out.roomId = value.roomId; + return out; } + })(); - const keys = Object.keys(raw); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; +export const labelsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Labels { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - if (entry !== undefined) { - additionalProperties[key] = entry; + + const keys = Object.keys(raw); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); } + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Labels): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} -export class MessageMapper { - public fromIntermediate(raw: unknown): Message { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + public toTransferType(value: Labels): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; + } + })(); + +export const messageTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Message { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "text" = undefined as unknown as "text"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "text"` }); + let kind: "text" = undefined as unknown as "text"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "text"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "text"` }); + } else { + kind = raw.kind as "text"; + } } - } - let body: string = undefined as unknown as string; - if (raw.body === undefined || raw.body === null) { - violations.push({ path: "body", reason: "required" }); - } else { - if (typeof raw.body !== "string") { - violations.push({ path: "body", reason: "expected string" }); + let body: string = undefined as unknown as string; + if (raw.body === undefined || raw.body === null) { + violations.push({ path: "body", reason: "required" }); } else { - body = raw.body; + if (typeof raw.body !== "string") { + violations.push({ path: "body", reason: "expected string" }); + } else { + body = raw.body; + } } - } - let replyToId: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.replyToId !== undefined) { - if (raw.replyToId === null) { - replyToId = null; - } else { - if (typeof raw.replyToId !== "string") { - violations.push({ path: "replyToId", reason: "expected string" }); + let replyToId: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.replyToId !== undefined) { + if (raw.replyToId === null) { + replyToId = null; } else { - replyToId = raw.replyToId; + if (typeof raw.replyToId !== "string") { + violations.push({ path: "replyToId", reason: "expected string" }); + } else { + replyToId = raw.replyToId; + } } } - } - let priority: number | undefined = undefined as unknown as number | undefined; - if (raw.priority === null) { - violations.push({ path: "priority", reason: "explicit null not allowed" }); - } else if (raw.priority !== undefined) { - if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { - violations.push({ path: "priority", reason: "expected integer" }); - } else { - priority = raw.priority; + let priority: number | undefined = undefined as unknown as number | undefined; + if (raw.priority === null) { + violations.push({ path: "priority", reason: "explicit null not allowed" }); + } else if (raw.priority !== undefined) { + if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { + violations.push({ path: "priority", reason: "expected integer" }); + } else { + priority = raw.priority; + } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "kind" && - key !== "body" && - key !== "replyToId" && - key !== "priority" - ) { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if ( + key !== "kind" && + key !== "body" && + key !== "replyToId" && + key !== "priority" + ) { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Message = { kind, body }; - if (replyToId !== undefined) { - out.replyToId = replyToId; - } - if (priority !== undefined) { - out.priority = priority; - } - return out; - } - - public toIntermediate(value: Message): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "text") { - violations.push({ path: "kind", reason: `must equal "text"` }); - } - out.kind = value.kind; - out.body = value.body; - if (value.replyToId !== undefined) { - out.replyToId = value.replyToId; - } - if (value.priority !== undefined) { - out.priority = value.priority; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Message = { kind, body }; + if (replyToId !== undefined) { + out.replyToId = replyToId; + } + if (priority !== undefined) { + out.priority = priority; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Message): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "text") { + violations.push({ path: "kind", reason: `must equal "text"` }); + } + out.kind = value.kind; + out.body = value.body; + if (value.replyToId !== undefined) { + out.replyToId = value.replyToId; + } + if (value.priority !== undefined) { + out.priority = value.priority; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const ROOM_DECLARED = new Set(["roomId", "displayName", "topic", "members", "labels"]); -export class RoomMapper { - public fromIntermediate(raw: unknown): Room { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); - } else { - roomId = raw.roomId; +export const roomTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Room { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let displayName: string = undefined as unknown as string; - if (raw.displayName === undefined || raw.displayName === null) { - violations.push({ path: "displayName", reason: "required" }); - } else { - if (typeof raw.displayName !== "string") { - violations.push({ path: "displayName", reason: "expected string" }); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); } else { - displayName = raw.displayName; + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - let topic: string | null = undefined as unknown as string | null; - if (raw.topic === undefined) { - violations.push({ path: "topic", reason: "required" }); - } else { - if (raw.topic === null) { - topic = null; + let displayName: string = undefined as unknown as string; + if (raw.displayName === undefined || raw.displayName === null) { + violations.push({ path: "displayName", reason: "required" }); } else { - if (typeof raw.topic !== "string") { - violations.push({ path: "topic", reason: "expected string" }); + if (typeof raw.displayName !== "string") { + violations.push({ path: "displayName", reason: "expected string" }); } else { - topic = raw.topic; + displayName = raw.displayName; } } - } - let members: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.members === null) { - violations.push({ path: "members", reason: "explicit null not allowed" }); - } else if (raw.members !== undefined) { - if (!Array.isArray(raw.members)) { - violations.push({ path: "members", reason: "expected array" }); + let topic: string | null = undefined as unknown as string | null; + if (raw.topic === undefined) { + violations.push({ path: "topic", reason: "required" }); } else { - members = []; - raw.members.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `members[${index}]`, reason: "expected element" }); + if (raw.topic === null) { + topic = null; + } else { + if (typeof raw.topic !== "string") { + violations.push({ path: "topic", reason: "expected string" }); } else { - item = element; - } - if (item !== undefined) { - members!.push(item); + topic = raw.topic; } - }); + } } - } - let labels: Labels | undefined = undefined as unknown as Labels | undefined; - if (raw.labels === null) { - violations.push({ path: "labels", reason: "explicit null not allowed" }); - } else if (raw.labels !== undefined) { - try { - labels = new LabelsMapper().fromIntermediate(raw.labels); - } catch (error) { - __nexgenDefinitions.collect(violations, "labels", error); + let members: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.members === null) { + violations.push({ path: "members", reason: "explicit null not allowed" }); + } else if (raw.members !== undefined) { + if (!Array.isArray(raw.members)) { + violations.push({ path: "members", reason: "expected array" }); + } else { + members = []; + raw.members.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ + path: `members[${index}]`, + reason: "expected element", + }); + } else { + item = element; + } + if (item !== undefined) { + members!.push(item); + } + }); + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!ROOM_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + let labels: Labels | undefined = undefined as unknown as Labels | undefined; + if (raw.labels === null) { + violations.push({ path: "labels", reason: "explicit null not allowed" }); + } else if (raw.labels !== undefined) { + try { + labels = labelsTransferTypeConverter.fromTransferType(raw.labels); + } catch (error) { + __nexgenDefinitions.collect(violations, "labels", error); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Room = { roomId, displayName, topic, additionalProperties }; - if (members !== undefined) { - out.members = members; - } - if (labels !== undefined) { - out.labels = labels; - } - return out; - } - - public toIntermediate(value: Room): unknown { - const out: Record = {}; - out.roomId = value.roomId; - out.displayName = value.displayName; - out.topic = value.topic; - if (value.members !== undefined) { - out.members = value.members; - } - if (value.labels !== undefined) { - out.labels = new LabelsMapper().toIntermediate(value.labels); - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - return out; - } -} + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!ROOM_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } + } -export class SendMessageInputMapper { - public fromIntermediate(raw: unknown): SendMessageInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Room = { roomId, displayName, topic, additionalProperties }; + if (members !== undefined) { + out.members = members; + } + if (labels !== undefined) { + out.labels = labels; + } + return out; } - let roomId: string = undefined as unknown as string; - if (raw.roomId === undefined || raw.roomId === null) { - violations.push({ path: "roomId", reason: "required" }); - } else { - if (typeof raw.roomId !== "string") { - violations.push({ path: "roomId", reason: "expected string" }); - } else { - roomId = raw.roomId; + public toTransferType(value: Room): unknown { + const out: Record = {}; + out.roomId = value.roomId; + out.displayName = value.displayName; + out.topic = value.topic; + if (value.members !== undefined) { + out.members = value.members; + } + if (value.labels !== undefined) { + out.labels = labelsTransferTypeConverter.toTransferType(value.labels); + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; + } + })(); + +export const sendMessageInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): SendMessageInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let message: Message = undefined as unknown as Message; - if (raw.message === undefined || raw.message === null) { - violations.push({ path: "message", reason: "required" }); - } else { - try { - message = new MessageMapper().fromIntermediate(raw.message); - } catch (error) { - __nexgenDefinitions.collect(violations, "message", error); + let roomId: string = undefined as unknown as string; + if (raw.roomId === undefined || raw.roomId === null) { + violations.push({ path: "roomId", reason: "required" }); + } else { + if (typeof raw.roomId !== "string") { + violations.push({ path: "roomId", reason: "expected string" }); + } else { + roomId = raw.roomId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "roomId" && key !== "message") { - violations.push({ path: key, reason: "unknown field" }); + let message: Message = undefined as unknown as Message; + if (raw.message === undefined || raw.message === null) { + violations.push({ path: "message", reason: "required" }); + } else { + try { + message = messageTransferTypeConverter.fromTransferType(raw.message); + } catch (error) { + __nexgenDefinitions.collect(violations, "message", error); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: SendMessageInput = { roomId, message }; - return out; - } - - public toIntermediate(value: SendMessageInput): unknown { - const out: Record = {}; - out.roomId = value.roomId; - out.message = new MessageMapper().toIntermediate(value.message); - return out; - } -} + for (const key of Object.keys(raw)) { + if (key !== "roomId" && key !== "message") { + violations.push({ path: key, reason: "unknown field" }); + } + } -export class SendMessageOutputMapper { - public fromIntermediate(raw: unknown): SendMessageOutput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: SendMessageInput = { roomId, message }; + return out; + } + + public toTransferType(value: SendMessageInput): unknown { + const out: Record = {}; + out.roomId = value.roomId; + out.message = messageTransferTypeConverter.toTransferType(value.message); + return out; + } + })(); + +export const sendMessageOutputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): SendMessageOutput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let messageId: string = undefined as unknown as string; - if (raw.messageId === undefined || raw.messageId === null) { - violations.push({ path: "messageId", reason: "required" }); - } else { - if (typeof raw.messageId !== "string") { - violations.push({ path: "messageId", reason: "expected string" }); + let messageId: string = undefined as unknown as string; + if (raw.messageId === undefined || raw.messageId === null) { + violations.push({ path: "messageId", reason: "required" }); } else { - messageId = raw.messageId; + if (typeof raw.messageId !== "string") { + violations.push({ path: "messageId", reason: "expected string" }); + } else { + messageId = raw.messageId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "messageId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "messageId") { + violations.push({ path: key, reason: "unknown field" }); + } } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: SendMessageOutput = { messageId }; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: SendMessageOutput): unknown { + const out: Record = {}; + out.messageId = value.messageId; + return out; } - const out: SendMessageOutput = { messageId }; - return out; - } - - public toIntermediate(value: SendMessageOutput): unknown { - const out: Record = {}; - out.messageId = value.messageId; - return out; - } -} + })(); diff --git a/samples/typescript/chat/services.ts b/samples/typescript/chat/services.ts index 130da53e..8c8c11dd 100644 --- a/samples/typescript/chat/services.ts +++ b/samples/typescript/chat/services.ts @@ -1,6 +1,12 @@ // Generated by nexgen. DO NOT EDIT! import * as nexus from "nexus-rpc"; +import { + getRoomInputTransferTypeConverter, + roomTransferTypeConverter, + sendMessageInputTransferTypeConverter, + sendMessageOutputTransferTypeConverter, +} from "./models"; import type { GetRoomInput, Room, SendMessageInput, SendMessageOutput } from "./models"; /** @@ -12,11 +18,17 @@ export const chatService = nexus.service("example.chat.v1.ChatService", { */ sendMessage: nexus.operation({ name: "SendMessage", + inputType: { transferTypeConverter: sendMessageInputTransferTypeConverter }, + outputType: { transferTypeConverter: sendMessageOutputTransferTypeConverter }, }), /** * Look up a room by id. */ - getRoom: nexus.operation({ name: "GetRoom" }), + getRoom: nexus.operation({ + name: "GetRoom", + inputType: { transferTypeConverter: getRoomInputTransferTypeConverter }, + outputType: { transferTypeConverter: roomTransferTypeConverter }, + }), /** * Liveness probe. */ diff --git a/samples/typescript/kb/content/block/models.ts b/samples/typescript/kb/content/block/models.ts index a0768b9f..89258a29 100644 --- a/samples/typescript/kb/content/block/models.ts +++ b/samples/typescript/kb/content/block/models.ts @@ -1,7 +1,8 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; -import { PageMapper } from "../page/models"; +import { pageTransferTypeConverter } from "../page/models"; import type { Page } from "../page/models"; /** @@ -29,202 +30,214 @@ export interface BlockStyle { indent?: number; } -export class BlockMapper { - public fromIntermediate(raw: unknown): Block { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let blockId: string = undefined as unknown as string; - if (raw.blockId === undefined || raw.blockId === null) { - violations.push({ path: "blockId", reason: "required" }); - } else { - if (typeof raw.blockId !== "string") { - violations.push({ path: "blockId", reason: "expected string" }); - } else { - blockId = raw.blockId; +export const blockTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Block { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let order: number = undefined as unknown as number; - if (raw.order === undefined || raw.order === null) { - violations.push({ path: "order", reason: "required" }); - } else { - if (typeof raw.order !== "number" || !Number.isSafeInteger(raw.order)) { - violations.push({ path: "order", reason: "expected integer" }); + let blockId: string = undefined as unknown as string; + if (raw.blockId === undefined || raw.blockId === null) { + violations.push({ path: "blockId", reason: "required" }); } else { - order = raw.order; - if (raw.order < 0) { - violations.push({ path: "order", reason: `must be >= 0, got ${raw.order}` }); + if (typeof raw.blockId !== "string") { + violations.push({ path: "blockId", reason: "expected string" }); + } else { + blockId = raw.blockId; } } - } - let text: string | undefined = undefined as unknown as string | undefined; - if (raw.text === null) { - violations.push({ path: "text", reason: "explicit null not allowed" }); - } else if (raw.text !== undefined) { - if (typeof raw.text !== "string") { - violations.push({ path: "text", reason: "expected string" }); + let order: number = undefined as unknown as number; + if (raw.order === undefined || raw.order === null) { + violations.push({ path: "order", reason: "required" }); } else { - text = raw.text; + if (typeof raw.order !== "number" || !Number.isSafeInteger(raw.order)) { + violations.push({ path: "order", reason: "expected integer" }); + } else { + order = raw.order; + if (raw.order < 0) { + violations.push({ + path: "order", + reason: `must be >= 0, got ${raw.order}`, + }); + } + } } - } - let style: BlockStyle | undefined = undefined as unknown as BlockStyle | undefined; - if (raw.style === null) { - violations.push({ path: "style", reason: "explicit null not allowed" }); - } else if (raw.style !== undefined) { - try { - style = new BlockStyleMapper().fromIntermediate(raw.style); - } catch (error) { - __nexgenDefinitions.collect(violations, "style", error); + let text: string | undefined = undefined as unknown as string | undefined; + if (raw.text === null) { + violations.push({ path: "text", reason: "explicit null not allowed" }); + } else if (raw.text !== undefined) { + if (typeof raw.text !== "string") { + violations.push({ path: "text", reason: "expected string" }); + } else { + text = raw.text; + } } - } - let page: Page | null | undefined = undefined as unknown as Page | null | undefined; - if (raw.page !== undefined) { - if (raw.page === null) { - page = null; - } else { + let style: BlockStyle | undefined = undefined as unknown as + | BlockStyle + | undefined; + if (raw.style === null) { + violations.push({ path: "style", reason: "explicit null not allowed" }); + } else if (raw.style !== undefined) { try { - page = new PageMapper().fromIntermediate(raw.page); + style = blockStyleTransferTypeConverter.fromTransferType(raw.style); } catch (error) { - __nexgenDefinitions.collect(violations, "page", error); + __nexgenDefinitions.collect(violations, "style", error); } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "blockId" && - key !== "order" && - key !== "text" && - key !== "style" && - key !== "page" - ) { - violations.push({ path: key, reason: "unknown field" }); + let page: Page | null | undefined = undefined as unknown as + | Page + | null + | undefined; + if (raw.page !== undefined) { + if (raw.page === null) { + page = null; + } else { + try { + page = pageTransferTypeConverter.fromTransferType(raw.page); + } catch (error) { + __nexgenDefinitions.collect(violations, "page", error); + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Block = { blockId, order }; - if (text !== undefined) { - out.text = text; - } - if (style !== undefined) { - out.style = style; - } - if (page !== undefined) { - out.page = page; - } - return out; - } - - public toIntermediate(value: Block): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.blockId = value.blockId; - if (value.order < 0) { - violations.push({ path: "order", reason: `must be >= 0, got ${value.order}` }); - } - out.order = value.order; - if (value.text !== undefined) { - out.text = value.text; - } - if (value.style !== undefined) { - out.style = new BlockStyleMapper().toIntermediate(value.style); - } - if (value.page !== undefined) { - out.page = - value.page === null ? null : new PageMapper().toIntermediate(value.page); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + for (const key of Object.keys(raw)) { + if ( + key !== "blockId" && + key !== "order" && + key !== "text" && + key !== "style" && + key !== "page" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } -export class BlockStyleMapper { - public fromIntermediate(raw: unknown): BlockStyle { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Block = { blockId, order }; + if (text !== undefined) { + out.text = text; + } + if (style !== undefined) { + out.style = style; + } + if (page !== undefined) { + out.page = page; + } + return out; } - let bold: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.bold === null) { - violations.push({ path: "bold", reason: "explicit null not allowed" }); - } else if (raw.bold !== undefined) { - if (typeof raw.bold !== "boolean") { - violations.push({ path: "bold", reason: "expected boolean" }); - } else { - bold = raw.bold; + public toTransferType(value: Block): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.blockId = value.blockId; + if (value.order < 0) { + violations.push({ path: "order", reason: `must be >= 0, got ${value.order}` }); } + out.order = value.order; + if (value.text !== undefined) { + out.text = value.text; + } + if (value.style !== undefined) { + out.style = blockStyleTransferTypeConverter.toTransferType(value.style); + } + if (value.page !== undefined) { + out.page = + value.page === null + ? null + : pageTransferTypeConverter.toTransferType(value.page); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; + } + })(); + +export const blockStyleTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): BlockStyle { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let bold: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.bold === null) { + violations.push({ path: "bold", reason: "explicit null not allowed" }); + } else if (raw.bold !== undefined) { + if (typeof raw.bold !== "boolean") { + violations.push({ path: "bold", reason: "expected boolean" }); + } else { + bold = raw.bold; + } + } + + let indent: number | undefined = undefined as unknown as number | undefined; + if (raw.indent === null) { + violations.push({ path: "indent", reason: "explicit null not allowed" }); + } else if (raw.indent !== undefined) { + if (typeof raw.indent !== "number" || !Number.isSafeInteger(raw.indent)) { + violations.push({ path: "indent", reason: "expected integer" }); + } else { + indent = raw.indent; + if (raw.indent < 0) { + violations.push({ + path: "indent", + reason: `must be >= 0, got ${raw.indent}`, + }); + } + } + } + + for (const key of Object.keys(raw)) { + if (key !== "bold" && key !== "indent") { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: BlockStyle = {}; + if (bold !== undefined) { + out.bold = bold; + } + if (indent !== undefined) { + out.indent = indent; + } + return out; } - let indent: number | undefined = undefined as unknown as number | undefined; - if (raw.indent === null) { - violations.push({ path: "indent", reason: "explicit null not allowed" }); - } else if (raw.indent !== undefined) { - if (typeof raw.indent !== "number" || !Number.isSafeInteger(raw.indent)) { - violations.push({ path: "indent", reason: "expected integer" }); - } else { - indent = raw.indent; - if (raw.indent < 0) { + public toTransferType(value: BlockStyle): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.bold !== undefined) { + out.bold = value.bold; + } + if (value.indent !== undefined) { + if (value.indent < 0) { violations.push({ path: "indent", - reason: `must be >= 0, got ${raw.indent}`, + reason: `must be >= 0, got ${value.indent}`, }); } + out.indent = value.indent; } - } - - for (const key of Object.keys(raw)) { - if (key !== "bold" && key !== "indent") { - violations.push({ path: key, reason: "unknown field" }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } - - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: BlockStyle = {}; - if (bold !== undefined) { - out.bold = bold; - } - if (indent !== undefined) { - out.indent = indent; - } - return out; - } - - public toIntermediate(value: BlockStyle): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.bold !== undefined) { - out.bold = value.bold; - } - if (value.indent !== undefined) { - if (value.indent < 0) { - violations.push({ - path: "indent", - reason: `must be >= 0, got ${value.indent}`, - }); - } - out.indent = value.indent; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + })(); diff --git a/samples/typescript/kb/kb/models.ts b/samples/typescript/kb/kb/models.ts index e5a0cedd..47c1e190 100644 --- a/samples/typescript/kb/kb/models.ts +++ b/samples/typescript/kb/kb/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../definitions"; export interface GetCategoryTreeInput { @@ -15,134 +16,137 @@ export interface PutBlockOutput { revision: number; } -export class GetCategoryTreeInputMapper { - public fromIntermediate(raw: unknown): GetCategoryTreeInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const getCategoryTreeInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetCategoryTreeInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let rootId: string = undefined as unknown as string; - if (raw.rootId === undefined || raw.rootId === null) { - violations.push({ path: "rootId", reason: "required" }); - } else { - if (typeof raw.rootId !== "string") { - violations.push({ path: "rootId", reason: "expected string" }); + let rootId: string = undefined as unknown as string; + if (raw.rootId === undefined || raw.rootId === null) { + violations.push({ path: "rootId", reason: "required" }); } else { - rootId = raw.rootId; + if (typeof raw.rootId !== "string") { + violations.push({ path: "rootId", reason: "expected string" }); + } else { + rootId = raw.rootId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "rootId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "rootId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetCategoryTreeInput = { rootId }; + return out; } - const out: GetCategoryTreeInput = { rootId }; - return out; - } - - public toIntermediate(value: GetCategoryTreeInput): unknown { - const out: Record = {}; - out.rootId = value.rootId; - return out; - } -} -export class GetPageInputMapper { - public fromIntermediate(raw: unknown): GetPageInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetCategoryTreeInput): unknown { + const out: Record = {}; + out.rootId = value.rootId; + return out; } + })(); + +export const getPageInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetPageInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let pageId: string = undefined as unknown as string; - if (raw.pageId === undefined || raw.pageId === null) { - violations.push({ path: "pageId", reason: "required" }); - } else { - if (typeof raw.pageId !== "string") { - violations.push({ path: "pageId", reason: "expected string" }); + let pageId: string = undefined as unknown as string; + if (raw.pageId === undefined || raw.pageId === null) { + violations.push({ path: "pageId", reason: "required" }); } else { - pageId = raw.pageId; + if (typeof raw.pageId !== "string") { + violations.push({ path: "pageId", reason: "expected string" }); + } else { + pageId = raw.pageId; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "pageId") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "pageId") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetPageInput = { pageId }; + return out; } - const out: GetPageInput = { pageId }; - return out; - } - - public toIntermediate(value: GetPageInput): unknown { - const out: Record = {}; - out.pageId = value.pageId; - return out; - } -} -export class PutBlockOutputMapper { - public fromIntermediate(raw: unknown): PutBlockOutput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: GetPageInput): unknown { + const out: Record = {}; + out.pageId = value.pageId; + return out; } + })(); + +export const putBlockOutputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): PutBlockOutput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let blockId: string = undefined as unknown as string; - if (raw.blockId === undefined || raw.blockId === null) { - violations.push({ path: "blockId", reason: "required" }); - } else { - if (typeof raw.blockId !== "string") { - violations.push({ path: "blockId", reason: "expected string" }); + let blockId: string = undefined as unknown as string; + if (raw.blockId === undefined || raw.blockId === null) { + violations.push({ path: "blockId", reason: "required" }); } else { - blockId = raw.blockId; + if (typeof raw.blockId !== "string") { + violations.push({ path: "blockId", reason: "expected string" }); + } else { + blockId = raw.blockId; + } } - } - let revision: number = undefined as unknown as number; - if (raw.revision === undefined || raw.revision === null) { - violations.push({ path: "revision", reason: "required" }); - } else { - if (typeof raw.revision !== "number" || !Number.isSafeInteger(raw.revision)) { - violations.push({ path: "revision", reason: "expected integer" }); + let revision: number = undefined as unknown as number; + if (raw.revision === undefined || raw.revision === null) { + violations.push({ path: "revision", reason: "required" }); } else { - revision = raw.revision; + if (typeof raw.revision !== "number" || !Number.isSafeInteger(raw.revision)) { + violations.push({ path: "revision", reason: "expected integer" }); + } else { + revision = raw.revision; + } + } + + for (const key of Object.keys(raw)) { + if (key !== "blockId" && key !== "revision") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "blockId" && key !== "revision") { - violations.push({ path: key, reason: "unknown field" }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + const out: PutBlockOutput = { blockId, revision }; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: PutBlockOutput): unknown { + const out: Record = {}; + out.blockId = value.blockId; + out.revision = value.revision; + return out; } - const out: PutBlockOutput = { blockId, revision }; - return out; - } - - public toIntermediate(value: PutBlockOutput): unknown { - const out: Record = {}; - out.blockId = value.blockId; - out.revision = value.revision; - return out; - } -} + })(); diff --git a/samples/typescript/kb/kb/services.ts b/samples/typescript/kb/kb/services.ts index d1b6a935..66f3c309 100644 --- a/samples/typescript/kb/kb/services.ts +++ b/samples/typescript/kb/kb/services.ts @@ -1,7 +1,15 @@ // Generated by nexgen. DO NOT EDIT! import * as nexus from "nexus-rpc"; +import { + getCategoryTreeInputTransferTypeConverter, + getPageInputTransferTypeConverter, + putBlockOutputTransferTypeConverter, +} from "./models"; import type { GetCategoryTreeInput, GetPageInput, PutBlockOutput } from "./models"; +import { blockTransferTypeConverter } from "../content/block/models"; +import { pageTransferTypeConverter } from "../content/page/models"; +import { categoryTransferTypeConverter } from "../tree/category/models"; import type { Block } from "../content/block/models"; import type { Page } from "../content/page/models"; import type { Category } from "../tree/category/models"; @@ -15,16 +23,26 @@ export const knowledgeBaseService = nexus.service( /** * Fetch a page by id. */ - getPage: nexus.operation({ name: "GetPage" }), + getPage: nexus.operation({ + name: "GetPage", + inputType: { transferTypeConverter: getPageInputTransferTypeConverter }, + outputType: { transferTypeConverter: pageTransferTypeConverter }, + }), /** * Create or update a content block. */ - putBlock: nexus.operation({ name: "PutBlock" }), + putBlock: nexus.operation({ + name: "PutBlock", + inputType: { transferTypeConverter: blockTransferTypeConverter }, + outputType: { transferTypeConverter: putBlockOutputTransferTypeConverter }, + }), /** * Fetch the category tree rooted at a category. */ getCategoryTree: nexus.operation({ name: "GetCategoryTree", + inputType: { transferTypeConverter: getCategoryTreeInputTransferTypeConverter }, + outputType: { transferTypeConverter: categoryTransferTypeConverter }, }), }, ); diff --git a/samples/typescript/shims/nexus-rpc-type-info.d.ts b/samples/typescript/shims/nexus-rpc-type-info.d.ts new file mode 100644 index 00000000..7cfdb311 --- /dev/null +++ b/samples/typescript/shims/nexus-rpc-type-info.d.ts @@ -0,0 +1,37 @@ +// TEMPORARY: the published nexus-rpc@0.0.2 predates +// https://github.com/nexus-rpc/sdk-typescript/pull/40, which added `TypeInfo` / +// `TransferTypeConverter` and the operation `inputType` / `outputType` metadata the +// generated code emits. `operation()` already spreads its options through +// untouched, so this is a typings-only gap: delete this file and bump the +// `nexus-rpc` pin once the release carrying #40 is published. +// +// The top-level `export {}` makes this file a module, so each `declare module` +// below *augments* the real package instead of shadowing it. `OperationOptions` +// and `OperationDefinition` are augmented at their declaring module paths rather +// than at the `nexus-rpc` barrel that re-exports them. +export {}; + +declare module "nexus-rpc" { + export interface TransferTypeConverter { + fromTransferType(value: D): T; + toTransferType(value: T): D; + } + + export interface TypeInfo { + transferTypeConverter?: TransferTypeConverter; + } +} + +declare module "nexus-rpc/lib/service/helpers" { + interface OperationOptions<_I, _O> { + inputType?: import("nexus-rpc").TypeInfo<_I, unknown>; + outputType?: import("nexus-rpc").TypeInfo<_O, unknown>; + } +} + +declare module "nexus-rpc/lib/service/service-definition" { + interface OperationDefinition { + inputType?: import("nexus-rpc").TypeInfo; + outputType?: import("nexus-rpc").TypeInfo; + } +} diff --git a/samples/typescript/showcase/services.ts b/samples/typescript/showcase/services.ts index 146cf9d7..d78851c9 100644 --- a/samples/typescript/showcase/services.ts +++ b/samples/typescript/showcase/services.ts @@ -1,6 +1,10 @@ // Generated by nexgen. DO NOT EDIT! import * as nexus from "nexus-rpc"; +import { + getShowcaseInputTransferTypeConverter, + showcaseTransferTypeConverter, +} from "./models"; import type { GetShowcaseInput, Showcase } from "./models"; /** @@ -20,5 +24,9 @@ export const showcaseServiceTs = nexus.service("example.showcase.v1.ShowcaseServ * `GetShowcase` and the synthesized I/O type stays `GetShowcaseInput` (derived from * the operation key, not the override). */ - getShowcaseTs: nexus.operation({ name: "GetShowcase" }), + getShowcaseTs: nexus.operation({ + name: "GetShowcase", + inputType: { transferTypeConverter: getShowcaseInputTransferTypeConverter }, + outputType: { transferTypeConverter: showcaseTransferTypeConverter }, + }), }); diff --git a/samples/typescript/temporal-date/models.ts b/samples/typescript/temporal-date/models.ts index 9819a947..d197196c 100644 --- a/samples/typescript/temporal-date/models.ts +++ b/samples/typescript/temporal-date/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; /** @@ -49,278 +50,279 @@ export interface Temporal { archivedOn?: string | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: Date = undefined as unknown as Date; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let birthday: string = undefined as unknown as string; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); + let createdAt: Date = undefined as unknown as Date; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; - } - } - } - - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let timeout: string = undefined as unknown as string; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let birthday: string = undefined as unknown as string; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let updatedAt: Date | undefined = undefined as unknown as Date | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: string | undefined = undefined as unknown as string | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: string = undefined as unknown as string; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: Date | undefined = undefined as unknown as Date | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: string | undefined = undefined as unknown as string | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: string | undefined = undefined as unknown as string | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: Date | null | undefined = undefined as unknown as - | Date - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: string | undefined = undefined as unknown as string | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: Date | null | undefined = undefined as unknown as + | Date + | null + | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); - out.birthday = value.birthday; - out.alarm = value.alarm; - out.timeout = value.timeout; - if (value.updatedAt !== undefined) { - out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); - } - if (value.expiresOn !== undefined) { - out.expiresOn = value.expiresOn; - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = value.retryDelay; - } - if (value.deletedAt !== undefined) { - out.deletedAt = - value.deletedAt === null - ? null - : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); - } - if (value.archivedOn !== undefined) { - out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); + out.birthday = value.birthday; + out.alarm = value.alarm; + out.timeout = value.timeout; + if (value.updatedAt !== undefined) { + out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); + } + if (value.expiresOn !== undefined) { + out.expiresOn = value.expiresOn; + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = value.retryDelay; + } + if (value.deletedAt !== undefined) { + out.deletedAt = + value.deletedAt === null + ? null + : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); + } + if (value.archivedOn !== undefined) { + out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/samples/typescript/temporal-temporal/models.ts b/samples/typescript/temporal-temporal/models.ts index a728881c..30fa7070 100644 --- a/samples/typescript/temporal-temporal/models.ts +++ b/samples/typescript/temporal-temporal/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; /** @@ -49,288 +50,289 @@ export interface Temporal { archivedOn?: Temporal.PlainDate | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: Temporal.ZonedDateTime = - undefined as unknown as Temporal.ZonedDateTime; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let birthday: Temporal.PlainDate = undefined as unknown as Temporal.PlainDate; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); + let createdAt: Temporal.ZonedDateTime = + undefined as unknown as Temporal.ZonedDateTime; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); + let birthday: Temporal.PlainDate = undefined as unknown as Temporal.PlainDate; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let timeout: Temporal.Duration = undefined as unknown as Temporal.Duration; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; - } - } - } - - let updatedAt: Temporal.ZonedDateTime | undefined = undefined as unknown as - | Temporal.ZonedDateTime - | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: Temporal.PlainDate | undefined = undefined as unknown as - | Temporal.PlainDate - | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: Temporal.Duration = undefined as unknown as Temporal.Duration; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: Temporal.ZonedDateTime | undefined = undefined as unknown as + | Temporal.ZonedDateTime + | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: Temporal.Duration | undefined = undefined as unknown as - | Temporal.Duration - | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: Temporal.PlainDate | undefined = undefined as unknown as + | Temporal.PlainDate + | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: Temporal.ZonedDateTime | null | undefined = undefined as unknown as - | Temporal.ZonedDateTime - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: Temporal.PlainDate | null | undefined = undefined as unknown as - | Temporal.PlainDate - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: Temporal.Duration | undefined = undefined as unknown as + | Temporal.Duration + | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: Temporal.ZonedDateTime | null | undefined = + undefined as unknown as Temporal.ZonedDateTime | null | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: Temporal.PlainDate | null | undefined = undefined as unknown as + | Temporal.PlainDate + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); - out.birthday = __nexgenDefinitions.serializeTemporalDate(value.birthday); - out.alarm = value.alarm; - out.timeout = __nexgenDefinitions.serializeTemporalDuration(value.timeout); - if (value.updatedAt !== undefined) { - out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); - } - if (value.expiresOn !== undefined) { - out.expiresOn = __nexgenDefinitions.serializeTemporalDate(value.expiresOn); - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = __nexgenDefinitions.serializeTemporalDuration(value.retryDelay); - } - if (value.deletedAt !== undefined) { - out.deletedAt = - value.deletedAt === null - ? null - : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); - } - if (value.archivedOn !== undefined) { - out.archivedOn = - value.archivedOn === null - ? null - : __nexgenDefinitions.serializeTemporalDate(value.archivedOn); + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = __nexgenDefinitions.serializeTemporalDateTime(value.createdAt); + out.birthday = __nexgenDefinitions.serializeTemporalDate(value.birthday); + out.alarm = value.alarm; + out.timeout = __nexgenDefinitions.serializeTemporalDuration(value.timeout); + if (value.updatedAt !== undefined) { + out.updatedAt = __nexgenDefinitions.serializeTemporalDateTime(value.updatedAt); + } + if (value.expiresOn !== undefined) { + out.expiresOn = __nexgenDefinitions.serializeTemporalDate(value.expiresOn); + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = __nexgenDefinitions.serializeTemporalDuration( + value.retryDelay, + ); + } + if (value.deletedAt !== undefined) { + out.deletedAt = + value.deletedAt === null + ? null + : __nexgenDefinitions.serializeTemporalDateTime(value.deletedAt); + } + if (value.archivedOn !== undefined) { + out.archivedOn = + value.archivedOn === null + ? null + : __nexgenDefinitions.serializeTemporalDate(value.archivedOn); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/samples/typescript/temporal/models.ts b/samples/typescript/temporal/models.ts index 6b520d85..55d78cf9 100644 --- a/samples/typescript/temporal/models.ts +++ b/samples/typescript/temporal/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; /** @@ -49,275 +50,276 @@ export interface Temporal { archivedOn?: string | null; } -export class TemporalMapper { - public fromIntermediate(raw: unknown): Temporal { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let createdAt: string = undefined as unknown as string; - if (raw.createdAt === undefined || raw.createdAt === null) { - violations.push({ path: "createdAt", reason: "required" }); - } else { - if (typeof raw.createdAt !== "string") { - violations.push({ path: "createdAt", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.createdAt, - "createdAt", - violations, - ); - if (parsed !== undefined) { - createdAt = parsed; - } +export const temporalTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Temporal { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let birthday: string = undefined as unknown as string; - if (raw.birthday === undefined || raw.birthday === null) { - violations.push({ path: "birthday", reason: "required" }); - } else { - if (typeof raw.birthday !== "string") { - violations.push({ path: "birthday", reason: "expected string" }); + let createdAt: string = undefined as unknown as string; + if (raw.createdAt === undefined || raw.createdAt === null) { + violations.push({ path: "createdAt", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.birthday, - "birthday", - violations, - ); - if (parsed !== undefined) { - birthday = parsed; - } - } - } - - let alarm: string = undefined as unknown as string; - if (raw.alarm === undefined || raw.alarm === null) { - violations.push({ path: "alarm", reason: "required" }); - } else { - if (typeof raw.alarm !== "string") { - violations.push({ path: "alarm", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.alarm, - "alarm", - violations, - ); - if (parsed !== undefined) { - alarm = parsed; + if (typeof raw.createdAt !== "string") { + violations.push({ path: "createdAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.createdAt, + "createdAt", + violations, + ); + if (parsed !== undefined) { + createdAt = parsed; + } } } - } - let timeout: string = undefined as unknown as string; - if (raw.timeout === undefined || raw.timeout === null) { - violations.push({ path: "timeout", reason: "required" }); - } else { - if (typeof raw.timeout !== "string") { - violations.push({ path: "timeout", reason: "expected string" }); + let birthday: string = undefined as unknown as string; + if (raw.birthday === undefined || raw.birthday === null) { + violations.push({ path: "birthday", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.timeout, - "timeout", - violations, - ); - if (parsed !== undefined) { - timeout = parsed; + if (typeof raw.birthday !== "string") { + violations.push({ path: "birthday", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.birthday, + "birthday", + violations, + ); + if (parsed !== undefined) { + birthday = parsed; + } } } - } - let updatedAt: string | undefined = undefined as unknown as string | undefined; - if (raw.updatedAt === null) { - violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); - } else if (raw.updatedAt !== undefined) { - if (typeof raw.updatedAt !== "string") { - violations.push({ path: "updatedAt", reason: "expected string" }); + let alarm: string = undefined as unknown as string; + if (raw.alarm === undefined || raw.alarm === null) { + violations.push({ path: "alarm", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.updatedAt, - "updatedAt", - violations, - ); - if (parsed !== undefined) { - updatedAt = parsed; + if (typeof raw.alarm !== "string") { + violations.push({ path: "alarm", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.alarm, + "alarm", + violations, + ); + if (parsed !== undefined) { + alarm = parsed; + } } } - } - let expiresOn: string | undefined = undefined as unknown as string | undefined; - if (raw.expiresOn === null) { - violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); - } else if (raw.expiresOn !== undefined) { - if (typeof raw.expiresOn !== "string") { - violations.push({ path: "expiresOn", reason: "expected string" }); + let timeout: string = undefined as unknown as string; + if (raw.timeout === undefined || raw.timeout === null) { + violations.push({ path: "timeout", reason: "required" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.expiresOn, - "expiresOn", - violations, - ); - if (parsed !== undefined) { - expiresOn = parsed; + if (typeof raw.timeout !== "string") { + violations.push({ path: "timeout", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.timeout, + "timeout", + violations, + ); + if (parsed !== undefined) { + timeout = parsed; + } } } - } - let reminder: string | undefined = undefined as unknown as string | undefined; - if (raw.reminder === null) { - violations.push({ path: "reminder", reason: "explicit null not allowed" }); - } else if (raw.reminder !== undefined) { - if (typeof raw.reminder !== "string") { - violations.push({ path: "reminder", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalTime( - raw.reminder, - "reminder", - violations, - ); - if (parsed !== undefined) { - reminder = parsed; + let updatedAt: string | undefined = undefined as unknown as string | undefined; + if (raw.updatedAt === null) { + violations.push({ path: "updatedAt", reason: "explicit null not allowed" }); + } else if (raw.updatedAt !== undefined) { + if (typeof raw.updatedAt !== "string") { + violations.push({ path: "updatedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.updatedAt, + "updatedAt", + violations, + ); + if (parsed !== undefined) { + updatedAt = parsed; + } } } - } - let retryDelay: string | undefined = undefined as unknown as string | undefined; - if (raw.retryDelay === null) { - violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); - } else if (raw.retryDelay !== undefined) { - if (typeof raw.retryDelay !== "string") { - violations.push({ path: "retryDelay", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.parseTemporalDuration( - raw.retryDelay, - "retryDelay", - violations, - ); - if (parsed !== undefined) { - retryDelay = parsed; + let expiresOn: string | undefined = undefined as unknown as string | undefined; + if (raw.expiresOn === null) { + violations.push({ path: "expiresOn", reason: "explicit null not allowed" }); + } else if (raw.expiresOn !== undefined) { + if (typeof raw.expiresOn !== "string") { + violations.push({ path: "expiresOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.expiresOn, + "expiresOn", + violations, + ); + if (parsed !== undefined) { + expiresOn = parsed; + } } } - } - let deletedAt: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.deletedAt !== undefined) { - if (raw.deletedAt === null) { - deletedAt = null; - } else { - if (typeof raw.deletedAt !== "string") { - violations.push({ path: "deletedAt", reason: "expected string" }); + let reminder: string | undefined = undefined as unknown as string | undefined; + if (raw.reminder === null) { + violations.push({ path: "reminder", reason: "explicit null not allowed" }); + } else if (raw.reminder !== undefined) { + if (typeof raw.reminder !== "string") { + violations.push({ path: "reminder", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDateTime( - raw.deletedAt, - "deletedAt", + const parsed = __nexgenDefinitions.parseTemporalTime( + raw.reminder, + "reminder", violations, ); if (parsed !== undefined) { - deletedAt = parsed; + reminder = parsed; } } } - } - let archivedOn: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.archivedOn !== undefined) { - if (raw.archivedOn === null) { - archivedOn = null; - } else { - if (typeof raw.archivedOn !== "string") { - violations.push({ path: "archivedOn", reason: "expected string" }); + let retryDelay: string | undefined = undefined as unknown as string | undefined; + if (raw.retryDelay === null) { + violations.push({ path: "retryDelay", reason: "explicit null not allowed" }); + } else if (raw.retryDelay !== undefined) { + if (typeof raw.retryDelay !== "string") { + violations.push({ path: "retryDelay", reason: "expected string" }); } else { - const parsed = __nexgenDefinitions.parseTemporalDate( - raw.archivedOn, - "archivedOn", + const parsed = __nexgenDefinitions.parseTemporalDuration( + raw.retryDelay, + "retryDelay", violations, ); if (parsed !== undefined) { - archivedOn = parsed; + retryDelay = parsed; } } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "createdAt" && - key !== "birthday" && - key !== "alarm" && - key !== "timeout" && - key !== "updatedAt" && - key !== "expiresOn" && - key !== "reminder" && - key !== "retryDelay" && - key !== "deletedAt" && - key !== "archivedOn" - ) { - violations.push({ path: key, reason: "unknown field" }); + let deletedAt: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.deletedAt !== undefined) { + if (raw.deletedAt === null) { + deletedAt = null; + } else { + if (typeof raw.deletedAt !== "string") { + violations.push({ path: "deletedAt", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDateTime( + raw.deletedAt, + "deletedAt", + violations, + ); + if (parsed !== undefined) { + deletedAt = parsed; + } + } + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Temporal = { createdAt, birthday, alarm, timeout }; - if (updatedAt !== undefined) { - out.updatedAt = updatedAt; - } - if (expiresOn !== undefined) { - out.expiresOn = expiresOn; - } - if (reminder !== undefined) { - out.reminder = reminder; - } - if (retryDelay !== undefined) { - out.retryDelay = retryDelay; - } - if (deletedAt !== undefined) { - out.deletedAt = deletedAt; - } - if (archivedOn !== undefined) { - out.archivedOn = archivedOn; - } - return out; - } + let archivedOn: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.archivedOn !== undefined) { + if (raw.archivedOn === null) { + archivedOn = null; + } else { + if (typeof raw.archivedOn !== "string") { + violations.push({ path: "archivedOn", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.parseTemporalDate( + raw.archivedOn, + "archivedOn", + violations, + ); + if (parsed !== undefined) { + archivedOn = parsed; + } + } + } + } - public toIntermediate(value: Temporal): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.createdAt = value.createdAt; - out.birthday = value.birthday; - out.alarm = value.alarm; - out.timeout = value.timeout; - if (value.updatedAt !== undefined) { - out.updatedAt = value.updatedAt; - } - if (value.expiresOn !== undefined) { - out.expiresOn = value.expiresOn; - } - if (value.reminder !== undefined) { - out.reminder = value.reminder; - } - if (value.retryDelay !== undefined) { - out.retryDelay = value.retryDelay; - } - if (value.deletedAt !== undefined) { - out.deletedAt = value.deletedAt === null ? null : value.deletedAt; - } - if (value.archivedOn !== undefined) { - out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + for (const key of Object.keys(raw)) { + if ( + key !== "createdAt" && + key !== "birthday" && + key !== "alarm" && + key !== "timeout" && + key !== "updatedAt" && + key !== "expiresOn" && + key !== "reminder" && + key !== "retryDelay" && + key !== "deletedAt" && + key !== "archivedOn" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Temporal = { createdAt, birthday, alarm, timeout }; + if (updatedAt !== undefined) { + out.updatedAt = updatedAt; + } + if (expiresOn !== undefined) { + out.expiresOn = expiresOn; + } + if (reminder !== undefined) { + out.reminder = reminder; + } + if (retryDelay !== undefined) { + out.retryDelay = retryDelay; + } + if (deletedAt !== undefined) { + out.deletedAt = deletedAt; + } + if (archivedOn !== undefined) { + out.archivedOn = archivedOn; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + + public toTransferType(value: Temporal): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.createdAt = value.createdAt; + out.birthday = value.birthday; + out.alarm = value.alarm; + out.timeout = value.timeout; + if (value.updatedAt !== undefined) { + out.updatedAt = value.updatedAt; + } + if (value.expiresOn !== undefined) { + out.expiresOn = value.expiresOn; + } + if (value.reminder !== undefined) { + out.reminder = value.reminder; + } + if (value.retryDelay !== undefined) { + out.retryDelay = value.retryDelay; + } + if (value.deletedAt !== undefined) { + out.deletedAt = value.deletedAt === null ? null : value.deletedAt; + } + if (value.archivedOn !== undefined) { + out.archivedOn = value.archivedOn === null ? null : value.archivedOn; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); diff --git a/samples/typescript/tests/json-converter-helper.ts b/samples/typescript/tests/json-converter-helper.ts index 45ad154c..9e81f23d 100644 --- a/samples/typescript/tests/json-converter-helper.ts +++ b/samples/typescript/tests/json-converter-helper.ts @@ -1,16 +1,6 @@ import { readFileSync } from "node:fs"; import { defaultPayloadConverter } from "@temporalio/common"; - -/** - * Minimal shape of a generated model's mapper: the type hint the generator - * emits alongside each model. `fromIntermediate` validates/parses a plain JSON - * value into the model; `toIntermediate` projects the model back to a plain - * JSON value. - */ -export interface IntermediateMapper { - fromIntermediate(raw: unknown): T; - toIntermediate(value: T): unknown; -} +import type { TransferTypeConverter } from "nexus-rpc"; const encoder = new TextEncoder(); @@ -22,21 +12,24 @@ function jsonPayload(data: Uint8Array) { /** * Deserialize fixture bytes into a generated model *through the Temporal data * converter*: the default payload converter decodes the json/plain bytes into a - * plain intermediate object, which the mapper's `fromIntermediate` turns into - * the typed model. This proves the generated type hint (the mapper) drives - * converter-based deserialization. + * plain transfer value, which the generated `TransferTypeConverter`'s + * `fromTransferType` turns into the typed model. This proves the converter the + * generator attaches to each operation drives converter-based deserialization. */ -export function decodeFixture(mapper: IntermediateMapper, bytes: Uint8Array): T { - const intermediate = defaultPayloadConverter.fromPayload(jsonPayload(bytes)); - return mapper.fromIntermediate(intermediate); +export function decodeFixture( + converter: TransferTypeConverter, + bytes: Uint8Array, +): T { + const transfer = defaultPayloadConverter.fromPayload(jsonPayload(bytes)); + return converter.fromTransferType(transfer); } /** * Serialize a model back through the Temporal data converter and return the * re-encoded JSON as a generic parsed value (for JSON-equality assertions). */ -export function encodeModel(mapper: IntermediateMapper, value: T): unknown { - const payload = defaultPayloadConverter.toPayload(mapper.toIntermediate(value)); +export function encodeModel(converter: TransferTypeConverter, value: T): unknown { + const payload = defaultPayloadConverter.toPayload(converter.toTransferType(value)); if (payload?.data == null) { throw new Error("payload converter produced no data"); } @@ -49,11 +42,11 @@ export function encodeModel(mapper: IntermediateMapper, value: T): unknown * assert JSON-equality against the fixture. */ export function roundTripFixture( - mapper: IntermediateMapper, + converter: TransferTypeConverter, bytes: Uint8Array, ): { value: T; serialized: unknown } { - const value = decodeFixture(mapper, bytes); - return { value, serialized: encodeModel(mapper, value) }; + const value = decodeFixture(converter, bytes); + return { value, serialized: encodeModel(converter, value) }; } /** Read a canonical wire fixture as raw bytes. */ diff --git a/samples/typescript/tests/json-schema-chat.test.ts b/samples/typescript/tests/json-schema-chat.test.ts index 2ce61575..9869162c 100644 --- a/samples/typescript/tests/json-schema-chat.test.ts +++ b/samples/typescript/tests/json-schema-chat.test.ts @@ -1,12 +1,13 @@ import { describe, expect, test } from "vitest"; +import type { TransferTypeConverter } from "nexus-rpc"; import { DEFAULT_PRIORITY, - LabelsMapper, - MessageMapper, - RoomMapper, - SendMessageInputMapper, - SendMessageOutputMapper, + labelsTransferTypeConverter, + messageTransferTypeConverter, + roomTransferTypeConverter, + sendMessageInputTransferTypeConverter, + sendMessageOutputTransferTypeConverter, ValidationError, type Labels, type Message, @@ -15,7 +16,6 @@ import { fixtureBytes, loadFixture as loadFixtureFrom, roundTripFixture, - type IntermediateMapper, } from "./json-converter-helper.ts"; const wireFixtureDir = new URL("../../wire/json_schema/chat/", import.meta.url); @@ -25,12 +25,12 @@ function loadFixture(name: string): unknown { } // Round-trip a fixture through the Temporal data converter (driven by the -// generated mapper) and assert the re-serialized JSON is JSON-equal to the -// fixture. TS mappers preserve explicit nulls, so all chat fixtures use exact +// generated converter) and assert the re-serialized JSON is JSON-equal to the +// fixture. TS converters preserve explicit nulls, so all chat fixtures use exact // JSON-equality (no optional+nullable collapse — unlike Go). -function expectRoundTrip(name: string, mapper: IntermediateMapper): T { +function expectRoundTrip(name: string, converter: TransferTypeConverter): T { const { value, serialized } = roundTripFixture( - mapper, + converter, fixtureBytes(wireFixtureDir, name), ); expect(serialized).toEqual(loadFixture(name)); @@ -39,7 +39,10 @@ function expectRoundTrip(name: string, mapper: IntermediateMapper): T { describe("json-schema chat generated definitions", () => { test("roundtrips canonical wire fixtures through the Temporal converter", () => { - const message = expectRoundTrip("message-minimal.json", new MessageMapper()); + const message = expectRoundTrip( + "message-minimal.json", + messageTransferTypeConverter, + ); expect(message).toMatchObject({ kind: "text", body: "hi", @@ -47,34 +50,37 @@ describe("json-schema chat generated definitions", () => { expect(message.replyToId).toBeUndefined(); expect(message.priority ?? DEFAULT_PRIORITY).toBe(0); - const fullMessage = expectRoundTrip("message-full.json", new MessageMapper()); + const fullMessage = expectRoundTrip( + "message-full.json", + messageTransferTypeConverter, + ); expect(fullMessage.replyToId).toBeNull(); expect(fullMessage.priority).toBe(7); - const room = expectRoundTrip("room-open.json", new RoomMapper()); + const room = expectRoundTrip("room-open.json", roomTransferTypeConverter); expect(room.additionalProperties).toEqual({ "x-extra": 42 }); - const labels = expectRoundTrip("labels.json", new LabelsMapper()); + const labels = expectRoundTrip("labels.json", labelsTransferTypeConverter); expect(labels).toMatchObject({ additionalProperties: { env: "prod", team: "core" }, }); const request = expectRoundTrip( "send-message-input.json", - new SendMessageInputMapper(), + sendMessageInputTransferTypeConverter, ); expect(request.message.body).toBe("hi"); const response = expectRoundTrip( "send-message-output.json", - new SendMessageOutputMapper(), + sendMessageOutputTransferTypeConverter, ); expect(response.messageId).toBe("m1"); }); test("reports JSON schema validation errors", () => { expect(() => - new SendMessageInputMapper().fromIntermediate({ + sendMessageInputTransferTypeConverter.fromTransferType({ roomId: "r1", message: { kind: "text", body: "hi" }, extra: true, @@ -82,10 +88,10 @@ describe("json-schema chat generated definitions", () => { ).toThrow(ValidationError); expect(() => - new MessageMapper().fromIntermediate({ kind: "image", body: "hi" }), + messageTransferTypeConverter.fromTransferType({ kind: "image", body: "hi" }), ).toThrow(ValidationError); - expect(() => new SendMessageOutputMapper().fromIntermediate({})).toThrow( + expect(() => sendMessageOutputTransferTypeConverter.fromTransferType({})).toThrow( ValidationError, ); }); diff --git a/samples/typescript/tests/json-schema-kb-nexus.test.ts b/samples/typescript/tests/json-schema-kb-nexus.test.ts index 4d244ca0..30c4977b 100644 --- a/samples/typescript/tests/json-schema-kb-nexus.test.ts +++ b/samples/typescript/tests/json-schema-kb-nexus.test.ts @@ -2,17 +2,18 @@ import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; import * as nexus from "nexus-rpc"; +import { chatService } from "../chat/services.ts"; import { knowledgeBaseService } from "../kb/kb/services.ts"; -import { BlockMapper } from "../kb/content/block/models.ts"; +import { blockTransferTypeConverter } from "../kb/content/block/models.ts"; import type { Block } from "../kb/content/block/models.ts"; -import { CategoryMapper } from "../kb/tree/category/models.ts"; +import { categoryTransferTypeConverter } from "../kb/tree/category/models.ts"; import type { Category } from "../kb/tree/category/models.ts"; -import { PageMapper } from "../kb/content/page/models.ts"; +import { pageTransferTypeConverter } from "../kb/content/page/models.ts"; import type { Page } from "../kb/content/page/models.ts"; import { - GetCategoryTreeInputMapper, - GetPageInputMapper, - PutBlockOutputMapper, + getCategoryTreeInputTransferTypeConverter, + getPageInputTransferTypeConverter, + putBlockOutputTransferTypeConverter, } from "../kb/kb/models.ts"; import type { PutBlockOutput } from "../kb/kb/models.ts"; import { loadFixture as loadFixtureFrom } from "./json-converter-helper.ts"; @@ -37,11 +38,39 @@ describe("json-schema KB generated Nexus service", () => { ); }); + // Every operation carries its models' transfer type converters as operation + // type info, so a protocol integration can apply the conversion without the + // caller naming the converter. `chatService.operations.ping` (void on both + // sides) is the counter-case: there is no value to convert, so it carries none. + test("carries transfer type info on every operation", () => { + expect( + knowledgeBaseService.operations.getPage.inputType?.transferTypeConverter, + ).toBe(getPageInputTransferTypeConverter); + expect( + knowledgeBaseService.operations.getPage.outputType?.transferTypeConverter, + ).toBe(pageTransferTypeConverter); + expect( + knowledgeBaseService.operations.putBlock.inputType?.transferTypeConverter, + ).toBe(blockTransferTypeConverter); + expect( + knowledgeBaseService.operations.putBlock.outputType?.transferTypeConverter, + ).toBe(putBlockOutputTransferTypeConverter); + expect( + knowledgeBaseService.operations.getCategoryTree.inputType?.transferTypeConverter, + ).toBe(getCategoryTreeInputTransferTypeConverter); + expect( + knowledgeBaseService.operations.getCategoryTree.outputType?.transferTypeConverter, + ).toBe(categoryTransferTypeConverter); + + expect(chatService.operations.ping.inputType).toBeUndefined(); + expect(chatService.operations.ping.outputType).toBeUndefined(); + }); + // Register a handler bound to the generated service definition, then run a // workflow that calls every operation through the SDK's Nexus client. Both - // sides bridge the Nexus wire payloads through the generated model mappers: - // the handler validates each request with `fromIntermediate` and projects - // each response with `toIntermediate`, exercising the generated + // sides bridge the Nexus wire payloads through the generated transfer type + // converters: the handler validates each request with `fromTransferType` and + // projects each response with `toTransferType`, exercising the generated // service/operation definitions end-to-end over a real Temporal + Nexus // endpoint. test("drives every operation through a real Nexus client", async () => { @@ -49,23 +78,26 @@ describe("json-schema KB generated Nexus service", () => { const calls: Array<[string, unknown]> = []; const handler = nexus.serviceHandler(knowledgeBaseService, { async getPage(_ctx, input) { - calls.push(["GetPage", new GetPageInputMapper().fromIntermediate(input)]); - return new PageMapper().toIntermediate( + calls.push([ + "GetPage", + getPageInputTransferTypeConverter.fromTransferType(input), + ]); + return pageTransferTypeConverter.toTransferType( loadFixture("page.json"), ) as Page; }, async putBlock(_ctx, input) { - calls.push(["PutBlock", new BlockMapper().fromIntermediate(input)]); - return new PutBlockOutputMapper().toIntermediate( + calls.push(["PutBlock", blockTransferTypeConverter.fromTransferType(input)]); + return putBlockOutputTransferTypeConverter.toTransferType( loadFixture("put-block-output.json"), ) as PutBlockOutput; }, async getCategoryTree(_ctx, input) { calls.push([ "GetCategoryTree", - new GetCategoryTreeInputMapper().fromIntermediate(input), + getCategoryTreeInputTransferTypeConverter.fromTransferType(input), ]); - return new CategoryMapper().toIntermediate( + return categoryTransferTypeConverter.toTransferType( loadFixture("category-tree.json"), ) as Category; }, diff --git a/samples/typescript/tests/json-schema-kb.test.ts b/samples/typescript/tests/json-schema-kb.test.ts index e10f4edc..34b66d2a 100644 --- a/samples/typescript/tests/json-schema-kb.test.ts +++ b/samples/typescript/tests/json-schema-kb.test.ts @@ -1,18 +1,18 @@ import { describe, expect, test } from "vitest"; +import type { TransferTypeConverter } from "nexus-rpc"; import { - BlockMapper, - CategoryMapper, - GetCategoryTreeInputMapper, - GetPageInputMapper, - PageMapper, - PutBlockOutputMapper, + blockTransferTypeConverter, + categoryTransferTypeConverter, + getCategoryTreeInputTransferTypeConverter, + getPageInputTransferTypeConverter, + pageTransferTypeConverter, + putBlockOutputTransferTypeConverter, } from "../kb/index.ts"; import { fixtureBytes, loadFixture as loadFixtureFrom, roundTripFixture, - type IntermediateMapper, } from "./json-converter-helper.ts"; const wireFixtureDir = new URL("../../wire/json_schema/kb/", import.meta.url); @@ -22,12 +22,12 @@ function loadFixture(name: string): T { } // Round-trip a fixture through the Temporal data converter (driven by the -// generated mapper) and assert the re-serialized JSON is JSON-equal to the -// fixture. TS mappers preserve explicit nulls, so all KB fixtures use exact +// generated converter) and assert the re-serialized JSON is JSON-equal to the +// fixture. TS converters preserve explicit nulls, so all KB fixtures use exact // JSON-equality (no optional+nullable collapse — unlike Go). -function expectRoundTrip(name: string, mapper: IntermediateMapper): T { +function expectRoundTrip(name: string, converter: TransferTypeConverter): T { const { value, serialized } = roundTripFixture( - mapper, + converter, fixtureBytes(wireFixtureDir, name), ); expect(serialized).toEqual(loadFixture(name)); @@ -36,31 +36,37 @@ function expectRoundTrip(name: string, mapper: IntermediateMapper): T { describe("json-schema KB generated output", () => { test("roundtrips multi-file KB fixtures through the Temporal converter", () => { - const page = expectRoundTrip("page.json", new PageMapper()); + const page = expectRoundTrip("page.json", pageTransferTypeConverter); expect(page.pageId).toBe("page-1"); expect(page.blocks?.[0]?.blockId).toBe("block-1"); expect(page.blocks?.[0]?.page).toBeNull(); expect(page.blocks?.[0]?.style?.bold).toBe(true); - const block = expectRoundTrip("block.json", new BlockMapper()); + const block = expectRoundTrip("block.json", blockTransferTypeConverter); expect(block.blockId).toBe("block-1"); expect(block.page).toBeNull(); - const category = expectRoundTrip("category-tree.json", new CategoryMapper()); + const category = expectRoundTrip( + "category-tree.json", + categoryTransferTypeConverter, + ); expect(category.children?.[0]?.id).toBe("child"); - const request = expectRoundTrip("get-page-input.json", new GetPageInputMapper()); + const request = expectRoundTrip( + "get-page-input.json", + getPageInputTransferTypeConverter, + ); expect(request.pageId).toBe("page-1"); const categoryRequest = expectRoundTrip( "get-category-tree-input.json", - new GetCategoryTreeInputMapper(), + getCategoryTreeInputTransferTypeConverter, ); expect(categoryRequest.rootId).toBe("root"); const response = expectRoundTrip( "put-block-output.json", - new PutBlockOutputMapper(), + putBlockOutputTransferTypeConverter, ); expect(response.revision).toBe(7); }); diff --git a/samples/typescript/tests/json-schema-showcase.test.ts b/samples/typescript/tests/json-schema-showcase.test.ts index 1f6f12c1..29d814dd 100644 --- a/samples/typescript/tests/json-schema-showcase.test.ts +++ b/samples/typescript/tests/json-schema-showcase.test.ts @@ -1,21 +1,22 @@ import { describe, expect, test } from "vitest"; +import type { TransferTypeConverter } from "nexus-rpc"; import { - AddressMapper, - AttributesMapper, - ContactTsMapper, + addressTransferTypeConverter, + attributesTransferTypeConverter, + contactTsTransferTypeConverter, DEFAULT_DEBUG, DEFAULT_GREETING, DEFAULT_RETRIES, - ExtrasMapper, - LabelsMapper, - NicknamesMapper, - QuotasMapper, - SettingsMapper, - ShowcaseMapper, - TokensMapper, + extrasTransferTypeConverter, + labelsTransferTypeConverter, + nicknamesTransferTypeConverter, + quotasTransferTypeConverter, + settingsTransferTypeConverter, + showcaseTransferTypeConverter, + tokensTransferTypeConverter, ValidationError, - WidgetMapper, + widgetTransferTypeConverter, type LinkNote, type Showcase, type ShowcaseDetailObject, @@ -26,7 +27,6 @@ import { fixtureBytes, loadFixture as loadFixtureFrom, roundTripFixture, - type IntermediateMapper, } from "./json-converter-helper.ts"; const wireFixtureDir = new URL("../../wire/json_schema/showcase/", import.meta.url); @@ -35,11 +35,11 @@ function loadFixture(name: string): unknown { return loadFixtureFrom(wireFixtureDir, name); } -// TS mappers preserve explicit nulls, so all showcase fixtures round-trip with +// TS converters preserve explicit nulls, so all showcase fixtures round-trip with // exact JSON-equality (no optional+nullable collapse — unlike Go/Java). -function expectRoundTrip(name: string, mapper: IntermediateMapper): T { +function expectRoundTrip(name: string, converter: TransferTypeConverter): T { const { value, serialized } = roundTripFixture( - mapper, + converter, fixtureBytes(wireFixtureDir, name), ); expect(serialized).toEqual(loadFixture(name)); @@ -48,7 +48,10 @@ function expectRoundTrip(name: string, mapper: IntermediateMapper): T { describe("json-schema showcase generated definitions", () => { test("roundtrips canonical wire fixtures through the Temporal converter", () => { - const minimal = expectRoundTrip("showcase-minimal.json", new ShowcaseMapper()); + const minimal = expectRoundTrip( + "showcase-minimal.json", + showcaseTransferTypeConverter, + ); expect(minimal).toMatchObject>({ kind: "showcase", revision: 1, @@ -74,7 +77,7 @@ describe("json-schema showcase generated definitions", () => { expect(minimalWire).not.toHaveProperty("debug"); expect(minimalWire).not.toHaveProperty("retries"); - const full = expectRoundTrip("showcase-full.json", new ShowcaseMapper()); + const full = expectRoundTrip("showcase-full.json", showcaseTransferTypeConverter); expect(full.retries).toBe(5); expect(full.middleName).toBe("Q"); expect(full.tags).toEqual(["a", "b"]); @@ -88,23 +91,26 @@ describe("json-schema showcase generated definitions", () => { }); expect(full.settings?.fontSize).toBe(14); - const nulls = expectRoundTrip("showcase-nulls.json", new ShowcaseMapper()); + const nulls = expectRoundTrip("showcase-nulls.json", showcaseTransferTypeConverter); expect(nulls.middleName).toBeNull(); expect(nulls.category).toBeNull(); expect(nulls.active).toBe(false); - const address = expectRoundTrip("address-open.json", new AddressMapper()); + const address = expectRoundTrip("address-open.json", addressTransferTypeConverter); expect(address.street).toBe("1 Main St"); expect(address.additionalProperties).toEqual({ "x-extra": 7 }); - const labels = expectRoundTrip("labels.json", new LabelsMapper()); + const labels = expectRoundTrip("labels.json", labelsTransferTypeConverter); expect(labels.additionalProperties).toEqual({ env: "prod", team: "core" }); - const settings = expectRoundTrip("settings.json", new SettingsMapper()); + const settings = expectRoundTrip("settings.json", settingsTransferTypeConverter); expect(settings.theme).toBe("dark"); expect(settings.fontSize).toBe(14); - const metrics = expectRoundTrip("showcase-metrics.json", new ShowcaseMapper()); + const metrics = expectRoundTrip( + "showcase-metrics.json", + showcaseTransferTypeConverter, + ); expect(metrics.priority).toBe(5); expect(metrics.level).toBe(2); expect(metrics.ratio).toBe(15); @@ -112,7 +118,10 @@ describe("json-schema showcase generated definitions", () => { // The astral crux: "a😀b" is 3 code points but 6 UTF-8 bytes / 4 UTF-16 // units; it must round-trip through code (maxLength:5) unchanged. - const strings = expectRoundTrip("showcase-strings.json", new ShowcaseMapper()); + const strings = expectRoundTrip( + "showcase-strings.json", + showcaseTransferTypeConverter, + ); expect(strings.code).toBe("a😀b"); expect(strings.nickname).toBe("buddy"); }); @@ -120,7 +129,7 @@ describe("json-schema showcase generated definitions", () => { test("roundtrips the allOf-merged Widget type and enforces its merged bounds", () => { // Widget is an allOf base-type extension (WidgetBase folded in + an extension // branch): a flat standalone object with the union of properties and required. - const widget = expectRoundTrip("widget.json", new WidgetMapper()); + const widget = expectRoundTrip("widget.json", widgetTransferTypeConverter); expect(widget).toMatchObject>({ id: "w-1", kind: "gadget", @@ -130,14 +139,22 @@ describe("json-schema showcase generated definitions", () => { // `size` carries a bound tightened from two allOf branches to [10, 20]. expect(() => - new WidgetMapper().fromIntermediate({ id: "w-1", name: "Widget One", size: 5 }), + widgetTransferTypeConverter.fromTransferType({ + id: "w-1", + name: "Widget One", + size: 5, + }), ).toThrow(/must be >= 10, got 5/); expect(() => - new WidgetMapper().fromIntermediate({ id: "w-1", name: "Widget One", size: 25 }), + widgetTransferTypeConverter.fromTransferType({ + id: "w-1", + name: "Widget One", + size: 25, + }), ).toThrow(/must be <= 20, got 25/); // A missing required member contributed by the extension branch is rejected. - expect(() => new WidgetMapper().fromIntermediate({ id: "w-1" })).toThrow( + expect(() => widgetTransferTypeConverter.fromTransferType({ id: "w-1" })).toThrow( ValidationError, ); }); @@ -145,7 +162,7 @@ describe("json-schema showcase generated definitions", () => { test("reports JSON schema validation errors", () => { // Wrong const value. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ kind: "nope", name: "w", count: 1, @@ -156,7 +173,7 @@ describe("json-schema showcase generated definitions", () => { // Missing required (required+nullable) field. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ kind: "showcase", name: "w", count: 1, @@ -166,12 +183,12 @@ describe("json-schema showcase generated definitions", () => { // Unknown key on a closed object. expect(() => - new SettingsMapper().fromIntermediate({ theme: "dark", nope: 1 }), + settingsTransferTypeConverter.fromTransferType({ theme: "dark", nope: 1 }), ).toThrow(ValidationError); // Wrong integer const value. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ kind: "showcase", revision: 2, enabled: true, @@ -201,20 +218,20 @@ describe("json-schema showcase generated definitions", () => { // Closed value-set (enum/const) rejections with informative reasons. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, status: "archived" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, status: "archived" }), ).toThrow(/must be one of \["active", "inactive", "pending"\], got "archived"/); - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, tier: 9 })).toThrow( - /must be one of \[1, 2, 3\], got 9/, - ); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, scale: 3.5 }), + showcaseTransferTypeConverter.fromTransferType({ ...base, tier: 9 }), + ).toThrow(/must be one of \[1, 2, 3\], got 9/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, scale: 3.5 }), ).toThrow(/must be one of \[1.5, 2.5\], got 3.5/); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, enabled: false }), + showcaseTransferTypeConverter.fromTransferType({ ...base, enabled: false }), ).toThrow(/must equal true/); // Valid enum/const values are accepted. expect( - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, status: "pending", tier: 3, @@ -222,63 +239,63 @@ describe("json-schema showcase generated definitions", () => { }).status, ).toBe("pending"); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, priority: 99 }), + showcaseTransferTypeConverter.fromTransferType({ ...base, priority: 99 }), ).toThrow(/must be <= 10, got 99/); - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, level: 0 })).toThrow( - /must be > 0, got 0/, - ); - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, step: 7 })).toThrow( - /must be a multiple of 3, got 7/, - ); - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, ratio: 7 })).toThrow( - /must be a multiple of 5, got 7/, - ); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, level: 0 }), + ).toThrow(/must be > 0, got 0/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, step: 7 }), + ).toThrow(/must be a multiple of 3, got 7/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, ratio: 7 }), + ).toThrow(/must be a multiple of 5, got 7/); // String-length bounds fire at runtime, counted in code points. - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, code: "a" })).toThrow( - /must have length >= 2, got 1/, - ); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, code: "abcdef" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, code: "a" }), + ).toThrow(/must have length >= 2, got 1/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, code: "abcdef" }), ).toThrow(/must have length <= 5, got 6/); // Astral: 6 emoji = 6 code points (24 bytes); rejected by code-point count. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, code: "😀😀😀😀😀😀" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, code: "😀😀😀😀😀😀" }), ).toThrow(/must have length <= 5, got 6/); // A multi-byte value within the code-point bound is accepted (byte count 6 // would exceed maxLength:5 — proving code points, not bytes). - expect(new ShowcaseMapper().fromIntermediate({ ...base, code: "a😀b" }).code).toBe( - "a😀b", - ); + expect( + showcaseTransferTypeConverter.fromTransferType({ ...base, code: "a😀b" }).code, + ).toBe("a😀b"); // Array constraints fire at runtime with informative reasons. // Too few / too many items (minItems:1 / maxItems:5). - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, tags: [] })).toThrow( - /must have at least 1 items, got 0/, - ); expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, tags: [] }), + ).toThrow(/must have at least 1 items, got 0/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, tags: ["a", "b", "c", "d", "e", "f"], }), ).toThrow(/must have at most 5 items, got 6/); // Duplicate element (uniqueItems). expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, aliases: ["x", "x"] }), + showcaseTransferTypeConverter.fromTransferType({ ...base, aliases: ["x", "x"] }), ).toThrow(/duplicate items: element at index 1 equals index 0/); // Missing required contains match (no "admin"). expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, roles: ["user"] }), + showcaseTransferTypeConverter.fromTransferType({ ...base, roles: ["user"] }), ).toThrow(/too few matching items: at least 1, got 0/); // Too many contains matches (maxContains:2). expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, roles: ["admin", "admin", "admin"], }), ).toThrow(/too many matching items: at most 2, got 3/); // Valid arrays are accepted. - const ok = new ShowcaseMapper().fromIntermediate({ + const ok = showcaseTransferTypeConverter.fromTransferType({ ...base, tags: ["a"], aliases: ["x", "y"], @@ -289,7 +306,10 @@ describe("json-schema showcase generated definitions", () => { test("enforces pattern constraints with RE2-safe portable semantics", () => { // sku `^[A-Z]{2,4}$` and phrase `^\S+\s\S+$` round-trip. - const patterns = expectRoundTrip("showcase-patterns.json", new ShowcaseMapper()); + const patterns = expectRoundTrip( + "showcase-patterns.json", + showcaseTransferTypeConverter, + ); expect(patterns.sku).toBe("AB"); expect(patterns.phrase).toBe("hello world"); @@ -307,16 +327,16 @@ describe("json-schema showcase generated definitions", () => { }; // Lowercase / too-long sku. - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, sku: "ab" })).toThrow( - /must match pattern/, - ); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, sku: "ABCDE" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, sku: "ab" }), + ).toThrow(/must match pattern/); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, sku: "ABCDE" }), ).toThrow(/must match pattern/); // phrase with no whitespace separator. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, phrase: "helloworld" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, phrase: "helloworld" }), ).toThrow(/must match pattern/); // `\s` ASCII-class crux: a NBSP (U+00A0) is NOT ASCII whitespace. The loader @@ -324,18 +344,24 @@ describe("json-schema showcase generated definitions", () => { // RegExp, so JS's otherwise-Unicode `\s` rejects it — consistent with // Go/Python/Java. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, phrase: "hello world" }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + phrase: "hello world", + }), ).toThrow(/must match pattern/); // `$` end-anchor crux: a trailing newline is rejected. JS `$` is already // end-of-input (no `\n` exception), matching the `\Z`/`\z` rewrite applied // for Python/Java. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, phrase: "hello world\n" }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + phrase: "hello world\n", + }), ).toThrow(/must match pattern/); // A valid ASCII-space phrase and sku are accepted. - const okPattern = new ShowcaseMapper().fromIntermediate({ + const okPattern = showcaseTransferTypeConverter.fromTransferType({ ...base, sku: "XY", phrase: "hello world", @@ -346,7 +372,10 @@ describe("json-schema showcase generated definitions", () => { test("enforces asserted string formats with pinned, portable checks", () => { // uuid/email/hostname/uri/ipv4 round-trip (string-typed, no materialization). - const formats = expectRoundTrip("showcase-format.json", new ShowcaseMapper()); + const formats = expectRoundTrip( + "showcase-format.json", + showcaseTransferTypeConverter, + ); expect(formats.requestId).toBe("de305d54-75b4-431b-adb2-eb6b9e546013"); expect(formats.contactEmail).toBe("user@example.com"); expect(formats.host).toBe("api.example.com"); @@ -368,12 +397,15 @@ describe("json-schema showcase generated definitions", () => { // A malformed uuid. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, requestId: "not-a-uuid" }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + requestId: "not-a-uuid", + }), ).toThrow(/must be a valid uuid, got "not-a-uuid"/); // Single-label email domain (user@localhost) is rejected. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, contactEmail: "user@localhost", }), @@ -381,48 +413,59 @@ describe("json-schema showcase generated definitions", () => { // ipv4 octet out of range. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, gateway: "256.0.0.1" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, gateway: "256.0.0.1" }), ).toThrow(/must be a valid ipv4, got "256.0.0.1"/); // uri with a double-`::` IPv6 IP-literal host (spliced ipv6 grammar rejects). expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, homepage: "http://[1::2::3]" }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + homepage: "http://[1::2::3]", + }), ).toThrow(/must be a valid uri/); // An over-long hostname (> 253 code points) is rejected by the length guard. const longHost = Array.from({ length: 64 }, () => "abc").join("."); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, host: longHost }), + showcaseTransferTypeConverter.fromTransferType({ ...base, host: longHost }), ).toThrow(/must be a valid hostname/); }); test("enforces object member-count, propertyNames, and dependentRequired", () => { // Valid map and object round-trip. - const attributes = expectRoundTrip("attributes.json", new AttributesMapper()); + const attributes = expectRoundTrip( + "attributes.json", + attributesTransferTypeConverter, + ); expect(attributes.additionalProperties).toEqual({ host: "a", port: "8080" }); - const contact = expectRoundTrip("contact.json", new ContactTsMapper()); + const contact = expectRoundTrip("contact.json", contactTsTransferTypeConverter); expect(contact.shippingStreet).toBe("1 Main St"); expect(contact.shippingZip).toBe("90210"); // minProperties:1 on a map — an empty object is too few. - expect(() => new AttributesMapper().fromIntermediate({})).toThrow( + expect(() => attributesTransferTypeConverter.fromTransferType({})).toThrow( /must have at least 1 properties, got 0/, ); // maxProperties:3 on a map. expect(() => - new AttributesMapper().fromIntermediate({ a: "1", b: "2", c: "3", d: "4" }), + attributesTransferTypeConverter.fromTransferType({ + a: "1", + b: "2", + c: "3", + d: "4", + }), ).toThrow(/must have at most 3 properties, got 4/); // propertyNames maxLength:8 — an over-long key. - expect(() => new AttributesMapper().fromIntermediate({ toolongkey: "1" })).toThrow( - /invalid property name "toolongkey": must have length <= 8, got 10/, - ); + expect(() => + attributesTransferTypeConverter.fromTransferType({ toolongkey: "1" }), + ).toThrow(/invalid property name "toolongkey": must have length <= 8, got 10/); // dependentRequired — a shipping street present without a shipping zip. expect(() => - new ContactTsMapper().fromIntermediate({ shippingStreet: "1 Main St" }), + contactTsTransferTypeConverter.fromTransferType({ shippingStreet: "1 Main St" }), ).toThrow(/property "shippingZip" is required when "shippingStreet" is present/); // minProperties:1 on a declared-property object — an empty object. - expect(() => new ContactTsMapper().fromIntermediate({})).toThrow( + expect(() => contactTsTransferTypeConverter.fromTransferType({})).toThrow( /must have at least 1 properties, got 0/, ); }); @@ -432,16 +475,25 @@ describe("json-schema showcase generated definitions", () => { // narrows natively. const asString = expectRoundTrip( "showcase-union-string.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(asString.idOrName).toBe("abc"); - const asInt = expectRoundTrip("showcase-union-int.json", new ShowcaseMapper()); + const asInt = expectRoundTrip( + "showcase-union-int.json", + showcaseTransferTypeConverter, + ); expect(asInt.idOrName).toBe(7); // Discriminated (tagged) union (Circle | Square) selected by `kind`. - const circle = expectRoundTrip("showcase-shape-circle.json", new ShowcaseMapper()); + const circle = expectRoundTrip( + "showcase-shape-circle.json", + showcaseTransferTypeConverter, + ); expect(circle.shape).toMatchObject({ kind: "circle", radius: 2.5 }); - const square = expectRoundTrip("showcase-shape-square.json", new ShowcaseMapper()); + const square = expectRoundTrip( + "showcase-shape-square.json", + showcaseTransferTypeConverter, + ); expect(square.shape).toMatchObject({ kind: "square", side: 4 }); const base = { @@ -459,12 +511,12 @@ describe("json-schema showcase generated definitions", () => { // An unmatchable wire token (boolean) names the admissible kinds. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, idOrName: true }), + showcaseTransferTypeConverter.fromTransferType({ ...base, idOrName: true }), ).toThrow(/expected one of: string, integer/); // An unknown discriminator value is rejected (closed value set, P13.1). expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, shape: { kind: "triangle" }, }), @@ -484,57 +536,57 @@ describe("json-schema showcase generated definitions", () => { active: true, category: "tools", } as const; - const mapper = new ShowcaseMapper(); + const converter = showcaseTransferTypeConverter; // The string branch's own `minLength` and the integer branch's own // `minimum` — each enforced only for the branch the token selects. - expect(mapper.fromIntermediate({ ...base, idOrName: "abc" }).idOrName).toBe("abc"); - expect(mapper.fromIntermediate({ ...base, idOrName: 1 }).idOrName).toBe(1); - expect(() => mapper.fromIntermediate({ ...base, idOrName: "ab" })).toThrow( + expect(converter.fromTransferType({ ...base, idOrName: "abc" }).idOrName).toBe("abc"); + expect(converter.fromTransferType({ ...base, idOrName: 1 }).idOrName).toBe(1); + expect(() => converter.fromTransferType({ ...base, idOrName: "ab" })).toThrow( /idOrName: must have length >= 3, got 2/, ); - expect(() => mapper.fromIntermediate({ ...base, idOrName: 0 })).toThrow( + expect(() => converter.fromTransferType({ ...base, idOrName: 0 })).toThrow( /idOrName: must be >= 1, got 0/, ); // A closed value set on a branch narrows to a literal union type, and an // unknown string is a Violation. - expect(mapper.fromIntermediate({ ...base, mode: "manual" }).mode).toBe("manual"); - expect(() => mapper.fromIntermediate({ ...base, mode: "turbo" })).toThrow( + expect(converter.fromTransferType({ ...base, mode: "manual" }).mode).toBe("manual"); + expect(() => converter.fromTransferType({ ...base, mode: "turbo" })).toThrow( /mode: must be one of \["auto", "manual"\]/, ); // The array branch's `minItems`/`uniqueItems` and the string branch's // `pattern`, on the same union. - expect(() => mapper.fromIntermediate({ ...base, measurements: [] })).toThrow( + expect(() => converter.fromTransferType({ ...base, measurements: [] })).toThrow( /measurements: must have at least 1 items, got 0/, ); expect(() => - mapper.fromIntermediate({ ...base, measurements: [1.5, 1.5] }), + converter.fromTransferType({ ...base, measurements: [1.5, 1.5] }), ).toThrow(/duplicate items: element at index 1 equals index 0/); - expect(() => mapper.fromIntermediate({ ...base, measurements: "AUTO" })).toThrow( + expect(() => converter.fromTransferType({ ...base, measurements: "AUTO" })).toThrow( /measurements: must match pattern/, ); // Serialize re-runs the selected branch's constraints (P12). - const valid = mapper.fromIntermediate({ ...base, idOrName: "abc" }); - expect(() => mapper.toIntermediate({ ...valid, idOrName: "ab" })).toThrow( + const valid = converter.fromTransferType({ ...base, idOrName: "abc" }); + expect(() => converter.toTransferType({ ...valid, idOrName: "ab" })).toThrow( /idOrName: must have length >= 3, got 2/, ); - expect(() => mapper.toIntermediate({ ...valid, measurements: [2, 2] })).toThrow( + expect(() => converter.toTransferType({ ...valid, measurements: [2, 2] })).toThrow( /duplicate items: element at index 1 equals index 0/, ); - // A named element union validates through its own mapper, in both + // A named element union validates through its own converter, in both // directions, with the element's index on the violation path. - expect(mapper.fromIntermediate({ ...base, segments: ["ab", 0] }).segments).toEqual([ + expect(converter.fromTransferType({ ...base, segments: ["ab", 0] }).segments).toEqual([ "ab", 0, ]); - expect(() => mapper.fromIntermediate({ ...base, segments: ["a"] })).toThrow( + expect(() => converter.fromTransferType({ ...base, segments: ["a"] })).toThrow( /segments\[0\]: must have length >= 2, got 1/, ); - expect(() => mapper.toIntermediate({ ...valid, segments: [-1] })).toThrow( + expect(() => converter.toTransferType({ ...valid, segments: [-1] })).toThrow( /must be >= 0, got -1/, ); }); @@ -542,29 +594,32 @@ describe("json-schema showcase generated definitions", () => { test("round-trips the free-form object as a union branch and a named model", () => { // The inline object branch of the `payload` union, and the named `Extras` // model: members are carried verbatim in both. - const asObject = expectRoundTrip("showcase-freeform.json", new ShowcaseMapper()); + const asObject = expectRoundTrip( + "showcase-freeform.json", + showcaseTransferTypeConverter, + ); expect(asObject.payload).toEqual({ note: "free-form", big: 9007199254740992 }); expect(asObject.extras?.additionalProperties).toEqual({ note: "free-form" }); // The same union's string branch, selected by the wire token. const asString = expectRoundTrip( "showcase-freeform-string.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(asString.payload).toBe("text"); // The named free-form model round-trips standalone, nested members included. - const extras = expectRoundTrip("extras.json", new ExtrasMapper()); + const extras = expectRoundTrip("extras.json", extrasTransferTypeConverter); expect(extras.additionalProperties.nested).toEqual({ a: 1 }); // maxProperties over the member set is enforced on parse… expect(() => - new ExtrasMapper().fromIntermediate({ a: 1, b: 2, c: 3, d: 4, e: 5 }), + extrasTransferTypeConverter.fromTransferType({ a: 1, b: 2, c: 3, d: 4, e: 5 }), ).toThrow(/must have at most 4 properties/); // …and on serialize (P12). expect(() => - new ExtrasMapper().toIntermediate({ + extrasTransferTypeConverter.toTransferType({ additionalProperties: { a: 1, b: 2, c: 3, d: 4, e: 5 }, }), ).toThrow(/must have at most 4 properties/); @@ -584,21 +639,27 @@ describe("json-schema showcase generated definitions", () => { // An unmatchable wire token (boolean) names the admissible kinds. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, payload: true }), + showcaseTransferTypeConverter.fromTransferType({ ...base, payload: true }), ).toThrow(/expected one of: object, string/); }); test("round-trips a tagged union whose object branches are written inline", () => { // Each `note` branch is named by its `x-ts-name` override and emitted as an - // interface with its own mapper, so the union narrows on the `kind` literal. - const text = expectRoundTrip("showcase-note-text.json", new ShowcaseMapper()); + // interface with its own converter, so the union narrows on the `kind` literal. + const text = expectRoundTrip( + "showcase-note-text.json", + showcaseTransferTypeConverter, + ); const note = text.note as TextNote; expect(note.kind).toBe("text"); expect(note.body).toBe("remember the milk"); // The branch stays open: an unknown member is preserved (P13). expect(note.additionalProperties).toEqual({ pinned: true }); - const link = expectRoundTrip("showcase-note-link.json", new ShowcaseMapper()); + const link = expectRoundTrip( + "showcase-note-link.json", + showcaseTransferTypeConverter, + ); expect((link.note as LinkNote).href).toBe("https://example.test/notes/1"); const base = { @@ -616,7 +677,7 @@ describe("json-schema showcase generated definitions", () => { // The selected branch's own constraints are enforced. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, note: { kind: "text", body: "" }, }), @@ -624,12 +685,15 @@ describe("json-schema showcase generated definitions", () => { // An unknown tag value matches no branch. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, note: { kind: "audio" } }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + note: { kind: "audio" }, + }), ).toThrow(/unknown discriminator kind/); // Serialize dispatches on the tag and re-runs that branch's constraints (P12). expect(() => - new ShowcaseMapper().toIntermediate({ + showcaseTransferTypeConverter.toTransferType({ ...link, note: { kind: "link", href: "", additionalProperties: {} }, }), @@ -638,16 +702,22 @@ describe("json-schema showcase generated definitions", () => { test("round-trips a union written inline on a property with an object branch", () => { // `detail`'s lone structured object branch derives `ShowcaseDetailObject` from - // the union it belongs to and gets an interface + mapper, so its members keep + // the union it belongs to and gets an interface + converter, so its members keep // their constraints while the string branch selects on its own token. - const object = expectRoundTrip("showcase-detail-object.json", new ShowcaseMapper()); + const object = expectRoundTrip( + "showcase-detail-object.json", + showcaseTransferTypeConverter, + ); const detail = object.detail as ShowcaseDetailObject; expect(detail.code).toBe("E_LIMIT"); expect(detail.hint).toBe("retry later"); // The branch stays open: an unknown member is preserved (P13). expect(detail.additionalProperties).toEqual({ retryAfterMs: 250 }); - const text = expectRoundTrip("showcase-detail-string.json", new ShowcaseMapper()); + const text = expectRoundTrip( + "showcase-detail-string.json", + showcaseTransferTypeConverter, + ); expect(text.detail).toBe("E_LIMIT"); const base = { @@ -665,17 +735,17 @@ describe("json-schema showcase generated definitions", () => { // The object branch's own constraints are enforced. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, detail: { code: "" } }), + showcaseTransferTypeConverter.fromTransferType({ ...base, detail: { code: "" } }), ).toThrow(/must have length >= 1/); // A value admitted by no branch names the admissible ones. - expect(() => new ShowcaseMapper().fromIntermediate({ ...base, detail: 7 })).toThrow( - /expected one of: ShowcaseDetailObject, string/, - ); + expect(() => + showcaseTransferTypeConverter.fromTransferType({ ...base, detail: 7 }), + ).toThrow(/expected one of: ShowcaseDetailObject, string/); // Serialize picks the object branch by shape and re-runs its constraints (P12). expect(() => - new ShowcaseMapper().toIntermediate({ + showcaseTransferTypeConverter.toTransferType({ ...object, detail: { code: "", additionalProperties: {} }, }), @@ -688,13 +758,13 @@ describe("json-schema showcase generated definitions", () => { // Square are the same branch types the `shape` union uses. const square = expectRoundTrip( "showcase-shape-or-name-square.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(square.shapeOrName).toMatchObject({ kind: "square", side: 4 }); const named = expectRoundTrip( "showcase-shape-or-name-string.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(named.shapeOrName).toBe("unit-square"); @@ -714,7 +784,7 @@ describe("json-schema showcase generated definitions", () => { // The object token still routes through the discriminator, so an unknown tag // is rejected rather than falling back to the string branch. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, shapeOrName: { kind: "triangle" }, }), @@ -722,7 +792,7 @@ describe("json-schema showcase generated definitions", () => { // A token matching no branch names all admissible ones. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, shapeOrName: 7 }), + showcaseTransferTypeConverter.fromTransferType({ ...base, shapeOrName: 7 }), ).toThrow(/expected one of: Circle, Square, string/); }); @@ -731,13 +801,13 @@ describe("json-schema showcase generated definitions", () => { // structurally (no synthesized variant type) and narrows with Array.isArray. const list = expectRoundTrip( "showcase-measurements-array.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(list.measurements).toEqual([1.5, 2.5, 3.75]); const preset = expectRoundTrip( "showcase-measurements-string.json", - new ShowcaseMapper(), + showcaseTransferTypeConverter, ); expect(preset.measurements).toBe("auto"); @@ -756,7 +826,7 @@ describe("json-schema showcase generated definitions", () => { // A token matching neither branch names both admissible kinds. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, measurements: true }), + showcaseTransferTypeConverter.fromTransferType({ ...base, measurements: true }), ).toThrow(/expected one of: number\[\], string/); }); @@ -764,9 +834,9 @@ describe("json-schema showcase generated definitions", () => { // Three positions with no property of their own: an array element at a // named union (`shapes`), an array element at an inline union the loader // names `ShowcaseSegmentsItem`, and a map member at an inline union named - // `ChoicesValue`. Each element runs its union's own mapper, so a bad value + // `ChoicesValue`. Each element runs its union's own converter, so a bad value // is reported at its index / key. - const value = expectRoundTrip("showcase-element-unions.json", new ShowcaseMapper()); + const value = expectRoundTrip("showcase-element-unions.json", showcaseTransferTypeConverter); expect(value.shapes).toEqual([ { kind: "circle", radius: 2.5, additionalProperties: {} }, @@ -796,22 +866,22 @@ describe("json-schema showcase generated definitions", () => { } as const; expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, shapes: [{ kind: "circle", radius: 1 }, true], }), ).toThrow(/shapes\[1\]/); expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, shapes: [{ kind: "triangle" }], }), ).toThrow(/triangle/); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, segments: ["ok", 1.5] }), + showcaseTransferTypeConverter.fromTransferType({ ...base, segments: ["ok", 1.5] }), ).toThrow(/segments\[1\]/); expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, choices: { primary: "circle" }, }), @@ -820,40 +890,43 @@ describe("json-schema showcase generated definitions", () => { test("rejects invalid in-memory values on serialize (P12, both directions)", () => { // A valid model round-trips; mutating a single field to an out-of-spec value - // and re-serializing (toIntermediate) is rejected before any wire object is + // and re-serializing (toTransferType) is rejected before any wire object is // produced, with the same informative reason as the parse path. - const full = new ShowcaseMapper().fromIntermediate( + const full = showcaseTransferTypeConverter.fromTransferType( loadFixture("showcase-full.json"), ); // Numeric bound: an in-memory value past `maximum` fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ ...full, priority: 42 }), + showcaseTransferTypeConverter.toTransferType({ ...full, priority: 42 }), ).toThrow(/must be <= 10, got 42/); // String length: an in-memory over-long string fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ ...full, code: "abcdef" }), + showcaseTransferTypeConverter.toTransferType({ ...full, code: "abcdef" }), ).toThrow(/must have length <= 5, got 6/); // Pattern: an in-memory off-pattern value fails to serialize. - expect(() => new ShowcaseMapper().toIntermediate({ ...full, sku: "xyz" })).toThrow( - /must match pattern/, - ); + expect(() => + showcaseTransferTypeConverter.toTransferType({ ...full, sku: "xyz" }), + ).toThrow(/must match pattern/); // Format: an in-memory malformed uuid fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ ...full, requestId: "nope" }), + showcaseTransferTypeConverter.toTransferType({ ...full, requestId: "nope" }), ).toThrow(/must be a valid uuid, got "nope"/); // Array: an in-memory duplicate (uniqueItems) fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ ...full, aliases: ["dup", "dup"] }), + showcaseTransferTypeConverter.toTransferType({ + ...full, + aliases: ["dup", "dup"], + }), ).toThrow(/duplicate items: element at index 1 equals index 0/); // Closed value-set: a mutated enum member fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ + showcaseTransferTypeConverter.toTransferType({ ...full, status: "archived" as (typeof full)["status"], }), @@ -861,21 +934,23 @@ describe("json-schema showcase generated definitions", () => { // const: a mutated integer const fails to serialize. expect(() => - new ShowcaseMapper().toIntermediate({ + showcaseTransferTypeConverter.toTransferType({ ...full, revision: 2 as (typeof full)["revision"], }), ).toThrow(/must equal 1/); // allOf-merged bound: an in-memory `size` past the tightened maximum fails. - const widget = new WidgetMapper().fromIntermediate(loadFixture("widget.json")); - expect(() => new WidgetMapper().toIntermediate({ ...widget, size: 25 })).toThrow( - /must be <= 20, got 25/, + const widget = widgetTransferTypeConverter.fromTransferType( + loadFixture("widget.json"), ); + expect(() => + widgetTransferTypeConverter.toTransferType({ ...widget, size: 25 }), + ).toThrow(/must be <= 20, got 25/); // Object dependentRequired: a shipping street with no zip fails to serialize. expect(() => - new ContactTsMapper().toIntermediate({ + contactTsTransferTypeConverter.toTransferType({ shippingStreet: "1 Main St", additionalProperties: {}, }), @@ -883,24 +958,24 @@ describe("json-schema showcase generated definitions", () => { // Object member-count: an empty map is below minProperties:1 on serialize. expect(() => - new AttributesMapper().toIntermediate({ additionalProperties: {} }), + attributesTransferTypeConverter.toTransferType({ additionalProperties: {} }), ).toThrow(/must have at least 1 properties, got 0/); // propertyNames key-shape: an over-long key fails to serialize. expect(() => - new AttributesMapper().toIntermediate({ + attributesTransferTypeConverter.toTransferType({ additionalProperties: { toolongkey: "1" }, }), ).toThrow(/invalid property name "toolongkey": must have length <= 8, got 10/); // A valid model still serializes cleanly (no false rejection). - expect(() => new ShowcaseMapper().toIntermediate(full)).not.toThrow(); + expect(() => showcaseTransferTypeConverter.toTransferType(full)).not.toThrow(); }); test("roundtrips materialized contentEncoding bytes and rejects malformed", () => { // blob (base64) and urlBlob (base64url) round-trip: a JSON string on the // wire, a native Uint8Array in the model, re-encoded byte-identically via the // pure-JS codec. The same bytes (">>>") encode to "Pj4+" vs "Pj4-". - const bytes = expectRoundTrip("showcase-bytes.json", new ShowcaseMapper()); + const bytes = expectRoundTrip("showcase-bytes.json", showcaseTransferTypeConverter); const expected = new Uint8Array([0x3e, 0x3e, 0x3e]); expect(bytes.blob).toEqual(expected); expect(bytes.urlBlob).toEqual(expected); @@ -918,17 +993,17 @@ describe("json-schema showcase generated definitions", () => { // A base64 field using the URL-safe alphabet is rejected by the pinned regex. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, blob: "Pj4-" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, blob: "Pj4-" }), ).toThrow(/must be base64-encoded, got "Pj4-"/); // A base64 field missing padding is rejected. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, blob: "aGk" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, blob: "aGk" }), ).toThrow(/must be base64-encoded/); // A base64url field carrying padding is rejected. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, urlBlob: "aGk=" }), + showcaseTransferTypeConverter.fromTransferType({ ...base, urlBlob: "aGk=" }), ).toThrow(/must be base64url-encoded, got "aGk="/); }); @@ -939,7 +1014,7 @@ describe("json-schema showcase generated definitions", () => { // map and its member (`ledger`), and a free-form bag (`metadata`). The same // fixture covers a typed map's member constraints (`quotas`, `tokens`, // `nicknames`) and a nested array (`grid`). - const value = expectRoundTrip("showcase-inline-shapes.json", new ShowcaseMapper()); + const value = expectRoundTrip("showcase-inline-shapes.json", showcaseTransferTypeConverter); expect(value.grid).toEqual([[1, 2], [3]]); expect(value.location).toEqual({ @@ -978,28 +1053,28 @@ describe("json-schema showcase generated definitions", () => { // A hoisted shape validates like any other model, at the nested path. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, location: { city: "" } }), + showcaseTransferTypeConverter.fromTransferType({ ...base, location: { city: "" } }), ).toThrow(/location\.city/); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, rows: [{ cell: "ok" }, {}] }), + showcaseTransferTypeConverter.fromTransferType({ ...base, rows: [{ cell: "ok" }, {}] }), ).toThrow(/rows\[1\]\.cell/); // A nested array reports the failing element at its own two-dimensional index. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, grid: [[1], [2, 1.5]] }), + showcaseTransferTypeConverter.fromTransferType({ ...base, grid: [[1], [2, 1.5]] }), ).toThrow(/grid\[1\]\[1\]/); // A typed map's member constraints are enforced, keyed by the member. expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, quotas: { cpu: 7 } }), + showcaseTransferTypeConverter.fromTransferType({ ...base, quotas: { cpu: 7 } }), ).toThrow(/cpu/); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, tokens: { primary: "AB" } }), + showcaseTransferTypeConverter.fromTransferType({ ...base, tokens: { primary: "AB" } }), ).toThrow(/primary/); expect(() => - new ShowcaseMapper().fromIntermediate({ ...base, nicknames: { tiny: "a" } }), + showcaseTransferTypeConverter.fromTransferType({ ...base, nicknames: { tiny: "a" } }), ).toThrow(/tiny/); // The free-form bag's member-count bound rides with the hoisted type. expect(() => - new ShowcaseMapper().fromIntermediate({ + showcaseTransferTypeConverter.fromTransferType({ ...base, metadata: { a: 1, b: 2, c: 3, d: 4 }, }), @@ -1007,13 +1082,13 @@ describe("json-schema showcase generated definitions", () => { // Serialize re-runs every member's own constraints before emitting (P12). expect(() => - new QuotasMapper().toIntermediate({ additionalProperties: { cpu: 7 } }), + quotasTransferTypeConverter.toTransferType({ additionalProperties: { cpu: 7 } }), ).toThrow(/cpu/); expect(() => - new TokensMapper().toIntermediate({ additionalProperties: { primary: "AB" } }), + tokensTransferTypeConverter.toTransferType({ additionalProperties: { primary: "AB" } }), ).toThrow(/primary/); expect(() => - new NicknamesMapper().toIntermediate({ additionalProperties: { tiny: "a" } }), + nicknamesTransferTypeConverter.toTransferType({ additionalProperties: { tiny: "a" } }), ).toThrow(/tiny/); }); }); diff --git a/samples/typescript/tests/json-schema-temporal.test.ts b/samples/typescript/tests/json-schema-temporal.test.ts index b5900415..0f32681d 100644 --- a/samples/typescript/tests/json-schema-temporal.test.ts +++ b/samples/typescript/tests/json-schema-temporal.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import { TemporalMapper as StringTemporalMapper } from "../temporal/models.ts"; -import { TemporalMapper as DateTemporalMapper } from "../temporal-date/models.ts"; -import { TemporalMapper as TemporalTemporalMapper } from "../temporal-temporal/models.ts"; +import { temporalTransferTypeConverter as stringTemporalTransferTypeConverter } from "../temporal/models.ts"; +import { temporalTransferTypeConverter as dateTemporalTransferTypeConverter } from "../temporal-date/models.ts"; +import { temporalTransferTypeConverter as temporalTemporalTransferTypeConverter } from "../temporal-temporal/models.ts"; import { decodeFixture, encodeModel, @@ -24,7 +24,7 @@ function load(name: string): unknown { describe("json-schema temporal (--js-temporal-repr=string, default)", () => { test("materialized temporals round-trip losslessly as canonical strings", () => { const { value, serialized } = roundTripFixture( - new StringTemporalMapper(), + stringTemporalTransferTypeConverter, bytes("temporal-full.json"), ); expect(serialized).toEqual(load("temporal-full.json")); @@ -32,7 +32,7 @@ describe("json-schema temporal (--js-temporal-repr=string, default)", () => { expect(value.timeout).toBe("PT1H30M"); const minimal = roundTripFixture( - new StringTemporalMapper(), + stringTemporalTransferTypeConverter, bytes("temporal-minimal.json"), ); expect(minimal.serialized).toEqual(load("temporal-minimal.json")); @@ -40,10 +40,10 @@ describe("json-schema temporal (--js-temporal-repr=string, default)", () => { test("non-canonical input is canonicalized (uppercase T/Z, +00:00 -> Z, PT90M -> PT1H30M)", () => { const value = decodeFixture( - new StringTemporalMapper(), + stringTemporalTransferTypeConverter, bytes("temporal-canonicalize.json"), ); - expect(encodeModel(new StringTemporalMapper(), value)).toEqual({ + expect(encodeModel(stringTemporalTransferTypeConverter, value)).toEqual({ createdAt: "2021-06-15T12:30:45Z", birthday: "2021-02-28", alarm: "12:30:45Z", @@ -52,7 +52,7 @@ describe("json-schema temporal (--js-temporal-repr=string, default)", () => { }); test("materialized narrowing rejects :60, calendar duration, bad date, missing offset", () => { - const mapper = new StringTemporalMapper(); + const converter = stringTemporalTransferTypeConverter; for (const bad of [ { createdAt: "2021-12-31T23:59:60Z", timeout: "PT0S" }, { createdAt: "2021-06-15T12:30:45Z", timeout: "P1Y" }, @@ -60,7 +60,7 @@ describe("json-schema temporal (--js-temporal-repr=string, default)", () => { { createdAt: "2021-06-15T12:30:45", timeout: "PT0S" }, ]) { const body = { birthday: "2000-01-01", alarm: "09:00:00", ...bad }; - expect(() => mapper.fromIntermediate(body)).toThrow(); + expect(() => converter.fromTransferType(body)).toThrow(); } }); }); @@ -68,11 +68,14 @@ describe("json-schema temporal (--js-temporal-repr=string, default)", () => { // --- --js-temporal-repr=date: date-time -> Date (UTC ms fold); others string. --- describe("json-schema temporal (--js-temporal-repr=date)", () => { test("date-time materializes to a Date and folds to a UTC instant on re-serialize", () => { - const value = decodeFixture(new DateTemporalMapper(), bytes("temporal-full.json")); + const value = decodeFixture( + dateTemporalTransferTypeConverter, + bytes("temporal-full.json"), + ); expect(value.createdAt).toBeInstanceOf(Date); expect((value.createdAt as Date).toISOString()).toBe("2021-06-15T10:30:45.123Z"); expect(value.birthday).toBe("2021-06-15"); // stays string - const serialized = encodeModel(new DateTemporalMapper(), value) as Record< + const serialized = encodeModel(dateTemporalTransferTypeConverter, value) as Record< string, unknown >; @@ -85,7 +88,7 @@ describe("json-schema temporal (--js-temporal-repr=date)", () => { describe("json-schema temporal (--js-temporal-repr=temporal)", () => { test("temporals materialize to Temporal types and round-trip losslessly", () => { const { value, serialized } = roundTripFixture( - new TemporalTemporalMapper(), + temporalTemporalTransferTypeConverter, bytes("temporal-full.json"), ); expect(serialized).toEqual(load("temporal-full.json")); @@ -100,10 +103,10 @@ describe("json-schema temporal (--js-temporal-repr=temporal)", () => { test("non-canonical input canonicalizes through Temporal types", () => { const value = decodeFixture( - new TemporalTemporalMapper(), + temporalTemporalTransferTypeConverter, bytes("temporal-canonicalize.json"), ); - expect(encodeModel(new TemporalTemporalMapper(), value)).toEqual({ + expect(encodeModel(temporalTemporalTransferTypeConverter, value)).toEqual({ createdAt: "2021-06-15T12:30:45Z", birthday: "2021-02-28", alarm: "12:30:45Z", diff --git a/samples/typescript/tests/workflows/json-schema-kb.ts b/samples/typescript/tests/workflows/json-schema-kb.ts index f47ccad8..fef6cdf3 100644 --- a/samples/typescript/tests/workflows/json-schema-kb.ts +++ b/samples/typescript/tests/workflows/json-schema-kb.ts @@ -1,24 +1,25 @@ import * as workflow from "@temporalio/workflow"; import { knowledgeBaseService } from "../../kb/kb/services.ts"; -import { BlockMapper } from "../../kb/content/block/models.ts"; +import { blockTransferTypeConverter } from "../../kb/content/block/models.ts"; import type { Block } from "../../kb/content/block/models.ts"; -import { PageMapper } from "../../kb/content/page/models.ts"; -import { CategoryMapper } from "../../kb/tree/category/models.ts"; +import { pageTransferTypeConverter } from "../../kb/content/page/models.ts"; +import { categoryTransferTypeConverter } from "../../kb/tree/category/models.ts"; import { - GetCategoryTreeInputMapper, - GetPageInputMapper, - PutBlockOutputMapper, + getCategoryTreeInputTransferTypeConverter, + getPageInputTransferTypeConverter, + putBlockOutputTransferTypeConverter, } from "../../kb/kb/models.ts"; import type { GetCategoryTreeInput, GetPageInput } from "../../kb/kb/models.ts"; // Drive the generated Nexus service *definition* through the Temporal SDK's // built-in Nexus client — no generated API client. Every request and response -// crossing the Nexus boundary goes through the generated model mappers: -// `toIntermediate` projects a typed model to its plain wire form on the way -// out, and `fromIntermediate` validates/parses the plain wire form back into a +// crossing the Nexus boundary goes through the generated transfer type +// converters: +// `toTransferType` projects a typed model to its plain wire form on the way +// out, and `fromTransferType` validates/parses the plain wire form back into a // typed model on the way in. The generated operations are typed with the model, -// so the intentionally-untyped wire value from `toIntermediate` is asserted +// so the intentionally-untyped wire value from `toTransferType` is asserted // back to the model type at the send boundary. export async function jsonSchemaKbCaller(): Promise<{ blockId: string; @@ -33,9 +34,11 @@ export async function jsonSchemaKbCaller(): Promise<{ const pageHandle = await client.startOperation( knowledgeBaseService.operations.getPage, - new GetPageInputMapper().toIntermediate({ pageId: "page-1" }) as GetPageInput, + getPageInputTransferTypeConverter.toTransferType({ + pageId: "page-1", + }) as GetPageInput, ); - const page = new PageMapper().fromIntermediate(await pageHandle.result()); + const page = pageTransferTypeConverter.fromTransferType(await pageHandle.result()); const block = page.blocks?.[0]; if (block == null) { throw new Error("expected page block"); @@ -43,19 +46,21 @@ export async function jsonSchemaKbCaller(): Promise<{ const putBlockHandle = await client.startOperation( knowledgeBaseService.operations.putBlock, - new BlockMapper().toIntermediate(block) as Block, + blockTransferTypeConverter.toTransferType(block) as Block, ); - const putBlockOutput = new PutBlockOutputMapper().fromIntermediate( + const putBlockOutput = putBlockOutputTransferTypeConverter.fromTransferType( await putBlockHandle.result(), ); const categoryHandle = await client.startOperation( knowledgeBaseService.operations.getCategoryTree, - new GetCategoryTreeInputMapper().toIntermediate({ + getCategoryTreeInputTransferTypeConverter.toTransferType({ rootId: "root", }) as GetCategoryTreeInput, ); - const category = new CategoryMapper().fromIntermediate(await categoryHandle.result()); + const category = categoryTransferTypeConverter.fromTransferType( + await categoryHandle.result(), + ); return { blockId: putBlockOutput.blockId, diff --git a/samples/typescript/tsconfig.json b/samples/typescript/tsconfig.json index 73ada578..26003d1c 100644 --- a/samples/typescript/tsconfig.json +++ b/samples/typescript/tsconfig.json @@ -12,6 +12,7 @@ "esModuleInterop": true }, "include": [ + "shims/**/*.d.ts", "chat/**/*.ts", "kb/**/*.ts", "showcase/**/*.ts", diff --git a/specs/json-schema/PRINCIPLES.md b/specs/json-schema/PRINCIPLES.md index e2024b20..4cec6b1f 100644 --- a/specs/json-schema/PRINCIPLES.md +++ b/specs/json-schema/PRINCIPLES.md @@ -59,9 +59,9 @@ renumber them. ## TypeScript 1. **Hand-emitted validators, no runtime schema library (P4).** The generated runtime ships only plain `typeof`/`Array.isArray`/`Number.isSafeInteger` checks — no `zod`/`ajv`/`lossless-json` dependency. The ±(2^53−1) integer cap (see [[type]]) is what makes this possible. -2. **Models emit `interface`s, not classes.** Models stay structural types — plain objects with no methods or runtime footprint (tree-shakeable, hand-written-feeling, P2). Conversion and validation live *off* the model, in a companion `TypeHint` converter class (§4). +2. **Models emit `interface`s, not classes.** Models stay structural types — plain objects with no methods or runtime footprint (tree-shakeable, hand-written-feeling, P2). Conversion and validation live *off* the model, in a companion `TransferTypeConverter` (§4). 3. **Aggregate via a single `ValidationError` (extends `Error`) holding `Violation[]` (P11).** Collect every `Violation { path, reason }` into the list and throw **one** custom `ValidationError` — *not* a built-in `AggregateError` — whose `message` surfaces every violation and whose `violations` array exposes them structured, mirroring Java's `ValidationException` and Python's `pydantic.ValidationError`. Structured `path`/`reason`, never stringly-typed. -4. **A companion `TypeHint` class converts model ⇄ intermediate and validates; it does not stringify (P12).** Each model gets a converter class — `class UserTypeHint { fromIntermediate(raw: unknown): User; toIntermediate(obj: User): unknown }` — where `toIntermediate` is the encode adapter (model → plain JSON value) and `fromIntermediate` the parse adapter (untrusted JSON value → model). Both run the same hand-emitted validators (collecting `Violation`s into one `ValidationError`), so validation lives *inside* the conversion — that is what makes them the single source of truth in both directions. The intermediate is plain `unknown` (raw JSON), never a `string`: the byte-level `JSON.stringify`/`JSON.parse` is the Temporal converter's boundary, which hands the TypeHint the parsed (or about-to-be-stringified) value. Working in intermediate values (not strings) is also what makes conversions **composable** — a parent's `toIntermediate` calls its children's on nested values and embeds the results, and `fromIntermediate` likewise; a `string` could not nest. The per-field omit-vs-`null` decision follows the [[nullability]] serialize table. +4. **A companion `TransferTypeConverter` instance converts model ⇄ transfer value and validates; it does not stringify (P12).** Each model gets one exported converter — `export const userTransferTypeConverter = new class implements TransferTypeConverter { fromTransferType(raw: unknown): User; toTransferType(value: User): unknown }()` — implementing nexus-rpc's `TransferTypeConverter` contract, where `toTransferType` is the encode adapter (model → plain JSON value) and `fromTransferType` the parse adapter (untrusted JSON value → model). It is an **instance**, not a class: the SDK's operation type info holds a converter, so emitting the instance is what lets each operation carry its own (see [[services]]) with no construction at the use site. Both directions run the same hand-emitted validators (collecting `Violation`s into one `ValidationError`), so validation lives *inside* the conversion — that is what makes them the single source of truth. The transfer value is plain `unknown` (raw JSON), never a `string`: the byte-level `JSON.stringify`/`JSON.parse` is the Temporal converter's boundary, which hands the converter the parsed (or about-to-be-stringified) value. Working in transfer values (not strings) is also what makes conversions **composable** — a parent's `toTransferType` calls its children's on nested values and embeds the results, and `fromTransferType` likewise; a `string` could not nest. The per-field omit-vs-`null` decision follows the [[nullability]] serialize table. Because the contract name is imported into every model module, `TransferTypeConverter` joins the module's reserved generated identifiers — a `$defs` type of that name is a §15 load reject, not a shadowed import. The derived `TransferTypeConverter` identifiers join that same per-module namespace: lower-camel-casing folds type names the namespace keeps apart, so two models whose converters coincide reject at load rather than emitting the same `export const` twice. ## Python diff --git a/specs/json-schema/features/const.md b/specs/json-schema/features/const.md index e2365146..72a43a50 100644 --- a/specs/json-schema/features/const.md +++ b/specs/json-schema/features/const.md @@ -347,7 +347,7 @@ in: | Language | Mechanism | |---|---| | Go | Field typed with the defined type (`Kind UserEventKind`), set idiomatically via the typed value constant (`UserEvent{Kind: UserEventKindUser}`). A forgotten field is the zero value (`UserEventKind("")`), which the shared `Validate` rejects **loudly** on serialize — consistent with how Go treats every required field. optional+const uses a pointer to the defined type + `,omitempty`, validated when non-nil. | -| TypeScript | The field is the closed literal (`kind: "user"`); a wrong value is a compile error, so a required+const is always correct in memory and emitted by the normal `toIntermediate`. optional+const emits when not `undefined`. | +| TypeScript | The field is the closed literal (`kind: "user"`); a wrong value is a compile error, so a required+const is always correct in memory and emitted by the normal `toTransferType`. optional+const emits when not `undefined`. | | Python | Presence follows [[required]] like any field — **no auto-fill**: a required+const absent on the wire is a required violation (Pydantic's own missing-field error), an optional+const absent stays omitted, and a `model_validator` enforces `== "user"` whenever the value is present. A required+const is set by the consumer, so it is already in `model_fields_set` and emits under plain `to_json` (the **default Temporal converter** path); the generated `@model_serializer(mode='wrap')` re-validates it before emit. | | Java | `private final UserEventKind kind = UserEventKind.USER;` for required+const, getter only. The value class can only hold a known constant, so the getter (via `@JsonValue`) emits `"user"` by the normal path. On the way in, the collecting deserializer's membership lookup records a `Violation` for a non-`"user"` wire value. optional+const is a `@Nullable UserEventKind` constructor parameter, validated if non-null. Numeric/boolean consts use their value classes the same way. | diff --git a/specs/json-schema/features/contentEncoding.md b/specs/json-schema/features/contentEncoding.md index 37569e51..e67af854 100644 --- a/specs/json-schema/features/contentEncoding.md +++ b/specs/json-schema/features/contentEncoding.md @@ -60,7 +60,7 @@ The defining choices (citing [[PRINCIPLES.md]]): browser-portable stdlib codec, so it gets a small generator-owned pure-JS codec (below). We already own the parse/encode adapters (PRINCIPLES: shadow-layout `UnmarshalJSON`, the collecting Jackson - (de)serializer, the TS TypeHint, the Python model hooks), so selecting + (de)serializer, the TS transfer type converter, the Python model hooks), so selecting the standard vs URL-safe codec per node is a codec choice, not new machinery. - **A native bytes field is the idiomatic shape (P2).** A base64 blob @@ -190,7 +190,7 @@ wire is unambiguous and the stdlib decoder below agrees. | Language | Strategy | |---|---| | Go | Parse adapter: run the encoding's pinned regex over the wire string, pushing a `Violation` on failure; else decode with the codec above → `[]byte`. Encode adapter: re-encode with the same codec. `regexp.MustCompile` compiled once at init. | -| TypeScript | `fromIntermediate`: pinned regex (`/…/u`) — **essential**, since the pure-JS decoder assumes canonical input and won't itself reject malformed text — then the generator-owned decoder → `Uint8Array`. `toIntermediate`: the generator-owned encoder. Lookup table + arithmetic; **no `Buffer`/`atob`**, so it runs in the browser. | +| TypeScript | `fromTransferType`: pinned regex (`/…/u`) — **essential**, since the pure-JS decoder assumes canonical input and won't itself reject malformed text — then the generator-owned decoder → `Uint8Array`. `toTransferType`: the generator-owned encoder. Lookup table + arithmetic; **no `Buffer`/`atob`**, so it runs in the browser. | | Python | Parse hook (an `AfterValidator` / model validator): regex over the wire string, then `b64decode(s, validate=True)` (`base64`) or `urlsafe_b64decode(s + pad)` (`base64url`) → `bytes`. Serialize: `@model_serializer` emits `b64encode(b)` / `urlsafe_b64encode(b).rstrip(b"=")` as ASCII. | | Java | The per-POJO collecting deserializer (PRINCIPLES Java §5): regex over the `String`, then `Base64.getDecoder()` / `getUrlDecoder()` `.decode(s)` → `byte[]`, pushing a `Violation` on failure. The `Serializer` emits with `getEncoder()` / `getUrlEncoder().withoutPadding()`. | diff --git a/specs/json-schema/features/default.md b/specs/json-schema/features/default.md index 0e358848..d1657856 100644 --- a/specs/json-schema/features/default.md +++ b/specs/json-schema/features/default.md @@ -181,7 +181,7 @@ default. Mechanisms (all empirically verified): | Language | Omit-unset mechanism | |---|---| | Go | `*T` with `,omitempty` → `nil` omitted by the stdlib encoder via the type-alias `MarshalJSON`. Pointer-to-zero-value still emits, so set-ness ≡ pointer-presence. | -| TypeScript | `toIntermediate` skips keys whose value is `undefined` when building the intermediate value (PRINCIPLES TS §4). | +| TypeScript | `toTransferType` skips keys whose value is `undefined` when building the transfer value (PRINCIPLES TS §4). | | Python | generated `@model_serializer(mode='wrap')` emits only `model_fields_set` keys — omits unset while the attribute still reads the default; explicit-set (incl. set-to-default) pins. No deep-equals. Baked into the model so the **default Temporal `pydantic_data_converter`** (which owns `to_json`, not us) honors it. | | Java | `@JsonInclude(NON_NULL)` — `null` (unset) omitted; getter still returns the default to the consumer. | diff --git a/specs/json-schema/features/items.md b/specs/json-schema/features/items.md index 1b24c18b..e10d5176 100644 --- a/specs/json-schema/features/items.md +++ b/specs/json-schema/features/items.md @@ -156,7 +156,7 @@ comes from [[type]]'s `"array"` row. `items` is symmetric across directions: serialize recurses the shared `Validate` into each element (a nested aggregate element runs its own -`MarshalJSON`/`toIntermediate`/`model_dump`; a scalar element re-runs the +`MarshalJSON`/`toTransferType`/`model_dump`; a scalar element re-runs the same predicate the deserializer used) **before emitting a byte**, failing with the same aggregated primitive (**P11**), and re-emits elements in order (arrays are ordered — unlike object members, element order is part diff --git a/specs/json-schema/features/maxProperties.md b/specs/json-schema/features/maxProperties.md index 6db4e5a2..efcff960 100644 --- a/specs/json-schema/features/maxProperties.md +++ b/specs/json-schema/features/maxProperties.md @@ -70,7 +70,7 @@ written** — i.e. *after* default omission and the omit-vs-`null` decision default is unset is omitted and does **not** count, exactly as it didn't on the way in. `model_fields_set` (Python) is again the exact emitted-key count under `exclude_unset`; Go/TS count the members the encoder will -emit; an over-cap model fails `MarshalJSON`/`toIntermediate`/`model_dump` +emit; an over-cap model fails `MarshalJSON`/`toTransferType`/`model_dump` rather than emitting an out-of-bounds object. Because the in-memory model can *read* a default as present that *serializes* as absent, a model can legitimately fail `maxProperties`/`minProperties` on serialize that diff --git a/specs/json-schema/features/minProperties.md b/specs/json-schema/features/minProperties.md index cea47b4c..30f44a72 100644 --- a/specs/json-schema/features/minProperties.md +++ b/specs/json-schema/features/minProperties.md @@ -71,7 +71,7 @@ serialize mirror of "before default population"). A field whose default is unset is omitted and does **not** count toward the floor, exactly as it didn't on the way in — so a model that reads as populated in memory (defaults visible) can legitimately fall **under** `minProperties` on the -wire, and serialize fails (`MarshalJSON`/`toIntermediate`/`model_dump`) rather +wire, and serialize fails (`MarshalJSON`/`toTransferType`/`model_dump`) rather than emitting an under-floor object. `model_fields_set` is again the exact emitted-key count under `exclude_unset`. See [[maxProperties]] serialize note (symmetric). diff --git a/specs/json-schema/features/oneOf.md b/specs/json-schema/features/oneOf.md index 374239d0..156a7c3e 100644 --- a/specs/json-schema/features/oneOf.md +++ b/specs/json-schema/features/oneOf.md @@ -93,7 +93,7 @@ typed map, a free-form object, member-count bounds). The constraint is not the shape, it is the **name**: every target has to materialize a *type* for a **structured** object branch — Go a defined type to carry the marker method, Java a class to `implement` the interface, Python a `BaseModel` for Pydantic -to select, TS an interface plus the mapper that validates its members — and a +to select, TS an interface plus the converter that validates its members — and a type needs a name. So every object branch must resolve to a determinate name: - **`$ref` to a named definition** — the definition's name *is* the branch @@ -399,9 +399,10 @@ literal property). This is the best-fit target: the acceptance rule is *exactly* what TS narrows without hand-written type guards (**P2**). An inline object branch is the one place TS does need a name: the branch's -members have to be validated, and that validation lives in a `Mapper` class -keyed to a type. A **structured** inline branch is therefore emitted as the -interface + mapper pair a named definition gets (`Object`, or the +members have to be validated, and that validation lives in a +`TransferTypeConverter` keyed to a type. A **structured** inline branch is +therefore emitted as the interface + converter pair a named definition gets +(`Object`, or the branch's `x-ts-name`) and enters the union under that name; the union still narrows structurally, on the object token or the discriminant literal. Only the **free-form** branch stays anonymous — it has no members to validate: @@ -414,9 +415,9 @@ export type Bar = Record | string; // free-form inline b A property-level (anonymous) union whose members need a transform gets one module-private `serialize` function — the same dispatch a named union's -`Mapper.toIntermediate` performs — so an object member is written through its -branch mapper rather than emitted with its in-memory `additionalProperties` -member intact. +`toTransferType` performs — so an object member is written through its branch +converter rather than emitted with its in-memory `additionalProperties` member +intact. ### Python @@ -636,7 +637,7 @@ then delegate**, never a trial-all-branches loop. | Language | Strategy | |---|---| | Go | The container's collecting `UnmarshalJSON` (shadow `*json.RawMessage` layout, **PRINCIPLES Go** / [[nullability]]) peeks the field's first non-space token, routes to the branch of that kind (`{`→object; `[`→array: `FooArray`; `"`→string: `FooString`; number→the numeric branch via `parseSpecInteger`/spec-number so `1.5` still yields a `Violation`). For an object token with 2+ object branches it further reads the discriminator property and selects the branch with that `const`. It then runs that branch's shared `Validate` and assigns the concrete type to the interface field. No matching kind / unknown discriminator value → `Violation` collected into the single `ValidationError`. | -| TypeScript | `fromIntermediate` is the `typeof`/`Array.isArray` chain shown above; for an object it switches on the discriminant literal (`raw.kind`) and delegates to that branch's converter (e.g. `CatTypeHint.fromIntermediate`); the fall-through pushes one `Violation`. Plain checks only (**PRINCIPLES TS §1** — no runtime schema lib). | +| TypeScript | `fromTransferType` is the `typeof`/`Array.isArray` chain shown above; for an object it switches on the discriminant literal (`raw.kind`) and delegates to that branch's converter (e.g. `catTransferTypeConverter.fromTransferType`); the fall-through pushes one `Violation`. Plain checks only (**PRINCIPLES TS §1** — no runtime schema lib). | | Python | Pydantic v2 strict `Union` selects by kind; an object tagged union uses `Field(discriminator=...)` for O(1) selection. Zero matches / unknown discriminator raise, aggregated into `pydantic.ValidationError`. | | Java | The union interface's static `fromNode` (called by the enclosing POJO's collecting deserializer, **PRINCIPLES Java §5**) switches on the `JsonNode` kind (`isObject`/`isArray`/`isTextual`/`isNumber`/`isBoolean`); for an object with 2+ object branches it peeks the discriminator node and dispatches to the matching POJO's collecting deserializer. On no match / unknown discriminator it pushes a `Violation` into the single `ValidationException` and returns `null`. One dispatcher serves both positions: a named union def and a union written inline on a property. | @@ -679,7 +680,7 @@ single branch member, so "exactly one" is structurally guaranteed and the encode adapter simply emits the held variant: Go `json.Marshal` on the interface marshals its dynamic type (a `FooString`/`FooArray` named type serializes as its underlying JSON kind; an object branch emits its -fields); TS `toIntermediate` branches on `typeof`/`Array.isArray` and +fields); TS `toTransferType` branches on `typeof`/`Array.isArray` and delegates to the member's converter; Java's `Serializer` writes by runtime class. The shared `Validate` still **re-runs the selected branch's constraints before emit**, so an in-memory member violating its own diff --git a/specs/json-schema/features/properties.md b/specs/json-schema/features/properties.md index 806b2559..b07c793d 100644 --- a/specs/json-schema/features/properties.md +++ b/specs/json-schema/features/properties.md @@ -308,7 +308,7 @@ aggregates, arrays use [[items]], etc. `properties` is symmetric across directions: serialize recurses the shared `Validate` into each present member (a nested aggregate's own -`MarshalJSON`/`toIntermediate`/`model_dump` validates it), and the JSON-name +`MarshalJSON`/`toTransferType`/`model_dump` validates it), and the JSON-name binding (`json` tag / alias / `@JsonProperty`) re-emits each member under its **original wire name**, not the case-mapped identifier — so the contract is stable in both directions. Member omit-vs-emit-`null` is diff --git a/specs/json-schema/features/ref.md b/specs/json-schema/features/ref.md index 6ee6488f..cf89f005 100644 --- a/specs/json-schema/features/ref.md +++ b/specs/json-schema/features/ref.md @@ -233,7 +233,9 @@ the recursion-pointer rule above applies to cyclic edges. Imports follow - **Python** — `from .b import Foo`, `from ._recursive import Node`, `from .definitions import ValidationError`. -- **TypeScript** — `import { Foo } from './b'`. +- **TypeScript** — `import type { Foo } from './b'`, plus + `import { fooTransferTypeConverter } from './b'` since the referencing + type's converter delegates to the target's (PRINCIPLES TS §4). - **Go / Java** — same package; no import. **Bare-`$ref`-root alias.** A file root that is exactly `{"$ref": diff --git a/specs/json-schema/features/required.md b/specs/json-schema/features/required.md index 569f372d..ed26562d 100644 --- a/specs/json-schema/features/required.md +++ b/specs/json-schema/features/required.md @@ -106,7 +106,7 @@ optional-non-nullable null rejection in [[nullability]]. For a required **Serialize side (P12).** The presence check runs again before emit, off the in-memory value: a required member that is empty in memory (Go `nil` pointer · TS `undefined` · Python unset · Java `null` reference) is a -`ValidationError`, so `MarshalJSON`/`toIntermediate`/`model_dump` fails +`ValidationError`, so `MarshalJSON`/`toTransferType`/`model_dump` fails rather than emitting a malformed object. A required member is therefore **never omitted** on serialize — required-non-nullable always emits its value; required+nullable emits the value or `null`, never absent (see the diff --git a/specs/json-schema/generated-file-layout.md b/specs/json-schema/generated-file-layout.md index cb1cc788..c4c004a5 100644 --- a/specs/json-schema/generated-file-layout.md +++ b/specs/json-schema/generated-file-layout.md @@ -227,10 +227,14 @@ types: cross-module import cycle is gone. A cycle **within** a single file stays in its module. - **TypeScript**: no recursive file. Type references erase - (`import type` is always cycle-safe) and validator-function imports are - ESM live bindings resolved at call time, not module-init; generated - const values are self-contained leaf literals, so there is no - init-order hazard. Cyclic types stay in their per-input modules. + (`import type` is always cycle-safe), and the imported *values* — a + sibling model's transfer type converter, a validator function — are ESM + live bindings read at call time, not module-init. A converter is + instantiated at module-init, but the sibling converters it delegates to + are named only inside its method bodies, so a mutually-referencing pair + initializes in either order. Every other generated const is a + self-contained leaf literal. So there is no init-order hazard, and cyclic + types stay in their per-input modules. - **Go**: no recursive file — the single flat package makes every cycle (same-file or cross-directory) a within-package reference, which Go resolves natively. This is exactly why Go flattens (above). diff --git a/specs/json-schema/nullability.md b/specs/json-schema/nullability.md index 4350ac09..c01fd464 100644 --- a/specs/json-schema/nullability.md +++ b/specs/json-schema/nullability.md @@ -337,7 +337,7 @@ Per-language mechanism (all are *encode-adapter* concerns; the shared omitted); required+nullable → `*T` **without** `omitempty` (nil → `null`); required-non-nullable → bare value type. The type-alias `MarshalJSON` lets the tags do the work. -- **TypeScript** — `toIntermediate` omits `undefined`, emits `null`; the +- **TypeScript** — `toTransferType` omits `undefined`, emits `null`; the three-state gives faithful optional+nullable for free. - **Python** — a generated `@model_serializer(mode='wrap')` emits only `model_fields_set` keys (plus const fields), implementing the whole diff --git a/specs/json-schema/services.md b/specs/json-schema/services.md index 3bd926af..5e8a7d81 100644 --- a/specs/json-schema/services.md +++ b/specs/json-schema/services.md @@ -213,7 +213,8 @@ three languages simply emit no comment, as elsewhere ([[description]]). | Aspect | Go | TypeScript | Python | Java | |---|---|---|---|---| | Service binding | pkg-level `var = struct{…}{…}` | `export const = nexus.service(fqn, {…})` | `@nexusrpc.service(name=fqn)` class | `@Service(name=fqn)` interface | -| Operation entry | field `nexus.OperationReference[In, Out]` set via `nexus.NewOperationReference[In,Out](wire)` | `nexus.operation({ name: wire })` | attr `nexusrpc.Operation[In, Out] = nexusrpc.Operation(name=wire)` | method `Out m(In input)` + `@Operation(name=wire)` | +| Operation entry | field `nexus.OperationReference[In, Out]` set via `nexus.NewOperationReference[In,Out](wire)` | `nexus.operation({ name: wire, inputType, outputType })` | attr `nexusrpc.Operation[In, Out] = nexusrpc.Operation(name=wire)` | method `Out m(In input)` + `@Operation(name=wire)` | +| Operation type info | — | `inputType`/`outputType` carry the I/O type's transfer type converter (below) | — | — | | Service wire name | `ServiceName` struct field | first arg to `nexus.service` | `service(name=…)` | `@Service(name=…)` | | Void output | `nexus.NoValue` | `void` | `None` | `void` return | | Void input | `nexus.NoValue` | `void` | `None` | **no-arg method** `Out m()` | @@ -227,7 +228,12 @@ three languages simply emit no comment, as elsewhere ([[description]]). `func NewOperationReference[I, O any](name string) OperationReference[I, O]`, `type NoValue *struct{}`. - **TypeScript** (`nexus-rpc`): `nexus.service(name, operations)`, - `nexus.operation({ name })` — confirmed by the existing + `nexus.operation({ name, inputType?, outputType? })` where both + type-info fields are `TypeInfo` and + `interface TypeInfo { transferTypeConverter?: + TransferTypeConverter }`, + `interface TransferTypeConverter { fromTransferType(value: + D): T; toTransferType(value: T): D }` — confirmed by the existing generator's compiling output. - **Python** (`nexusrpc`): `@nexusrpc.service` / `@nexusrpc.service(name=…)` (name defaults to the class name); @@ -281,12 +287,15 @@ export const chatService = nexus.service("example.v1.ChatService", { */ pollMessages: nexus.operation({ name: "poll-messages", + inputType: { transferTypeConverter: pollMessagesInputTransferTypeConverter }, + outputType: { transferTypeConverter: pollMessagesOutputTransferTypeConverter }, }), /** * Send a message. */ sendMessage: nexus.operation({ name: "SendMessage", + inputType: { transferTypeConverter: sendMessageInputTransferTypeConverter }, }), }); ``` @@ -422,6 +431,43 @@ binding itself adds nothing to that path. Void I/O (`nexus.NoValue` / TS `void` / Python `None` / Java `void` return or no-arg method) has no value to validate. +### TypeScript operation type info + +TypeScript is the one target where the operation entry **names** its I/O +types' converters: each non-void side carries +`inputType`/`outputType` = `{ transferTypeConverter: TransferTypeConverter }`, +the model's exported converter instance (PRINCIPLES TS §4). This is +metadata, not behavior — nexus-rpc carries it verbatim and interprets +nothing; a protocol integration applies the conversion when transferring +the value. It exists because TS is the only target whose conversion is +*not* discoverable from the type: Go reaches it through +`MarshalJSON`/`UnmarshalJSON` on the model, Python through the model's +Pydantic hooks, Java through the POJO's class-level Jackson +(de)serializer — all attached to the type itself, so the SDK finds them +with nothing named at the operation. A TS model is a bare `interface` with +no runtime footprint (PRINCIPLES TS §2), so its converter is a separate +value and the operation is the only place that can point at it. + +A **void** side carries neither field. There is no value to convert, so an +empty `TypeInfo` would assert a conversion that does not exist; absence is +the accurate encoding and matches the SDK's optional fields. Since a +declared `input`/`output` is always an object type (above), a non-void side +always has exactly one converter to name — there is no case where the +field would be present but empty. + +The converter identifier is derived, not declared: it is the model's +resolved type identifier lower-camel-cased plus `TransferTypeConverter` — +the same identifier the type declaration uses, so an `x-ts-name` override +moves the type and its converter together. Because it is derived and +lower-camel-casing folds names the type namespace keeps apart (`HTTPError` +and `HttpError` both yield `httpErrorTransferTypeConverter`), the converter +identifier also enters the module's identifier namespace for the +PRINCIPLES §15 collision pass: a fold rejects at load with a fix-it rather +than emitting one `export const` twice. Converters declared in another +input file's module import as **values** from that module (beside the +type-only model import), following the same module resolution as any +cross-module reference ([[ref]], [[generated-file-layout]]). + ## Reuse (existing WIT emitters) This spec is intentionally shape-compatible with the WIT generator's @@ -438,9 +484,10 @@ emission code rather than duplicate it: shared emitter must recase it. - Only the **input model** differs (JSON Schema here vs WIT there); none of WIT's input-side concepts (directives, proto backing, resources) - cross into this spec. The TS `TypeHint` converter wiring (a - not-yet-supported nexus-rpc feature) concerns the *converter* argument, - not the TS generics, which are emitted today. + cross into this spec. The TS transfer type converter wiring (below) is + JSON-Schema-only: a WIT-input operation carries the TS generics but no + operation type info, since WIT models convert through proto helpers + rather than a `TransferTypeConverter`. ## Property-testing matrix @@ -453,6 +500,9 @@ emission code rather than duplicate it: | Inline-object I/O promoted | `input: {type: object, properties: {…}}` → `Input` | | Omitted output | → `NoValue` / `void` / `None` / Java `void` return | | Omitted input | → `NoValue` / `void` / `None` / Java **no-arg method** | +| TS type info on a non-void side | `inputType`/`outputType` = `{ transferTypeConverter: … }` naming the I/O type's converter | +| TS type info on a void side | neither field emitted | +| TS type info across modules | I/O `$ref` into another module → converter imported as a value from that module | | `fqn` overrides on service and op | wire name = `fqn` verbatim | | Defaults applied | op without `fqn` → PascalCase wire; service without `fqn` → service name | | Acronym op name | `sendHTTPRequest` → field `SendHttpRequest`, type `SendHttpRequestInput` (folded; `x-*-name` to refine) | @@ -471,6 +521,7 @@ emission code rather than duplicate it: | Non-object I/O (`$ref`) | `input: {$ref: '#/$defs/Y'}` where `Y` is not `type: object` | | Synthesized name collides | inline `sendMessage.input` + a `$defs/SendMessageInput` | | Service collides with a model | service `ChatService` + a `$defs/ChatService` model (same per-package identifier) | +| TS converter identifiers fold together | `$defs/HTTPError` (kept verbatim by `x-ts-name`) + `$defs/HttpError` → one `httpErrorTransferTypeConverter` | | `$ref` I/O unresolvable / non-`$defs` | `input: {$ref: '#/properties/x'}` — per [[ref]] | | Identifier invalid/reserved in an emitted lang (no override) | a service/op key mapping to a reserved word | | `x--name` value not a legal identifier | `x-go-name: "2fa"` / a reserved word on a service or op | diff --git a/src/generator/json_schema/typescript.rs b/src/generator/json_schema/typescript.rs index 01f3acd4..677b24a3 100644 --- a/src/generator/json_schema/typescript.rs +++ b/src/generator/json_schema/typescript.rs @@ -20,6 +20,12 @@ use crate::parser::NameManifest; use crate::planning::{PlannedFamily, PlannedJsonType, PlannedSpec}; use crate::spec::{ExternalTypeSpec, ModulePath, RecordSpec}; +/// The converter identifier is owned by the parser's per-language naming policy, +/// which also enters it into the P15 collision namespace. Re-exported so the +/// shared TypeScript emitter reaches the name through this backend — the JSON +/// tier that emits the converters — and never spells the derivation itself. +pub(in crate::generator) use crate::parser::ts_transfer_type_converter_name; + thread_local! { /// The active `--date-time-types` while rendering the TS models/runtime. /// Generation is single-threaded per file, so a thread-local avoids threading @@ -803,7 +809,7 @@ fn field_needs_serialize_check(schema: &Schema) -> bool { } } -/// True when a model's `toIntermediate` must run collecting validation before +/// True when a model's `toTransferType` must run collecting validation before /// emitting the wire object: any constrained declared field, a constrained /// typed-map value, or an object-level count/name/dependency constraint. fn model_needs_serialize_validation(schema: &Schema) -> Result { @@ -861,10 +867,10 @@ fn render_ts_serialize_closed_check( /// serialize path, reusing the same emitters as the parse path (numeric / /// string-length / pattern / format / array / enum / const). References, /// temporal, and contentEncoding carry no serialize-side field check here -/// (nested mappers validate their own values; materialized reprs re-encode +/// (nested converters validate their own values; materialized reprs re-encode /// losslessly). An **inline** `oneOf` sum type narrows to the branch it holds and /// runs that branch's own checks; a `$ref` to a named union validates through the -/// union's mapper instead. +/// union's converter instead. fn render_ts_field_checks( output: &mut String, schema: &Schema, @@ -998,14 +1004,6 @@ fn render_ts_serialize_property_check( } } -pub(in crate::generator) fn model_type_ref(json_type: &PlannedJsonType) -> String { - json_type.model_name.clone() -} - -fn mapper_class_name(model_name: &str) -> String { - format!("{model_name}Mapper") -} - fn push_indented(output: &mut String, body: &str, indent: &str) { for line in body.lines() { output.push_str(indent); @@ -1027,6 +1025,31 @@ pub(in crate::generator) struct ModelBackend { ref_names: BTreeMap, } +impl ModelBackend { + /// A model's emitted type identifier, resolved through the name manifest so + /// an `x-ts-name` override applies. Every reference the backend answers has + /// to come back through the manifest: `prepare` rewrites `model_name` only on + /// the clones this backend renders, while the plan hands operations (and + /// fields) their own clones still carrying the pre-override derived name. + /// A model declared in another module is absent from this leaf's manifest and + /// keeps its planned name. + fn resolved_model_name(&self, json_type: &PlannedJsonType) -> String { + self.manifest + .type_name(&json_type.full_name) + .unwrap_or(json_type.model_name.as_str()) + .to_string() + } + + /// The identifier of the model's exported `TransferTypeConverter` instance, + /// which the operation type info and the cross-module value imports name. + pub(in crate::generator) fn transfer_type_converter( + &self, + json_type: &PlannedJsonType, + ) -> String { + ts_transfer_type_converter_name(&self.resolved_model_name(json_type)) + } +} + impl ExternalModelBackend for ModelBackend { type ModelFragments = RenderedExternalModelFragments; type WireConversion = WireValueConversion; @@ -1040,8 +1063,10 @@ impl ExternalModelBackend for ModelBackend { }; // Resolve every emitted identifier once (overrides applied), then adopt the // resolved type name as each model's `model_name` so every downstream - // derivation (interface/type decl, mapper class, `model_type_ref`) follows the - // same identifier. `$ref` targets are resolved via `ref_names` below. + // derivation (interface/type decl, converter const) follows the same + // identifier. `$ref` targets are resolved via `ref_names` below, and + // references handed in from outside the backend via + // [`ModelBackend::resolved_model_name`]. self.manifest = build_json_name_manifest(Language::TypeScript, api_plan)?; self.json_models = api_plan .external_types() @@ -1100,7 +1125,7 @@ impl ExternalModelBackend for ModelBackend { } fn model_type_annotation(&self, json_type: &PlannedJsonType) -> Option { - Some(model_type_ref(json_type)) + Some(self.resolved_model_name(json_type)) } fn wire_type_identifier(&self, json_type: &PlannedJsonType) -> Option { @@ -1113,7 +1138,7 @@ impl ExternalModelBackend for ModelBackend { _planned_record: Option<&RecordSpec>, ) -> Option { Some(WireValueConversion { - annotation: model_type_ref(json_type), + annotation: self.resolved_model_name(json_type), from_wire: "{wire}".to_string(), to_wire: "{value}".to_string(), function_name_to_wire: None, @@ -1155,7 +1180,7 @@ fn render_external_models( for model in json_models { output.push('\n'); - render_model_mapper(&mut output, model, json_models)?; + render_model_transfer_type_converter(&mut output, model, json_models)?; } Ok(RenderedExternalModelFragments { @@ -1167,7 +1192,7 @@ fn render_external_models( .collect(), value_exported_names: json_models .iter() - .map(|model| mapper_class_name(&model.model_name)) + .map(|model| ts_transfer_type_converter_name(&model.model_name)) .collect(), }) } @@ -1180,6 +1205,9 @@ const DEFINITIONS_NAMESPACE: &str = "__nexgenDefinitions"; fn render_json_model_imports(runtime_import_module: &str) -> String { let mut imports = String::new(); + // Every model gets a converter, so the SDK contract it implements is always + // referenced. Type-only: nexus-rpc contributes no runtime code to `models.ts`. + imports.push_str("import type { TransferTypeConverter } from \"nexus-rpc\";\n"); // Temporal-repr models reference the ambient global `Temporal.*` types // (TS 6's `esnext.temporal` lib) — no import required (P4). // `ValidationError`/`isPlainObject`/`Violation` are referenced by every @@ -1656,7 +1684,7 @@ fn render_default_constants(output: &mut String, models: &[&PlannedJsonType]) -> struct TsUnionVariant { ts_type: String, is_object: bool, - mapper: Option, + converter: Option, discriminant_value: Option, typeof_guard: Option<&'static str>, is_integer: bool, @@ -1766,15 +1794,15 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option nullable = true, Some("object") => { - // A `$ref` branch is the named model (parsed by its mapper); an + // A `$ref` branch is the named model (parsed by its converter); an // inline branch is the free-form object (loader-enforced), so it // stays an anonymous `Record` carried verbatim — TS needs no // synthesized name to narrow on the object token. - let (name, mapper, label) = match &branch.reference { + let (name, converter, label) = match &branch.reference { Some(reference) => { let name = reference_model_name(reference); - let mapper = mapper_class_name(&name); - (name.clone(), Some(mapper), name) + let converter = ts_transfer_type_converter_name(&name); + (name.clone(), Some(converter), name) } None => { let value = ts_map_shape(&resolved) @@ -1793,7 +1821,7 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option Option variants.push(TsUnionVariant { ts_type: ts_scalar_branch_type(&resolved, "string"), is_object: false, - mapper: None, + converter: None, discriminant_value: None, typeof_guard: Some("string"), is_integer: false, @@ -1816,7 +1844,7 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option variants.push(TsUnionVariant { ts_type: ts_scalar_branch_type(&resolved, "number"), is_object: false, - mapper: None, + converter: None, discriminant_value: None, typeof_guard: Some("number"), is_integer: true, @@ -1827,7 +1855,7 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option variants.push(TsUnionVariant { ts_type: ts_scalar_branch_type(&resolved, "number"), is_object: false, - mapper: None, + converter: None, discriminant_value: None, typeof_guard: Some("number"), is_integer: false, @@ -1838,7 +1866,7 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option variants.push(TsUnionVariant { ts_type: ts_scalar_branch_type(&resolved, "boolean"), is_object: false, - mapper: None, + converter: None, discriminant_value: None, typeof_guard: Some("boolean"), is_integer: false, @@ -1852,7 +1880,7 @@ fn classify_ts_union(schema: &Schema, models: &[&PlannedJsonType]) -> Option { + match variant.converter.as_deref() { + Some(converter) => { output.push_str(indent); output.push_str(" try {\n"); output.push_str(indent); output.push_str(&format!( - " {target} = new {mapper}().fromIntermediate({raw_expr});\n" + " {target} = {converter}.fromTransferType({raw_expr});\n" )); output.push_str(indent); output.push_str(" } catch (error) {\n"); @@ -2005,7 +2033,7 @@ fn render_ts_union_parse( output.push_str(indent); output.push_str(" }\n"); } - // An inline map-shaped branch has no mapper: the wire object is + // An inline map-shaped branch has no converter: the wire object is // already the in-memory value. None => { output.push_str(indent); @@ -2082,7 +2110,7 @@ fn ts_variant_guard(variant: &TsUnionVariant, value_expr: &str) -> Option)[{}] === {literal}) {{\n return new {mapper}().toIntermediate({value_expr} as {member});\n }}\n", + " if (({value_expr} as unknown as Record)[{}] === {literal}) {{\n return {converter}.toTransferType({value_expr} as {member});\n }}\n", typescript_string_literal(discriminant) )); } else { @@ -2143,7 +2171,7 @@ fn render_ts_union_serialize(output: &mut String, union: &TsUnion, value_expr: & // object token so a scalar/array member still reaches its own // branch below (the token is the selector, both directions). output.push_str(&format!( - " if ({DEFINITIONS_NAMESPACE}.isPlainObject({value_expr})) {{\n return new {mapper}().toIntermediate({value_expr} as unknown as {member});\n }}\n" + " if ({DEFINITIONS_NAMESPACE}.isPlainObject({value_expr})) {{\n return {converter}.toTransferType({value_expr} as unknown as {member});\n }}\n" )); } } else if variant.is_array { @@ -2173,7 +2201,7 @@ fn render_ts_union_serialize(output: &mut String, union: &TsUnion, value_expr: & /// The module-private serializer function an **inline** (property-level) union /// needs when a member's in-memory form differs from its wire form — an object -/// branch, whose mapper spreads `additionalProperties` back out. A union of +/// branch, whose converter spreads `additionalProperties` back out. A union of /// scalars, arrays, and free-form objects needs none: the member already *is* the /// wire value, so the property is assigned verbatim. fn ts_inline_union_serializer( @@ -2189,7 +2217,7 @@ fn ts_inline_union_serializer( if !union .variants .iter() - .any(|variant| variant.mapper.is_some()) + .any(|variant| variant.converter.is_some()) { return None; } @@ -2201,7 +2229,7 @@ fn ts_inline_union_serializer( /// Emits the inline-union serializers a module's models reference (see /// [`ts_inline_union_serializer`]). A named `$defs` union needs none — its own -/// `Mapper.toIntermediate` is the same dispatch. +/// `toTransferType` is the same dispatch. fn render_ts_inline_union_serializers( output: &mut String, models: &[&PlannedJsonType], @@ -2280,19 +2308,41 @@ fn render_model_interface(output: &mut String, model: &PlannedJsonType) -> Resul Ok(()) } -fn render_model_mapper( +/// Opens the model's converter: an anonymous `TransferTypeConverter` class +/// expression instantiated in place, so consumers reference a ready instance +/// (`inputType: { transferTypeConverter: … }`) instead of constructing one. +fn open_transfer_type_converter(output: &mut String, model_name: &str) { + output.push_str("export const "); + output.push_str(&ts_transfer_type_converter_name(model_name)); + output.push_str(" = new class implements TransferTypeConverter<"); + output.push_str(model_name); + output.push_str("> {\n"); + output.push_str(" public fromTransferType(raw: unknown): "); + output.push_str(model_name); + output.push_str(" {\n"); +} + +/// Closes the parse method and opens the serialize one. +fn split_transfer_type_converter(output: &mut String, model_name: &str) { + output.push_str(" }\n\n"); + output.push_str(" public toTransferType(value: "); + output.push_str(model_name); + output.push_str("): unknown {\n"); +} + +fn close_transfer_type_converter(output: &mut String) { + output.push_str(" }\n"); + output.push_str("}();\n"); +} + +fn render_model_transfer_type_converter( output: &mut String, model: &PlannedJsonType, models: &[&PlannedJsonType], ) -> Result<()> { let schema = decode_schema(model)?; if let Some(union) = classify_ts_union(&schema, models) { - output.push_str("export class "); - output.push_str(&mapper_class_name(&model.model_name)); - output.push_str(" {\n"); - output.push_str(" public fromIntermediate(raw: unknown): "); - output.push_str(&model.model_name); - output.push_str(" {\n"); + open_transfer_type_converter(output, &model.model_name); output.push_str(&format!( " const violations: {DEFINITIONS_NAMESPACE}.Violation[] = [];\n" )); @@ -2308,10 +2358,7 @@ fn render_model_mapper( )); output.push_str(" }\n"); output.push_str(" return out;\n"); - output.push_str(" }\n\n"); - output.push_str(" public toIntermediate(value: "); - output.push_str(&model.model_name); - output.push_str("): unknown {\n"); + split_transfer_type_converter(output, &model.model_name); // A named union has no enclosing model to aggregate into, so it collects // its own branch violations and throws the one aggregated error (P11/P12). let mut checks = String::new(); @@ -2328,8 +2375,7 @@ fn render_model_mapper( output.push_str(" }\n"); } render_ts_union_serialize(output, &union, "value"); - output.push_str(" }\n"); - output.push_str("}\n"); + close_transfer_type_converter(output); return Ok(()); } if is_open_object(&schema) { @@ -2337,28 +2383,19 @@ fn render_model_mapper( output.push('\n'); } - output.push_str("export class "); - output.push_str(&mapper_class_name(&model.model_name)); - output.push_str(" {\n"); - output.push_str(" public fromIntermediate(raw: unknown): "); - output.push_str(&model.model_name); - output.push_str(" {\n"); + open_transfer_type_converter(output, &model.model_name); let mut parser_body = String::new(); render_model_parser_body(&mut parser_body, model, &schema, models)?; push_indented(output, &parser_body, " "); - output.push_str(" }\n\n"); - output.push_str(" public toIntermediate(value: "); - output.push_str(&model.model_name); - output.push_str("): unknown {\n"); + split_transfer_type_converter(output, &model.model_name); let mut serializer_body = String::new(); render_model_serializer_body(&mut serializer_body, model, &schema, models)?; push_indented(output, &serializer_body, " "); - output.push_str(" }\n"); - output.push_str("}\n"); + close_transfer_type_converter(output); Ok(()) } @@ -2701,7 +2738,7 @@ fn render_property_value_parser( } // An inline `oneOf` sum-type union dispatches on the wire token / - // discriminant (a `$ref` to a named union routes through its mapper via the + // discriminant (a `$ref` to a named union routes through its converter via the // reference path below). if let Some(union) = classify_ts_union(property, models) { render_ts_union_parse(output, &union, &raw_expr, field_name, &path_expr, " "); @@ -2755,9 +2792,9 @@ fn render_value_parser_at_depth( output.push_str(indent); output.push_str(" "); output.push_str(target); - output.push_str(" = new "); - output.push_str(&mapper_class_name(&model_name)); - output.push_str("().fromIntermediate("); + output.push_str(" = "); + output.push_str(&ts_transfer_type_converter_name(&model_name)); + output.push_str(".fromTransferType("); output.push_str(raw_expr); output.push_str(");\n"); output.push_str(indent); @@ -3354,8 +3391,8 @@ fn serialize_expr(schema: &Schema, value_expr: &str) -> String { if let Some(reference) = &schema.reference { let model_name = reference_model_name(reference); return format!( - "new {}().toIntermediate({value_expr})", - mapper_class_name(&model_name) + "{}.toTransferType({value_expr})", + ts_transfer_type_converter_name(&model_name) ); } // A materialized temporal re-serializes through the generator-owned @@ -3391,7 +3428,7 @@ fn serialize_expr(schema: &Schema, value_expr: &str) -> String { } } // An array whose elements need a transform re-serializes elementwise: an - // element model's own `toIntermediate` flattens its catch-all bag onto the + // element model's own `toTransferType` flattens its catch-all bag onto the // wire object and re-encodes its temporal/bytes members, none of which the // in-memory value carries in wire form. if schema.ty.as_ref().and_then(Value::as_str) == Some("array") diff --git a/src/generator/typescript.rs b/src/generator/typescript.rs index dd285c3e..68e107df 100644 --- a/src/generator/typescript.rs +++ b/src/generator/typescript.rs @@ -245,6 +245,19 @@ impl TypeScriptExternalModels { files.extend(self.json.render_support_files()?); Ok(files) } + + /// The `TransferTypeConverter` instance a model reference converts through, if + /// the backend that owns the model emits one. Only the JSON backend does, so + /// proto-backed and locally-planned records answer `None` and their operations + /// carry no type info. + fn transfer_type_converter(&self, model_type: &PlannedType) -> Option { + match model_type { + PlannedType::External(ExternalTypeSpec::Json(json_type)) => { + Some(self.json.transfer_type_converter(json_type)) + } + _ => None, + } + } } impl ExternalModelBackend for TypeScriptExternalModels { @@ -453,6 +466,7 @@ impl<'a> ApiPlanner<'a> { }) .unwrap_or_default(), to_wire_expr: input_conversion.to_wire_expr("request"), + transfer_type_converter: self.external_models.transfer_type_converter(input), } }); @@ -532,6 +546,9 @@ impl<'a> ApiPlanner<'a> { output_model_name: operation .output_type() .and_then(|output| self.locally_defined_model_name(output)), + output_transfer_type_converter: operation + .output_type() + .and_then(|output| self.external_models.transfer_type_converter(output)), }) } @@ -554,7 +571,9 @@ impl<'a> ApiPlanner<'a> { .map(|module_path| *module_path == self.api_plan.module_path) .unwrap_or(true) => { - Some(json_type.model_name.clone()) + // Through the backend, so the re-exported name is the resolved one + // (`x-ts-name`) rather than the plan's derived `model_name`. + self.external_models.model_type_annotation(model_type) } _ => None, } @@ -2766,6 +2785,7 @@ struct RenderedOperation<'a> { output_resource_return: Option, output_direct_result: bool, output_model_name: Option, + output_transfer_type_converter: Option, } #[derive(Debug)] @@ -2777,6 +2797,7 @@ struct RenderedOperationInput { annotation: String, api_omitted_fields: Vec, to_wire_expr: String, + transfer_type_converter: Option, } #[derive(Debug)] @@ -3784,7 +3805,7 @@ fn render_cross_module_model_value_imports( for (module_path, names) in &api_plan.data.module_imports { let candidates = names .iter() - .map(|name| format!("{name}Mapper")) + .map(|name| typescript_json::ts_transfer_type_converter_name(name)) .collect::>(); let used_names = used_import_names(source, &candidates); if !used_names.is_empty() { @@ -3853,6 +3874,19 @@ fn render_service_module( &[("nexus", "nexus-rpc"), ("workflow", "@temporalio/workflow")], ); render_typescript_default_type_import_if_used(&mut imports, &body, "Long", "long"); + // Operation type info references converter *values*, so they import alongside + // (and before) the type-only model imports. + render_value_imports( + &mut imports, + "./models", + &used_import_names( + &body, + &external_model_names + .iter() + .map(|name| typescript_json::ts_transfer_type_converter_name(name)) + .collect::>(), + ), + ); render_type_imports( &mut imports, "./models", @@ -3861,6 +3895,12 @@ fn render_service_module( &model_type_names(enums, flags, variants, models, external_model_names), ), ); + render_cross_module_model_value_imports( + &mut imports, + &api_plan.module_path.to_path_buf(), + api_plan, + &body, + ); render_cross_module_model_type_imports( &mut imports, &api_plan.module_path.to_path_buf(), @@ -4911,11 +4951,38 @@ fn render_service_definition(output: &mut String, service: &RenderedService<'_>) output.push('\n'); output.push_str(" >({ name: "); output.push_str(&typescript_string_literal(operation.wire_name)); + // Operation type info carries the model's transfer type converter to the + // protocol integration. A void side has no value to convert, so it stays + // absent rather than carrying an empty `TypeInfo`. + render_operation_type_info( + output, + "inputType", + operation + .input + .as_ref() + .and_then(|input| input.transfer_type_converter.as_deref()), + ); + render_operation_type_info( + output, + "outputType", + operation.output_transfer_type_converter.as_deref(), + ); output.push_str(" }),\n"); } output.push_str("});\n\n"); } +fn render_operation_type_info(output: &mut String, field: &str, converter: Option<&str>) { + let Some(converter) = converter else { + return; + }; + output.push_str(", "); + output.push_str(field); + output.push_str(": { transferTypeConverter: "); + output.push_str(converter); + output.push_str(" }"); +} + fn resource_client_symbol_name(resource: &PlannedResource) -> String { format!( "{}Client", diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index d4367bf9..f28856ae 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -2972,15 +2972,16 @@ fn is_inline_object_shape(schema: &Schema) -> bool { /// `$ref` at it. Every target has to materialize a *type* for such a shape: Go a /// struct (plus a defined type to carry a union's marker method), Java a class /// (to `implement` a union interface), Python a `BaseModel` for Pydantic to -/// select, TypeScript an interface plus the mapper that validates its members — -/// so the shape needs a name; and once it has one, a named definition is exactly -/// what every target already emits. Hoisting is therefore the whole feature: -/// downstream the position holds an ordinary `$ref` and its target an ordinary -/// model, so validation, ref resolution, P15, module exports, and emission all -/// apply unchanged, and the inline form emits byte-identical code to the `$defs` -/// + `$ref` form. See `specs/json-schema/features/properties.md` §"Naming an -/// inline object shape" and `specs/json-schema/features/oneOf.md` §"Object -/// branches — naming the inline shape". +/// select, TypeScript an interface plus the converter that validates its members +/// — so the shape needs a name; and once it has one, a named definition is +/// exactly what every target already emits. Hoisting is therefore the whole +/// feature: downstream the position holds an ordinary `$ref` and its target an +/// ordinary model, so validation, ref resolution, P15, module exports, and +/// emission all apply unchanged, and the inline form emits byte-identical code to +/// the `$defs` + `$ref` form. See +/// `specs/json-schema/features/properties.md` §"Naming an inline object shape" +/// and `specs/json-schema/features/oneOf.md` §"Object branches — naming the +/// inline shape". /// /// The one object left inline is the **free-form** object as a `oneOf` *branch*: /// there it is the union's object kind rather than a value position of its own, so @@ -5473,6 +5474,16 @@ fn type_identifier(language: Language, model_name: &str, schema: &Schema) -> Str .unwrap_or_else(|| model_name.to_string()) } +/// The TypeScript identifier of a model's `TransferTypeConverter` instance, +/// derived from the model's resolved type identifier. This is the single owner of +/// the name: the P15 collision pass enters it into the module namespace here and +/// the TypeScript emitters (model declaration, cross-module value imports, +/// operation `inputType`/`outputType`) ask for it, so the derivation is never +/// spelled twice and the check can never drift from emission. +pub(crate) fn ts_transfer_type_converter_name(type_ident: &str) -> String { + format!("{}TransferTypeConverter", type_ident.to_lower_camel_case()) +} + /// Whether a property schema is a scalar closed value set (`const`/`enum`) that /// synthesizes a Go defined type + value constants / Java value constants. fn schema_closed_values(schema: &Schema) -> Vec { @@ -5796,10 +5807,12 @@ pub(crate) fn build_name_manifest( )?; } } - // TypeScript `DEFAULT_` constants share the module scope; make - // them participate rather than silently coexist (P15). + // TypeScript `DEFAULT_` constants and per-model transfer type + // converters share the module scope; make them participate rather than + // silently coexist (P15). if language == Language::TypeScript { collect_ts_default_constants(module_key, &ns_models, &mut top)?; + collect_ts_transfer_type_converters(module_key, &ns_models, &mut top)?; } } @@ -5817,9 +5830,15 @@ pub(crate) fn build_name_manifest( /// and `ValidationError` live in the models' own package; every other runtime /// symbol is unexported (`addViolations`, `parseSpecInteger`, …) and cannot /// collide with an exported user type. -/// - TypeScript (`src/generator/json/typescript.rs`): `Violation` (interface) -/// and `ValidationError` (class) are imported into every model module; the -/// helper functions (`isPlainObject`, `collect`, …) are `camelCase`. +/// - TypeScript (`src/generator/json/typescript.rs`): nexus-rpc's +/// `TransferTypeConverter` is a bare named import in every model module (the +/// contract each model's converter implements), so a user type of that name is +/// an import-versus-local-declaration conflict. `Violation` (interface) and +/// `ValidationError` (class) reach `models.ts` only through the namespace +/// import `__nexgenDefinitions`, but the package barrel re-exports both from +/// `./definitions` beside `export *` of the model modules, so a user type of +/// either name is silently shadowed out of the package surface (P7). The +/// runtime helper functions (`isPlainObject`, `collect`, …) are `camelCase`. /// - Python (`src/generator/json/python.rs`): the `UpperCamelCase` runtime type /// aliases imported into model modules (`SpecInt`, the materialized temporal /// and base64 field aliases). There is no generated `Violation`/error class — @@ -5831,7 +5850,8 @@ pub(crate) fn build_name_manifest( /// (`TemporalSupport`/`Base64Support` are schema-dependent, so excluded.) fn boilerplate_idents(language: Language) -> &'static [&'static str] { match language { - Language::Go | Language::TypeScript => &["Violation", "ValidationError"], + Language::Go => &["Violation", "ValidationError"], + Language::TypeScript => &["Violation", "ValidationError", "TransferTypeConverter"], Language::Python => &[ "SpecInt", "DateTimeField", @@ -6079,6 +6099,28 @@ fn collect_ts_default_constants( Ok(()) } +/// TypeScript per-model `TransferTypeConverter` instances (module scope). The +/// identifier is derived from the model's type identifier +/// ([`ts_transfer_type_converter_name`]), and lower-camel-casing is not +/// injective over the distinct `UpperCamelCase` type names — `HTTPError` and +/// `HttpError` both derive `httpErrorTransferTypeConverter` — so the derived +/// name has to enter the shared module namespace too, or two models emit the +/// same `export const` (P15). +fn collect_ts_transfer_type_converters( + module_key: &str, + models: &[NsModel], + top: &mut Namespace, +) -> Result<()> { + for model in models.iter().filter(|model| model.module_key == module_key) { + top.insert( + Language::TypeScript, + ts_transfer_type_converter_name(&model.type_ident), + format!("type `{}` transfer type converter", model.full_name), + )?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -9956,6 +9998,98 @@ $defs: parse_for(Language::Python, input).expect("Python has no Violation boilerplate"); } + #[test] + fn rejects_type_colliding_with_typescript_transfer_type_converter() { + // Every TS model module imports nexus-rpc's `TransferTypeConverter` for + // the contract its converter implements, so a `$defs` type of that name + // conflicts with the import. + let input = r##" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + c: { $ref: "#/$defs/TransferTypeConverter" } +$defs: + TransferTypeConverter: + type: object + properties: { a: { type: string } } +"##; + let error = reject_for(Language::TypeScript, input); + assert!( + error.contains("collision") && error.contains("TransferTypeConverter"), + "{error}" + ); + // The other targets import no such symbol, so the same schema is accepted. + parse_for(Language::Go, input).expect("Go has no TransferTypeConverter boilerplate"); + parse_for(Language::Java, input).expect("Java has no TransferTypeConverter boilerplate"); + } + + #[test] + fn rejects_typescript_transfer_type_converters_that_case_fold_together() { + // The converter identifier is derived by lower-camel-casing the resolved + // type name, which is not injective over the distinct type names P15 + // guarantees: both types below keep their verbatim names through an + // override, yet derive the same `httpErrorTransferTypeConverter` — one + // `export const` emitted twice. The derived name participates in the + // pass, so this rejects with a fix-it instead. + let input = r##" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +required: [a, b] +properties: + a: { $ref: "#/$defs/HTTPError" } + b: { $ref: "#/$defs/HttpError" } +$defs: + HTTPError: + type: object + x-ts-name: HTTPError + x-go-name: HTTPError + x-py-name: HTTPError + x-java-name: HTTPError + properties: { m: { type: string } } + HttpError: + type: object + properties: { n: { type: string } } +"##; + let error = reject_for(Language::TypeScript, input); + assert!( + error.contains("collision") && error.contains("httpErrorTransferTypeConverter"), + "{error}" + ); + // The other targets derive no value identifier from a type name, so the + // two distinct type names are all they have to keep apart. + parse_for(Language::Go, input).expect("Go derives no converter identifier"); + parse_for(Language::Python, input).expect("Python derives no converter identifier"); + parse_for(Language::Java, input).expect("Java derives no converter identifier"); + } + + #[test] + fn rejects_service_name_colliding_with_a_transfer_type_converter() { + // A service's TypeScript identifier shares the module scope with the + // derived converter identifiers, so an override that lands on one is a + // P15 collision (TS2440 plus a temporal-dead-zone `ReferenceError` if + // emitted). + let input = r##" +$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Thing: + fqn: example.t.v1.Thing + x-ts-name: getInputTransferTypeConverter + operations: + get: + input: { $ref: "#/$defs/GetInput" } +$defs: + GetInput: + type: object + properties: { id: { type: string } } +"##; + let error = reject_for(Language::TypeScript, input); + assert!( + error.contains("service `Thing`") && error.contains("getInputTransferTypeConverter"), + "{error}" + ); + } + #[test] fn rejects_type_colliding_with_java_runtime_boilerplate() { // Java emits `ValidationException` as an always-present public runtime diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a1d8c0be..f2a1b02b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13,7 +13,10 @@ pub use json_schema::{ load_api_spec_tree_from_json_schema_for_language_with_inputs, }; -pub(crate) use json_schema::{ManifestModel, ManifestService, NameManifest, build_name_manifest}; +pub(crate) use json_schema::{ + ManifestModel, ManifestService, NameManifest, build_name_manifest, + ts_transfer_type_converter_name, +}; pub use wit::{load_api_spec_from_wit_for_language_with_inputs, write_prepared_wit_directory}; pub(crate) use wit::{ diff --git a/tests/generate_typescript.rs b/tests/generate_typescript.rs index f440a74d..29c8a0d5 100644 --- a/tests/generate_typescript.rs +++ b/tests/generate_typescript.rs @@ -85,6 +85,47 @@ properties: - { type: string, enum: [auto, manual] } "#; +/// A service whose two operations are each one-sided: one declares only an +/// `input`, the other only an `output`. +const ONE_SIDED_OPERATION_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Jobs: + fqn: example.jobs.v1.Jobs + operations: + accept: + input: { $ref: "#/$defs/Job" } + produce: + output: { $ref: "#/$defs/Job" } +$defs: + Job: + type: object + properties: + id: { type: string } +"##; + +/// An operation whose output type carries an `x-ts-name` override. +const OPERATION_IO_TS_NAME_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Pages: + fqn: example.pages.v1.Pages + operations: + get: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "#/$defs/Page" } +$defs: + GetInput: + type: object + properties: + id: { type: string } + Page: + type: object + x-ts-name: RenamedPage + properties: + title: { type: string } +"##; + fn project_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) } @@ -383,15 +424,52 @@ fn typescript_json_example_generation_matches_checked_in_output() { assert!(all.contains("export interface Extras {")); assert!(all.contains("additionalProperties: Record;")); // A tagged union whose branches are written inline: each branch names - // itself with `x-ts-name` and is emitted as an interface + mapper. + // itself with `x-ts-name` and is emitted as an interface + converter. assert!(all.contains("export type Note = TextNote | LinkNote;")); assert!(all.contains("export interface TextNote {")); - assert!(all.contains("export class LinkNoteMapper {")); + assert!(all.contains( + "export const linkNoteTransferTypeConverter =\n new (class implements TransferTypeConverter {" + )); // The lone inline object branch of a property union derives its name // from the union it belongs to. assert!(all.contains("detail?: ShowcaseDetailObject | string;")); assert!(all.contains("export interface ShowcaseDetailObject {")); assert!(all.contains("out.detail = serializeShowcaseDetail(value.detail);")); + // Each operation carries its models' converters as operation type + // info; the `x-ts-name` override flows into the converter identifier. + assert!(all.contains( + "inputType: { transferTypeConverter: getShowcaseInputTransferTypeConverter }," + )); + assert!( + all.contains( + "outputType: { transferTypeConverter: showcaseTransferTypeConverter }," + ) + ); + assert!(all.contains("export const contactTsTransferTypeConverter =")); + } + if example_id == "chat" { + let services = rendered + .get(std::path::Path::new("services.ts")) + .expect("chat services module"); + // A void side has no value to convert, so it carries no type info. + assert!(services.contains("ping: nexus.operation({ name: \"Ping\" }),")); + assert!(services.contains( + "inputType: { transferTypeConverter: sendMessageInputTransferTypeConverter }," + )); + } + if example_id == "kb" { + // A cross-module I/O model's converter imports as a value from the + // module that declares it, alongside the type-only model import. + let services = rendered + .get(std::path::Path::new("kb/services.ts")) + .expect("kb services module"); + assert!(services.contains( + "import { blockTransferTypeConverter } from \"../content/block/models\";" + )); + assert!( + services + .contains("outputType: { transferTypeConverter: pageTransferTypeConverter },") + ); } fs::remove_dir_all(output_path).unwrap(); } @@ -728,9 +806,9 @@ fn typescript_renders_required_fields_and_custom_message_types() { } /// An inline **structured** object `oneOf` branch on a property: the branch is -/// named `Object` and emitted as an interface with its own mapper, and the -/// union's serialize side routes through it (the in-memory `additionalProperties` -/// member must not reach the wire). +/// named `Object` and emitted as an interface with its own converter, and +/// the union's serialize side routes through it (the in-memory +/// `additionalProperties` member must not reach the wire). /// See `specs/json-schema/features/oneOf.md` ("Object branches"). #[test] fn typescript_json_names_inline_object_union_branch() { @@ -756,9 +834,13 @@ fn typescript_json_names_inline_object_union_branch() { assert!(rendered.contains("payload?: DetailPayloadObject | string;")); assert!(rendered.contains("export interface DetailPayloadObject {")); - assert!(rendered.contains("export class DetailPayloadObjectMapper {")); - // Parse and serialize both route the object token through the branch mapper. - assert!(rendered.contains("new DetailPayloadObjectMapper().fromIntermediate(raw.payload)")); + assert!(rendered.contains( + "export const detailPayloadObjectTransferTypeConverter = new class implements TransferTypeConverter {" + )); + // Parse and serialize both route the object token through the branch converter. + assert!( + rendered.contains("detailPayloadObjectTransferTypeConverter.fromTransferType(raw.payload)") + ); assert!(rendered.contains( "function serializeDetailPayload(value: DetailPayloadObject | string): unknown {" )); @@ -767,8 +849,8 @@ fn typescript_json_names_inline_object_union_branch() { } /// Every constraint a **non-object** branch declares is checked once the token -/// narrows to it, on both sides of the mapper (P12). A `const`/`enum` branch also -/// narrows the member *type* to its literal set, without which the narrowed +/// narrows to it, on both sides of the converter (P12). A `const`/`enum` branch +/// also narrows the member *type* to its literal set, without which the narrowed /// assignment would not even typecheck. /// See `specs/json-schema/features/oneOf.md` ("Validator mapping"). #[test] @@ -812,10 +894,10 @@ fn typescript_json_validates_non_object_union_branch_constraints() { } /// A union in an element position: the loader names it, so TypeScript emits an -/// ordinary union alias plus mapper and runs it per element/member — including -/// on the serialize side, where an element model's catch-all bag would otherwise -/// reach the wire. A nullable element parenthesizes (`(T | null)[]`), which -/// `T | null[]` would silently misread. +/// ordinary union alias plus converter and runs it per element/member — +/// including on the serialize side, where an element model's catch-all bag would +/// otherwise reach the wire. A nullable element parenthesizes (`(T | null)[]`), +/// which `T | null[]` would silently misread. /// See `specs/json-schema/features/oneOf.md` ("Unions in element positions"). #[test] fn typescript_json_maps_element_position_unions() { @@ -841,12 +923,90 @@ fn typescript_json_maps_element_position_unions() { assert!(rendered.contains("export type BagSegmentsItem = string | number;")); assert!(rendered.contains("segments?: BagSegmentsItem[];")); - assert!(rendered.contains("new BagSegmentsItemMapper().fromIntermediate(element)")); - assert!(rendered.contains("new ChoiceMapper().fromIntermediate(element)")); - // A map member runs the member mapper in both directions. - assert!(rendered.contains("new EntriesValueMapper().fromIntermediate(raw[key])")); - assert!(rendered.contains("out[key] = new EntriesValueMapper().toIntermediate(entry);")); + assert!(rendered.contains("bagSegmentsItemTransferTypeConverter.fromTransferType(element)")); + assert!(rendered.contains("choiceTransferTypeConverter.fromTransferType(element)")); + // A map member runs the member converter in both directions. + assert!(rendered.contains("entriesValueTransferTypeConverter.fromTransferType(raw[key])")); + assert!(rendered.contains("out[key] = entriesValueTransferTypeConverter.toTransferType(entry);")); // Element nullability is the element's own concern, and parenthesized. assert!(rendered.contains("slots?: (string | null)[];")); fs::remove_dir_all(temp_dir).unwrap(); } + +/// A one-sided operation: the non-void side carries its converter as operation +/// type info, the void side carries no field at all (there is no value to +/// convert, so an empty `TypeInfo` would assert a conversion that does not +/// exist). See `specs/json-schema/services.md` ("TypeScript operation type +/// info"); the checked-in samples only cover void-on-both-sides. +#[test] +fn typescript_json_one_sided_operation_type_info() { + let temp_dir = unique_output_path("ts-json-one-sided-type-info"); + fs::create_dir_all(&temp_dir).unwrap(); + let input_path = temp_dir.join("jobs.nexusrpc.yaml"); + fs::write(&input_path, ONE_SIDED_OPERATION_SCHEMA).unwrap(); + let output_path = temp_dir.join("jobs"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_path], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + let rendered = fs::read_to_string(output_path.join("services.ts")).unwrap(); + + // Input present, output omitted: `inputType` only. + assert!(rendered.contains( + " >({ name: \"Accept\", inputType: { transferTypeConverter: jobTransferTypeConverter } })," + )); + // The mirror: output present, input omitted. + assert!(rendered.contains( + " >({ name: \"Produce\", outputType: { transferTypeConverter: jobTransferTypeConverter } })," + )); + fs::remove_dir_all(temp_dir).unwrap(); +} + +/// An `x-ts-name` override on an operation's I/O type moves every emitted +/// reference with the type: the operation generic, the model/converter imports, +/// and the converter named in the operation type info (the identifier is derived +/// from the *resolved* type name). +#[test] +fn typescript_json_operation_type_info_follows_ts_name_override() { + let temp_dir = unique_output_path("ts-json-type-info-override"); + fs::create_dir_all(&temp_dir).unwrap(); + let input_path = temp_dir.join("pages.nexusrpc.yaml"); + fs::write(&input_path, OPERATION_IO_TS_NAME_SCHEMA).unwrap(); + let output_path = temp_dir.join("pages"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_path], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + let models = fs::read_to_string(output_path.join("models.ts")).unwrap(); + let services = fs::read_to_string(output_path.join("services.ts")).unwrap(); + + assert!(models.contains("export interface RenamedPage {")); + assert!(models.contains("export const renamedPageTransferTypeConverter = new class")); + assert!(services.contains("import { getInputTransferTypeConverter, renamedPageTransferTypeConverter } from './models';")); + assert!(services.contains("import type { GetInput, RenamedPage } from './models';")); + assert!(services.contains(" RenamedPage\n")); + assert!( + services.contains( + "outputType: { transferTypeConverter: renamedPageTransferTypeConverter } })," + ) + ); + fs::remove_dir_all(temp_dir).unwrap(); +} From 9225f1b959ad5ffa50287141f881c579e6445165 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 10:15:38 -0700 Subject: [PATCH 03/10] Resolve emitted JSON model names across input files A model's `x--name` override was invisible to every other input file that referenced it. Each leaf built its name manifest from its own declarations only, so a consuming module emitted the pre-override identifier in all four languages: a dangling operation generic, model import and TypeScript converter import, a dangling field type for a cross-file `$ref` property, and -- in Go's flat package -- a reference to a type name that is never declared. `EmittedNameResolutionPass` now builds one manifest over the whole tree and rewrites every emitted name from it: the leaf's declarations, every reference (routed through `ApiSpec::map_names`, so operation I/O and record field types are reached too), and the names `module_imports` is rendered from. Collision scoping is untouched -- `build_name_manifest` groups the models it is handed by module key, so a foreign model never joins a local module's namespace (P15). Cross-module `$ref`s inside a model's schema are resolved by the backends, which see one leaf at a time, so the pass records the foreign identifiers on `PlannedSpecData::cross_module_model_names` and each backend seeds its `$ref` registry with them. That replaces the Go backend's approximation of the same map, which keyed entries by the pre-override name. Generated samples are byte-identical: no sample declares a cross-file name override. --- CHANGELOG.md | 10 +- .../json_schema/api/kb/content/page/models.ts | 257 +- .../api/kb/tree/category/models.ts | 230 +- .../json_schema/api/showcase/models.ts | 6288 +++++++++-------- architecture.md | 5 +- samples/typescript/kb/content/page/models.ts | 259 +- samples/typescript/kb/tree/category/models.ts | 228 +- samples/typescript/showcase/models.ts | 6288 +++++++++-------- .../tests/json-schema-showcase.test.ts | 59 +- specs/json-schema/features/ref.md | 5 + src/generator/json_schema/go.rs | 20 +- src/generator/json_schema/mod.rs | 23 + src/generator/json_schema/python.rs | 2 + src/generator/json_schema/typescript.rs | 2 + src/lib.rs | 4 +- src/planning/emitted_names.rs | 284 +- src/planning/mod.rs | 6 + src/planning/type_planning.rs | 3 + tests/generate_go.rs | 100 + tests/generate_java.rs | 118 + tests/generate_python.rs | 106 + tests/generate_typescript.rs | 115 +- 22 files changed, 7647 insertions(+), 6765 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd7a1ae..b7f98fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,11 +235,19 @@ array"` at runtime, though `items.md` accepts them. Both now decode elementwise, - TypeScript: An array of models or unions serialized its elements verbatim, so an element's in-memory `additionalProperties` bag reached the wire as a literal member (and an element's temporal/bytes members were never re-encoded). Each - element now re-serializes through its own mapper, as does a typed map's member. + element now re-serializes through its own converter, as does a typed map's + member. - Go: A schema `description` ending a sentence with a package-like word ("one at a time.") added that package to the import block, and an unused import is a Go compile error. Package use is now read off the emitted code, not the doc comments. +- JSON Schema: An `x--name` override on a model was ignored by every + *other* input file that referenced it, in all four languages. The consuming + module emitted the pre-override identifier — a dangling operation generic, + model import, and (in TypeScript) converter import; a dangling field type for a + cross-file `$ref` property; and, in Go's flat package, a reference to a type + name that is never declared. Emitted identifiers are now resolved once over the + whole input closure, so a cross-file reference names the overridden type. - JSON Schema: A `oneOf` with an inline object branch generated uncompilable Go (a marker method on an undeclared `Object` type) and uncompilable TypeScript (a converter named after the anonymous `Record` diff --git a/advanced/samples/typescript/json_schema/api/kb/content/page/models.ts b/advanced/samples/typescript/json_schema/api/kb/content/page/models.ts index ec0187b2..d1c928ef 100644 --- a/advanced/samples/typescript/json_schema/api/kb/content/page/models.ts +++ b/advanced/samples/typescript/json_schema/api/kb/content/page/models.ts @@ -1,7 +1,8 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; -import { BlockMapper } from "../block/models"; +import { blockTransferTypeConverter } from "../block/models"; import type { Block } from "../block/models"; export function requiredField( @@ -36,153 +37,155 @@ export interface PageMeta { wordCount?: number; } -export class PageMapper { - public fromIntermediate(raw: unknown): Page { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let pageId: string = undefined as unknown as string; - if (raw.pageId === undefined || raw.pageId === null) { - violations.push({ path: "pageId", reason: "required" }); - } else { - if (typeof raw.pageId !== "string") { - violations.push({ path: "pageId", reason: "expected string" }); - } else { - pageId = raw.pageId; +export const pageTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Page { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let title: string = undefined as unknown as string; - if (raw.title === undefined || raw.title === null) { - violations.push({ path: "title", reason: "required" }); - } else { - if (typeof raw.title !== "string") { - violations.push({ path: "title", reason: "expected string" }); + let pageId: string = undefined as unknown as string; + if (raw.pageId === undefined || raw.pageId === null) { + violations.push({ path: "pageId", reason: "required" }); } else { - title = raw.title; + if (typeof raw.pageId !== "string") { + violations.push({ path: "pageId", reason: "expected string" }); + } else { + pageId = raw.pageId; + } } - } - let meta: PageMeta = undefined as unknown as PageMeta; - if (raw.meta === undefined || raw.meta === null) { - violations.push({ path: "meta", reason: "required" }); - } else { - try { - meta = new PageMetaMapper().fromIntermediate(raw.meta); - } catch (error) { - __nexgenDefinitions.collect(violations, "meta", error); + let title: string = undefined as unknown as string; + if (raw.title === undefined || raw.title === null) { + violations.push({ path: "title", reason: "required" }); + } else { + if (typeof raw.title !== "string") { + violations.push({ path: "title", reason: "expected string" }); + } else { + title = raw.title; + } } - } - let blocks: Block[] | undefined = undefined as unknown as Block[] | undefined; - if (raw.blocks === null) { - violations.push({ path: "blocks", reason: "explicit null not allowed" }); - } else if (raw.blocks !== undefined) { - if (!Array.isArray(raw.blocks)) { - violations.push({ path: "blocks", reason: "expected array" }); + let meta: PageMeta = undefined as unknown as PageMeta; + if (raw.meta === undefined || raw.meta === null) { + violations.push({ path: "meta", reason: "required" }); } else { - blocks = []; - raw.blocks.forEach((element: unknown, index: number) => { - let item: Block = undefined as unknown as Block; - try { - item = new BlockMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `blocks[${index}]`, error); - } - if (item !== undefined) { - blocks!.push(item); - } - }); + try { + meta = pageMetaTransferTypeConverter.fromTransferType(raw.meta); + } catch (error) { + __nexgenDefinitions.collect(violations, "meta", error); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "pageId" && key !== "title" && key !== "meta" && key !== "blocks") { - violations.push({ path: key, reason: "unknown field" }); + let blocks: Block[] | undefined = undefined as unknown as Block[] | undefined; + if (raw.blocks === null) { + violations.push({ path: "blocks", reason: "explicit null not allowed" }); + } else if (raw.blocks !== undefined) { + if (!Array.isArray(raw.blocks)) { + violations.push({ path: "blocks", reason: "expected array" }); + } else { + blocks = []; + raw.blocks.forEach((element: unknown, index: number) => { + let item: Block = undefined as unknown as Block; + try { + item = blockTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `blocks[${index}]`, error); + } + if (item !== undefined) { + blocks!.push(item); + } + }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Page = { pageId, title, meta }; - if (blocks !== undefined) { - out.blocks = blocks; - } - return out; - } - - public toIntermediate(value: Page): unknown { - const out: Record = {}; - out.pageId = value.pageId; - out.title = value.title; - out.meta = new PageMetaMapper().toIntermediate(value.meta); - if (value.blocks !== undefined) { - out.blocks = value.blocks.map((element) => - new BlockMapper().toIntermediate(element), - ); - } - return out; - } -} + for (const key of Object.keys(raw)) { + if (key !== "pageId" && key !== "title" && key !== "meta" && key !== "blocks") { + violations.push({ path: key, reason: "unknown field" }); + } + } -export class PageMetaMapper { - public fromIntermediate(raw: unknown): PageMeta { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Page = { pageId, title, meta }; + if (blocks !== undefined) { + out.blocks = blocks; + } + return out; + } + + public toTransferType(value: Page): unknown { + const out: Record = {}; + out.pageId = value.pageId; + out.title = value.title; + out.meta = pageMetaTransferTypeConverter.toTransferType(value.meta); + if (value.blocks !== undefined) { + out.blocks = value.blocks.map((element) => + blockTransferTypeConverter.toTransferType(element), + ); + } + return out; + } + })(); + +export const pageMetaTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): PageMeta { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let author: string = undefined as unknown as string; - if (raw.author === undefined || raw.author === null) { - violations.push({ path: "author", reason: "required" }); - } else { - if (typeof raw.author !== "string") { - violations.push({ path: "author", reason: "expected string" }); + let author: string = undefined as unknown as string; + if (raw.author === undefined || raw.author === null) { + violations.push({ path: "author", reason: "required" }); } else { - author = raw.author; + if (typeof raw.author !== "string") { + violations.push({ path: "author", reason: "expected string" }); + } else { + author = raw.author; + } } - } - let wordCount: number | undefined = undefined as unknown as number | undefined; - if (raw.wordCount === null) { - violations.push({ path: "wordCount", reason: "explicit null not allowed" }); - } else if (raw.wordCount !== undefined) { - if (typeof raw.wordCount !== "number" || !Number.isSafeInteger(raw.wordCount)) { - violations.push({ path: "wordCount", reason: "expected integer" }); - } else { - wordCount = raw.wordCount; + let wordCount: number | undefined = undefined as unknown as number | undefined; + if (raw.wordCount === null) { + violations.push({ path: "wordCount", reason: "explicit null not allowed" }); + } else if (raw.wordCount !== undefined) { + if (typeof raw.wordCount !== "number" || !Number.isSafeInteger(raw.wordCount)) { + violations.push({ path: "wordCount", reason: "expected integer" }); + } else { + wordCount = raw.wordCount; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "author" && key !== "wordCount") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "author" && key !== "wordCount") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: PageMeta = { author }; - if (wordCount !== undefined) { - out.wordCount = wordCount; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: PageMeta = { author }; + if (wordCount !== undefined) { + out.wordCount = wordCount; + } + return out; } - return out; - } - public toIntermediate(value: PageMeta): unknown { - const out: Record = {}; - out.author = value.author; - if (value.wordCount !== undefined) { - out.wordCount = value.wordCount; + public toTransferType(value: PageMeta): unknown { + const out: Record = {}; + out.author = value.author; + if (value.wordCount !== undefined) { + out.wordCount = value.wordCount; + } + return out; } - return out; - } -} + })(); diff --git a/advanced/samples/typescript/json_schema/api/kb/tree/category/models.ts b/advanced/samples/typescript/json_schema/api/kb/tree/category/models.ts index 4a2a7b60..bf8f188e 100644 --- a/advanced/samples/typescript/json_schema/api/kb/tree/category/models.ts +++ b/advanced/samples/typescript/json_schema/api/kb/tree/category/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; export function requiredField( @@ -32,137 +33,142 @@ export interface Palette { swatches: string[]; } -export class CategoryMapper { - public fromIntermediate(raw: unknown): Category { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); - } else { - id = raw.id; +export const categoryTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Category { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - name = raw.name; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let children: Category[] | undefined = undefined as unknown as - | Category[] - | undefined; - if (raw.children === null) { - violations.push({ path: "children", reason: "explicit null not allowed" }); - } else if (raw.children !== undefined) { - if (!Array.isArray(raw.children)) { - violations.push({ path: "children", reason: "expected array" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - children = []; - raw.children.forEach((element: unknown, index: number) => { - let item: Category = undefined as unknown as Category; - try { - item = new CategoryMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `children[${index}]`, error); - } - if (item !== undefined) { - children!.push(item); - } - }); + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "id" && key !== "name" && key !== "children") { - violations.push({ path: key, reason: "unknown field" }); + let children: Category[] | undefined = undefined as unknown as + | Category[] + | undefined; + if (raw.children === null) { + violations.push({ path: "children", reason: "explicit null not allowed" }); + } else if (raw.children !== undefined) { + if (!Array.isArray(raw.children)) { + violations.push({ path: "children", reason: "expected array" }); + } else { + children = []; + raw.children.forEach((element: unknown, index: number) => { + let item: Category = undefined as unknown as Category; + try { + item = categoryTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `children[${index}]`, error); + } + if (item !== undefined) { + children!.push(item); + } + }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Category = { id, name }; - if (children !== undefined) { - out.children = children; - } - return out; - } + for (const key of Object.keys(raw)) { + if (key !== "id" && key !== "name" && key !== "children") { + violations.push({ path: key, reason: "unknown field" }); + } + } - public toIntermediate(value: Category): unknown { - const out: Record = {}; - out.id = value.id; - out.name = value.name; - if (value.children !== undefined) { - out.children = value.children.map((element) => - new CategoryMapper().toIntermediate(element), - ); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Category = { id, name }; + if (children !== undefined) { + out.children = children; + } + return out; } - return out; - } -} -export class PaletteMapper { - public fromIntermediate(raw: unknown): Palette { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); + public toTransferType(value: Category): unknown { + const out: Record = {}; + out.id = value.id; + out.name = value.name; + if (value.children !== undefined) { + out.children = value.children.map((element) => + categoryTransferTypeConverter.toTransferType(element), + ); + } + return out; } + })(); + +export const paletteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Palette { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let swatches: string[] = undefined as unknown as string[]; - if (raw.swatches === undefined || raw.swatches === null) { - violations.push({ path: "swatches", reason: "required" }); - } else { - if (!Array.isArray(raw.swatches)) { - violations.push({ path: "swatches", reason: "expected array" }); + let swatches: string[] = undefined as unknown as string[]; + if (raw.swatches === undefined || raw.swatches === null) { + violations.push({ path: "swatches", reason: "required" }); } else { - swatches = []; - raw.swatches.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `swatches[${index}]`, reason: "expected element" }); - } else { - item = element; - } - if (item !== undefined) { - swatches!.push(item); - } - }); + if (!Array.isArray(raw.swatches)) { + violations.push({ path: "swatches", reason: "expected array" }); + } else { + swatches = []; + raw.swatches.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ + path: `swatches[${index}]`, + reason: "expected element", + }); + } else { + item = element; + } + if (item !== undefined) { + swatches!.push(item); + } + }); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "swatches") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "swatches") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Palette = { swatches }; + return out; } - const out: Palette = { swatches }; - return out; - } - public toIntermediate(value: Palette): unknown { - const out: Record = {}; - out.swatches = value.swatches; - return out; - } -} + public toTransferType(value: Palette): unknown { + const out: Record = {}; + out.swatches = value.swatches; + return out; + } + })(); diff --git a/advanced/samples/typescript/json_schema/api/showcase/models.ts b/advanced/samples/typescript/json_schema/api/showcase/models.ts index d0ca2c1d..a2ad18b3 100644 --- a/advanced/samples/typescript/json_schema/api/showcase/models.ts +++ b/advanced/samples/typescript/json_schema/api/showcase/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; export function requiredField( @@ -465,7 +466,7 @@ export interface WidgetBase { function serializeShowcaseDetail(value: ShowcaseDetailObject | string): unknown { if (__nexgenDefinitions.isPlainObject(value)) { - return new ShowcaseDetailObjectMapper().toIntermediate( + return showcaseDetailObjectTransferTypeConverter.toTransferType( value as unknown as ShowcaseDetailObject, ); } @@ -479,10 +480,10 @@ function serializeShowcaseDetail(value: ShowcaseDetailObject | string): unknown function serializeShowcaseShapeOrName(value: Circle | Square | string): unknown { if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); + return circleTransferTypeConverter.toTransferType(value as Circle); } if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + return squareTransferTypeConverter.toTransferType(value as Square); } if (typeof value === "string") { return value; @@ -494,3684 +495,3767 @@ function serializeShowcaseShapeOrName(value: Circle | Square | string): unknown const ADDRESS_DECLARED = new Set(["street", "city", "zip"]); -export class AddressMapper { - public fromIntermediate(raw: unknown): Address { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const addressTransferTypeConverter = + new (class implements TransferTypeConverter
{ + public fromTransferType(raw: unknown): Address { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let street: string = undefined as unknown as string; - if (raw.street === undefined || raw.street === null) { - violations.push({ path: "street", reason: "required" }); - } else { - if (typeof raw.street !== "string") { - violations.push({ path: "street", reason: "expected string" }); + let street: string = undefined as unknown as string; + if (raw.street === undefined || raw.street === null) { + violations.push({ path: "street", reason: "required" }); } else { - street = raw.street; + if (typeof raw.street !== "string") { + violations.push({ path: "street", reason: "expected string" }); + } else { + street = raw.street; + } } - } - let city: string | undefined = undefined as unknown as string | undefined; - if (raw.city === null) { - violations.push({ path: "city", reason: "explicit null not allowed" }); - } else if (raw.city !== undefined) { - if (typeof raw.city !== "string") { - violations.push({ path: "city", reason: "expected string" }); - } else { - city = raw.city; + let city: string | undefined = undefined as unknown as string | undefined; + if (raw.city === null) { + violations.push({ path: "city", reason: "explicit null not allowed" }); + } else if (raw.city !== undefined) { + if (typeof raw.city !== "string") { + violations.push({ path: "city", reason: "expected string" }); + } else { + city = raw.city; + } } - } - let zip: number | undefined = undefined as unknown as number | undefined; - if (raw.zip === null) { - violations.push({ path: "zip", reason: "explicit null not allowed" }); - } else if (raw.zip !== undefined) { - if (typeof raw.zip !== "number" || !Number.isSafeInteger(raw.zip)) { - violations.push({ path: "zip", reason: "expected integer" }); - } else { - zip = raw.zip; + let zip: number | undefined = undefined as unknown as number | undefined; + if (raw.zip === null) { + violations.push({ path: "zip", reason: "explicit null not allowed" }); + } else if (raw.zip !== undefined) { + if (typeof raw.zip !== "number" || !Number.isSafeInteger(raw.zip)) { + violations.push({ path: "zip", reason: "expected integer" }); + } else { + zip = raw.zip; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!ADDRESS_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!ADDRESS_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Address = { street, additionalProperties }; - if (city !== undefined) { - out.city = city; - } - if (zip !== undefined) { - out.zip = zip; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Address = { street, additionalProperties }; + if (city !== undefined) { + out.city = city; + } + if (zip !== undefined) { + out.zip = zip; + } + return out; } - return out; - } - public toIntermediate(value: Address): unknown { - const out: Record = {}; - out.street = value.street; - if (value.city !== undefined) { - out.city = value.city; - } - if (value.zip !== undefined) { - out.zip = value.zip; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; + public toTransferType(value: Address): unknown { + const out: Record = {}; + out.street = value.street; + if (value.city !== undefined) { + out.city = value.city; + } + if (value.zip !== undefined) { + out.zip = value.zip; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - return out; - } -} + })(); -export class AttributesMapper { - public fromIntermediate(raw: unknown): Attributes { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const attributesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Attributes { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - if (keys.length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${keys.length}`, - }); - } - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - for (const key of keys) { - if ([...key].length > 8) { + const keys = Object.keys(raw); + if (keys.length < 1) { violations.push({ - path: key, - reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + path: "", + reason: `must have at least 1 properties, got ${keys.length}`, }); } - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; + if (keys.length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${keys.length}`, + }); + } + for (const key of keys) { + if ([...key].length > 8) { + violations.push({ + path: key, + reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + }); + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Attributes): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${keys.length}`, - }); - } - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - for (const key of keys) { - if ([...key].length > 8) { + public toTransferType(value: Attributes): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length < 1) { violations.push({ - path: key, - reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + path: "", + reason: `must have at least 1 properties, got ${keys.length}`, }); } + if (keys.length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${keys.length}`, + }); + } + for (const key of keys) { + if ([...key].length > 8) { + violations.push({ + path: key, + reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + }); + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + })(); -export class ChoicesMapper { - public fromIntermediate(raw: unknown): Choices { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const choicesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Choices { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: ChoicesValue | undefined = undefined; - try { - entry = new ChoicesValueMapper().fromIntermediate(raw[key]); - } catch (error) { - __nexgenDefinitions.collect(violations, key, error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: ChoicesValue | undefined = undefined; + try { + entry = choicesValueTransferTypeConverter.fromTransferType(raw[key]); + } catch (error) { + __nexgenDefinitions.collect(violations, key, error); + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Choices): unknown { - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = new ChoicesValueMapper().toIntermediate(entry); + public toTransferType(value: Choices): unknown { + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = choicesValueTransferTypeConverter.toTransferType(entry); + } + return out; } - return out; - } -} + })(); -export class ChoicesValueMapper { - public fromIntermediate(raw: unknown): ChoicesValue { - const violations: __nexgenDefinitions.Violation[] = []; - let out: ChoicesValue = undefined as unknown as ChoicesValue; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "circle": - try { - out = new CircleMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - case "square": - try { - out = new SquareMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, - }); +export const choicesValueTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ChoicesValue { + const violations: __nexgenDefinitions.Violation[] = []; + let out: ChoicesValue = undefined as unknown as ChoicesValue; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "circle": + try { + out = circleTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "square": + try { + out = squareTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, + }); + } + } else { + violations.push({ path: "", reason: "expected one of: Circle, Square" }); } - } else { - violations.push({ path: "", reason: "expected one of: Circle, Square" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } - public toIntermediate(value: ChoicesValue): unknown { - if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); - } - if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + public toTransferType(value: ChoicesValue): unknown { + if ((value as unknown as Record)["kind"] === "circle") { + return circleTransferTypeConverter.toTransferType(value as Circle); + } + if ((value as unknown as Record)["kind"] === "square") { + return squareTransferTypeConverter.toTransferType(value as Square); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: Circle, Square" }, + ]); } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: Circle, Square" }, - ]); - } -} + })(); const CIRCLE_DECLARED = new Set(["kind", "radius"]); -export class CircleMapper { - public fromIntermediate(raw: unknown): Circle { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const circleTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Circle { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "circle" = undefined as unknown as "circle"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== CIRCLE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "circle"` }); + let kind: "circle" = undefined as unknown as "circle"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "circle"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== CIRCLE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "circle"` }); + } else { + kind = raw.kind as "circle"; + } } - } - let radius: number = undefined as unknown as number; - if (raw.radius === undefined || raw.radius === null) { - violations.push({ path: "radius", reason: "required" }); - } else { - if (typeof raw.radius !== "number") { - violations.push({ path: "radius", reason: "expected number" }); + let radius: number = undefined as unknown as number; + if (raw.radius === undefined || raw.radius === null) { + violations.push({ path: "radius", reason: "required" }); } else { - radius = raw.radius; + if (typeof raw.radius !== "number") { + violations.push({ path: "radius", reason: "expected number" }); + } else { + radius = raw.radius; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!CIRCLE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!CIRCLE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Circle = { kind, radius, additionalProperties }; + return out; } - const out: Circle = { kind, radius, additionalProperties }; - return out; - } - public toIntermediate(value: Circle): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "circle") { - violations.push({ path: "kind", reason: `must equal "circle"` }); - } - out.kind = value.kind; - out.radius = value.radius; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Circle): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "circle") { + violations.push({ path: "kind", reason: `must equal "circle"` }); + } + out.kind = value.kind; + out.radius = value.radius; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const CONTACT_TS_DECLARED = new Set(["email", "shippingStreet", "shippingZip"]); -export class ContactTsMapper { - public fromIntermediate(raw: unknown): ContactTs { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let email: string | undefined = undefined as unknown as string | undefined; - if (raw.email === null) { - violations.push({ path: "email", reason: "explicit null not allowed" }); - } else if (raw.email !== undefined) { - if (typeof raw.email !== "string") { - violations.push({ path: "email", reason: "expected string" }); - } else { - email = raw.email; +export const contactTsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ContactTs { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let email: string | undefined = undefined as unknown as string | undefined; + if (raw.email === null) { + violations.push({ path: "email", reason: "explicit null not allowed" }); + } else if (raw.email !== undefined) { + if (typeof raw.email !== "string") { + violations.push({ path: "email", reason: "expected string" }); + } else { + email = raw.email; + } } - } - let shippingStreet: string | undefined = undefined as unknown as string | undefined; - if (raw.shippingStreet === null) { - violations.push({ path: "shippingStreet", reason: "explicit null not allowed" }); - } else if (raw.shippingStreet !== undefined) { - if (typeof raw.shippingStreet !== "string") { - violations.push({ path: "shippingStreet", reason: "expected string" }); - } else { - shippingStreet = raw.shippingStreet; + let shippingStreet: string | undefined = undefined as unknown as + | string + | undefined; + if (raw.shippingStreet === null) { + violations.push({ + path: "shippingStreet", + reason: "explicit null not allowed", + }); + } else if (raw.shippingStreet !== undefined) { + if (typeof raw.shippingStreet !== "string") { + violations.push({ path: "shippingStreet", reason: "expected string" }); + } else { + shippingStreet = raw.shippingStreet; + } } - } - let shippingZip: string | undefined = undefined as unknown as string | undefined; - if (raw.shippingZip === null) { - violations.push({ path: "shippingZip", reason: "explicit null not allowed" }); - } else if (raw.shippingZip !== undefined) { - if (typeof raw.shippingZip !== "string") { - violations.push({ path: "shippingZip", reason: "expected string" }); - } else { - shippingZip = raw.shippingZip; + let shippingZip: string | undefined = undefined as unknown as string | undefined; + if (raw.shippingZip === null) { + violations.push({ path: "shippingZip", reason: "explicit null not allowed" }); + } else if (raw.shippingZip !== undefined) { + if (typeof raw.shippingZip !== "string") { + violations.push({ path: "shippingZip", reason: "expected string" }); + } else { + shippingZip = raw.shippingZip; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!CONTACT_TS_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!CONTACT_TS_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (Object.keys(raw).length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${Object.keys(raw).length}`, - }); - } - if (Object.keys(raw).length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${Object.keys(raw).length}`, - }); - } - if (raw["shippingStreet"] !== undefined) { - if (raw["shippingZip"] === undefined) { + if (Object.keys(raw).length < 1) { violations.push({ - path: "shippingZip", - reason: `property "shippingZip" is required when "shippingStreet" is present`, + path: "", + reason: `must have at least 1 properties, got ${Object.keys(raw).length}`, }); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ContactTs = { additionalProperties }; - if (email !== undefined) { - out.email = email; - } - if (shippingStreet !== undefined) { - out.shippingStreet = shippingStreet; - } - if (shippingZip !== undefined) { - out.shippingZip = shippingZip; - } - return out; - } - - public toIntermediate(value: ContactTs): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.email !== undefined) { - out.email = value.email; - } - if (value.shippingStreet !== undefined) { - out.shippingStreet = value.shippingStreet; - } - if (value.shippingZip !== undefined) { - out.shippingZip = value.shippingZip; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (Object.keys(out).length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${Object.keys(out).length}`, - }); - } - if (Object.keys(out).length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${Object.keys(out).length}`, - }); - } - if (out["shippingStreet"] !== undefined) { - if (out["shippingZip"] === undefined) { + if (Object.keys(raw).length > 3) { violations.push({ - path: "shippingZip", - reason: `property "shippingZip" is required when "shippingStreet" is present`, + path: "", + reason: `must have at most 3 properties, got ${Object.keys(raw).length}`, }); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class ExtrasMapper { - public fromIntermediate(raw: unknown): Extras { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 4) { - violations.push({ - path: "", - reason: `must have at most 4 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - additionalProperties[key] = raw[key]; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Extras): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 4) { - violations.push({ - path: "", - reason: `must have at most 4 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class LabelsMapper { - public fromIntermediate(raw: unknown): Labels { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; + if (raw["shippingStreet"] !== undefined) { + if (raw["shippingZip"] === undefined) { + violations.push({ + path: "shippingZip", + reason: `property "shippingZip" is required when "shippingStreet" is present`, + }); + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Labels): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -const LINK_NOTE_DECLARED = new Set(["kind", "href"]); - -export class LinkNoteMapper { - public fromIntermediate(raw: unknown): LinkNote { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let kind: "link" = undefined as unknown as "link"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== LINK_NOTE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "link"` }); - } else { - kind = raw.kind as "link"; + const out: ContactTs = { additionalProperties }; + if (email !== undefined) { + out.email = email; + } + if (shippingStreet !== undefined) { + out.shippingStreet = shippingStreet; } + if (shippingZip !== undefined) { + out.shippingZip = shippingZip; + } + return out; } - let href: string = undefined as unknown as string; - if (raw.href === undefined || raw.href === null) { - violations.push({ path: "href", reason: "required" }); - } else { - if (typeof raw.href !== "string") { - violations.push({ path: "href", reason: "expected string" }); - } else { - href = raw.href; - if ([...raw.href].length < 1) { + public toTransferType(value: ContactTs): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.email !== undefined) { + out.email = value.email; + } + if (value.shippingStreet !== undefined) { + out.shippingStreet = value.shippingStreet; + } + if (value.shippingZip !== undefined) { + out.shippingZip = value.shippingZip; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (Object.keys(out).length < 1) { + violations.push({ + path: "", + reason: `must have at least 1 properties, got ${Object.keys(out).length}`, + }); + } + if (Object.keys(out).length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${Object.keys(out).length}`, + }); + } + if (out["shippingStreet"] !== undefined) { + if (out["shippingZip"] === undefined) { violations.push({ - path: "href", - reason: `must have length >= 1, got ${[...raw.href].length}`, + path: "shippingZip", + reason: `property "shippingZip" is required when "shippingStreet" is present`, }); } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } + })(); + +export const extrasTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Extras { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!LINK_NOTE_DECLARED.has(key)) { + const keys = Object.keys(raw); + if (keys.length > 4) { + violations.push({ + path: "", + reason: `must have at most 4 properties, got ${keys.length}`, + }); + } + const additionalProperties: Record = {}; + for (const key of keys) { additionalProperties[key] = raw[key]; } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: LinkNote = { kind, href, additionalProperties }; - return out; - } - - public toIntermediate(value: LinkNote): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "link") { - violations.push({ path: "kind", reason: `must equal "link"` }); - } - out.kind = value.kind; - if ([...value.href].length < 1) { - violations.push({ - path: "href", - reason: `must have length >= 1, got ${[...value.href].length}`, - }); - } - out.href = value.href; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Extras): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 4) { + violations.push({ + path: "", + reason: `must have at most 4 properties, got ${keys.length}`, + }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class NicknamesMapper { - public fromIntermediate(raw: unknown): Nicknames { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const labelsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Labels { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | null | undefined = undefined; - if (raw[key] === null) { - entry = null; - } else { + const keys = Object.keys(raw); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); + } + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; if (typeof raw[key] !== "string") { violations.push({ path: key, reason: "expected string" }); } else { entry = raw[key]; - if ([...raw[key]].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...raw[key]].length}`, - }); - } + } + if (entry !== undefined) { + additionalProperties[key] = entry; } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Nicknames): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if (entry !== null) { - if ([...entry].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...entry].length}`, - }); - } + public toTransferType(value: Labels): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; } - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class NoteMapper { - public fromIntermediate(raw: unknown): Note { - const violations: __nexgenDefinitions.Violation[] = []; - let out: Note = undefined as unknown as Note; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "text": - try { - out = new TextNoteMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - case "link": - try { - out = new LinkNoteMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["text", "link"]`, - }); + const keys = Object.keys(out); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); } - } else { - violations.push({ path: "", reason: "expected one of: TextNote, LinkNote" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } + })(); - public toIntermediate(value: Note): unknown { - if ((value as unknown as Record)["kind"] === "text") { - return new TextNoteMapper().toIntermediate(value as TextNote); - } - if ((value as unknown as Record)["kind"] === "link") { - return new LinkNoteMapper().toIntermediate(value as LinkNote); - } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: TextNote, LinkNote" }, - ]); - } -} +const LINK_NOTE_DECLARED = new Set(["kind", "href"]); -export class QuotasMapper { - public fromIntermediate(raw: unknown): Quotas { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const linkNoteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): LinkNote { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: number | undefined = undefined; - if (typeof raw[key] !== "number" || !Number.isSafeInteger(raw[key])) { - violations.push({ path: key, reason: "expected integer" }); + let kind: "link" = undefined as unknown as "link"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - entry = raw[key]; - if (raw[key] < 0) { - violations.push({ path: key, reason: `must be >= 0, got ${raw[key]}` }); + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== LINK_NOTE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "link"` }); + } else { + kind = raw.kind as "link"; } - if (raw[key] > 100) { - violations.push({ path: key, reason: `must be <= 100, got ${raw[key]}` }); + } + + let href: string = undefined as unknown as string; + if (raw.href === undefined || raw.href === null) { + violations.push({ path: "href", reason: "required" }); + } else { + if (typeof raw.href !== "string") { + violations.push({ path: "href", reason: "expected string" }); + } else { + href = raw.href; + if ([...raw.href].length < 1) { + violations.push({ + path: "href", + reason: `must have length >= 1, got ${[...raw.href].length}`, + }); + } } - if (raw[key] % 5 !== 0) { - violations.push({ - path: key, - reason: `must be a multiple of 5, got ${raw[key]}`, - }); + } + + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!LINK_NOTE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; } } - if (entry !== undefined) { - additionalProperties[key] = entry; + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + const out: LinkNote = { kind, href, additionalProperties }; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Quotas): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if (entry < 0) { - violations.push({ path: key, reason: `must be >= 0, got ${entry}` }); + public toTransferType(value: LinkNote): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "link") { + violations.push({ path: "kind", reason: `must equal "link"` }); } - if (entry > 100) { - violations.push({ path: key, reason: `must be <= 100, got ${entry}` }); + out.kind = value.kind; + if ([...value.href].length < 1) { + violations.push({ + path: "href", + reason: `must have length >= 1, got ${[...value.href].length}`, + }); } - if (entry % 5 !== 0) { - violations.push({ path: key, reason: `must be a multiple of 5, got ${entry}` }); + out.href = value.href; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; } - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class SettingsMapper { - public fromIntermediate(raw: unknown): Settings { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const nicknamesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Nicknames { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let theme: string | undefined = undefined as unknown as string | undefined; - if (raw.theme === null) { - violations.push({ path: "theme", reason: "explicit null not allowed" }); - } else if (raw.theme !== undefined) { - if (typeof raw.theme !== "string") { - violations.push({ path: "theme", reason: "expected string" }); - } else { - theme = raw.theme; + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | null | undefined = undefined; + if (raw[key] === null) { + entry = null; + } else { + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + if ([...raw[key]].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...raw[key]].length}`, + }); + } + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - let fontSize: number | undefined = undefined as unknown as number | undefined; - if (raw.fontSize === null) { - violations.push({ path: "fontSize", reason: "explicit null not allowed" }); - } else if (raw.fontSize !== undefined) { - if (typeof raw.fontSize !== "number" || !Number.isSafeInteger(raw.fontSize)) { - violations.push({ path: "fontSize", reason: "expected integer" }); - } else { - fontSize = raw.fontSize; + public toTransferType(value: Nicknames): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if (entry !== null) { + if ([...entry].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...entry].length}`, + }); + } + } + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } + })(); - for (const key of Object.keys(raw)) { - if (key !== "theme" && key !== "fontSize") { - violations.push({ path: key, reason: "unknown field" }); +export const noteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Note { + const violations: __nexgenDefinitions.Violation[] = []; + let out: Note = undefined as unknown as Note; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "text": + try { + out = textNoteTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "link": + try { + out = linkNoteTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["text", "link"]`, + }); + } + } else { + violations.push({ path: "", reason: "expected one of: TextNote, LinkNote" }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Settings = {}; - if (theme !== undefined) { - out.theme = theme; - } - if (fontSize !== undefined) { - out.fontSize = fontSize; + public toTransferType(value: Note): unknown { + if ((value as unknown as Record)["kind"] === "text") { + return textNoteTransferTypeConverter.toTransferType(value as TextNote); + } + if ((value as unknown as Record)["kind"] === "link") { + return linkNoteTransferTypeConverter.toTransferType(value as LinkNote); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: TextNote, LinkNote" }, + ]); } - return out; - } + })(); - public toIntermediate(value: Settings): unknown { - const out: Record = {}; - if (value.theme !== undefined) { - out.theme = value.theme; - } - if (value.fontSize !== undefined) { - out.fontSize = value.fontSize; - } - return out; - } -} +export const quotasTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Quotas { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } -export class ShapeMapper { - public fromIntermediate(raw: unknown): Shape { - const violations: __nexgenDefinitions.Violation[] = []; - let out: Shape = undefined as unknown as Shape; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "circle": - try { - out = new CircleMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: number | undefined = undefined; + if (typeof raw[key] !== "number" || !Number.isSafeInteger(raw[key])) { + violations.push({ path: key, reason: "expected integer" }); + } else { + entry = raw[key]; + if (raw[key] < 0) { + violations.push({ path: key, reason: `must be >= 0, got ${raw[key]}` }); } - break; - case "square": - try { - out = new SquareMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); + if (raw[key] > 100) { + violations.push({ path: key, reason: `must be <= 100, got ${raw[key]}` }); } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, - }); + if (raw[key] % 5 !== 0) { + violations.push({ + path: key, + reason: `must be a multiple of 5, got ${raw[key]}`, + }); + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - } else { - violations.push({ path: "", reason: "expected one of: Circle, Square" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - return out; - } - public toIntermediate(value: Shape): unknown { - if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); - } - if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + public toTransferType(value: Quotas): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if (entry < 0) { + violations.push({ path: key, reason: `must be >= 0, got ${entry}` }); + } + if (entry > 100) { + violations.push({ path: key, reason: `must be <= 100, got ${entry}` }); + } + if (entry % 5 !== 0) { + violations.push({ + path: key, + reason: `must be a multiple of 5, got ${entry}`, + }); + } + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: Circle, Square" }, - ]); - } -} + })(); -export class ShowcaseMapper { - public fromIntermediate(raw: unknown): Showcase { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const settingsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Settings { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "showcase" = undefined as unknown as "showcase"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== SHOWCASE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "showcase"` }); - } else { - kind = raw.kind as "showcase"; + let theme: string | undefined = undefined as unknown as string | undefined; + if (raw.theme === null) { + violations.push({ path: "theme", reason: "explicit null not allowed" }); + } else if (raw.theme !== undefined) { + if (typeof raw.theme !== "string") { + violations.push({ path: "theme", reason: "expected string" }); + } else { + theme = raw.theme; + } } - } - let revision: 1 = undefined as unknown as 1; - if (raw.revision === undefined || raw.revision === null) { - violations.push({ path: "revision", reason: "required" }); - } else { - if (typeof raw.revision !== "number") { - violations.push({ path: "revision", reason: "expected number" }); - } else if (raw.revision !== REVISION_CONST) { - violations.push({ path: "revision", reason: `must equal 1` }); - } else { - revision = raw.revision as 1; + let fontSize: number | undefined = undefined as unknown as number | undefined; + if (raw.fontSize === null) { + violations.push({ path: "fontSize", reason: "explicit null not allowed" }); + } else if (raw.fontSize !== undefined) { + if (typeof raw.fontSize !== "number" || !Number.isSafeInteger(raw.fontSize)) { + violations.push({ path: "fontSize", reason: "expected integer" }); + } else { + fontSize = raw.fontSize; + } } - } - let enabled: true = undefined as unknown as true; - if (raw.enabled === undefined || raw.enabled === null) { - violations.push({ path: "enabled", reason: "required" }); - } else { - if (typeof raw.enabled !== "boolean") { - violations.push({ path: "enabled", reason: "expected boolean" }); - } else if (raw.enabled !== ENABLED_CONST) { - violations.push({ path: "enabled", reason: `must equal true` }); - } else { - enabled = raw.enabled as true; + for (const key of Object.keys(raw)) { + if (key !== "theme" && key !== "fontSize") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - let status: "active" | "inactive" | "pending" = undefined as unknown as - | "active" - | "inactive" - | "pending"; - if (raw.status === undefined || raw.status === null) { - violations.push({ path: "status", reason: "required" }); - } else { - if (typeof raw.status !== "string") { - violations.push({ path: "status", reason: "expected string" }); - } else if ( - raw.status !== "active" && - raw.status !== "inactive" && - raw.status !== "pending" - ) { - violations.push({ - path: "status", - reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(raw.status)}`, - }); - } else { - status = raw.status as "active" | "inactive" | "pending"; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Settings = {}; + if (theme !== undefined) { + out.theme = theme; + } + if (fontSize !== undefined) { + out.fontSize = fontSize; } + return out; } - let tier: 1 | 2 | 3 = undefined as unknown as 1 | 2 | 3; - if (raw.tier === undefined || raw.tier === null) { - violations.push({ path: "tier", reason: "required" }); - } else { - if (typeof raw.tier !== "number") { - violations.push({ path: "tier", reason: "expected number" }); - } else if (raw.tier !== 1 && raw.tier !== 2 && raw.tier !== 3) { - violations.push({ - path: "tier", - reason: `must be one of [1, 2, 3], got ${JSON.stringify(raw.tier)}`, - }); - } else { - tier = raw.tier as 1 | 2 | 3; + public toTransferType(value: Settings): unknown { + const out: Record = {}; + if (value.theme !== undefined) { + out.theme = value.theme; } + if (value.fontSize !== undefined) { + out.fontSize = value.fontSize; + } + return out; } + })(); - let scale: 1.5 | 2.5 = undefined as unknown as 1.5 | 2.5; - if (raw.scale === undefined || raw.scale === null) { - violations.push({ path: "scale", reason: "required" }); - } else { - if (typeof raw.scale !== "number") { - violations.push({ path: "scale", reason: "expected number" }); - } else if (raw.scale !== 1.5 && raw.scale !== 2.5) { - violations.push({ - path: "scale", - reason: `must be one of [1.5, 2.5], got ${JSON.stringify(raw.scale)}`, - }); +export const shapeTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Shape { + const violations: __nexgenDefinitions.Violation[] = []; + let out: Shape = undefined as unknown as Shape; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "circle": + try { + out = circleTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "square": + try { + out = squareTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, + }); + } } else { - scale = raw.scale as 1.5 | 2.5; + violations.push({ path: "", reason: "expected one of: Circle, Square" }); } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); - } else { - name = raw.name; - if ([...raw.name].length < 1) { - violations.push({ - path: "name", - reason: `must have length >= 1, got ${[...raw.name].length}`, - }); - } - if ([...raw.name].length > 64) { - violations.push({ - path: "name", - reason: `must have length <= 64, got ${[...raw.name].length}`, - }); - } + public toTransferType(value: Shape): unknown { + if ((value as unknown as Record)["kind"] === "circle") { + return circleTransferTypeConverter.toTransferType(value as Circle); } + if ((value as unknown as Record)["kind"] === "square") { + return squareTransferTypeConverter.toTransferType(value as Square); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: Circle, Square" }, + ]); } + })(); - let count: number = undefined as unknown as number; - if (raw.count === undefined || raw.count === null) { - violations.push({ path: "count", reason: "required" }); - } else { - if (typeof raw.count !== "number" || !Number.isSafeInteger(raw.count)) { - violations.push({ path: "count", reason: "expected integer" }); - } else { - count = raw.count; +export const showcaseTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Showcase { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let active: boolean = undefined as unknown as boolean; - if (raw.active === undefined || raw.active === null) { - violations.push({ path: "active", reason: "required" }); - } else { - if (typeof raw.active !== "boolean") { - violations.push({ path: "active", reason: "expected boolean" }); + let kind: "showcase" = undefined as unknown as "showcase"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - active = raw.active; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== SHOWCASE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "showcase"` }); + } else { + kind = raw.kind as "showcase"; + } } - } - let nickname: string | undefined = undefined as unknown as string | undefined; - if (raw.nickname === null) { - violations.push({ path: "nickname", reason: "explicit null not allowed" }); - } else if (raw.nickname !== undefined) { - if (typeof raw.nickname !== "string") { - violations.push({ path: "nickname", reason: "expected string" }); + let revision: 1 = undefined as unknown as 1; + if (raw.revision === undefined || raw.revision === null) { + violations.push({ path: "revision", reason: "required" }); } else { - nickname = raw.nickname; - if ([...raw.nickname].length > 12) { - violations.push({ - path: "nickname", - reason: `must have length <= 12, got ${[...raw.nickname].length}`, - }); + if (typeof raw.revision !== "number") { + violations.push({ path: "revision", reason: "expected number" }); + } else if (raw.revision !== REVISION_CONST) { + violations.push({ path: "revision", reason: `must equal 1` }); + } else { + revision = raw.revision as 1; } } - } - let code: string | undefined = undefined as unknown as string | undefined; - if (raw.code === null) { - violations.push({ path: "code", reason: "explicit null not allowed" }); - } else if (raw.code !== undefined) { - if (typeof raw.code !== "string") { - violations.push({ path: "code", reason: "expected string" }); + let enabled: true = undefined as unknown as true; + if (raw.enabled === undefined || raw.enabled === null) { + violations.push({ path: "enabled", reason: "required" }); } else { - code = raw.code; - if ([...raw.code].length < 2) { - violations.push({ - path: "code", - reason: `must have length >= 2, got ${[...raw.code].length}`, - }); - } - if ([...raw.code].length > 5) { - violations.push({ - path: "code", - reason: `must have length <= 5, got ${[...raw.code].length}`, - }); + if (typeof raw.enabled !== "boolean") { + violations.push({ path: "enabled", reason: "expected boolean" }); + } else if (raw.enabled !== ENABLED_CONST) { + violations.push({ path: "enabled", reason: `must equal true` }); + } else { + enabled = raw.enabled as true; } } - } - let sku: string | undefined = undefined as unknown as string | undefined; - if (raw.sku === null) { - violations.push({ path: "sku", reason: "explicit null not allowed" }); - } else if (raw.sku !== undefined) { - if (typeof raw.sku !== "string") { - violations.push({ path: "sku", reason: "expected string" }); + let status: "active" | "inactive" | "pending" = undefined as unknown as + | "active" + | "inactive" + | "pending"; + if (raw.status === undefined || raw.status === null) { + violations.push({ path: "status", reason: "required" }); } else { - sku = raw.sku; - if (!PATTERN_821EF753B4B37A85.test(raw.sku)) { + if (typeof raw.status !== "string") { + violations.push({ path: "status", reason: "expected string" }); + } else if ( + raw.status !== "active" && + raw.status !== "inactive" && + raw.status !== "pending" + ) { violations.push({ - path: "sku", - reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(raw.sku)}`, + path: "status", + reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(raw.status)}`, }); + } else { + status = raw.status as "active" | "inactive" | "pending"; } } - } - let phrase: string | undefined = undefined as unknown as string | undefined; - if (raw.phrase === null) { - violations.push({ path: "phrase", reason: "explicit null not allowed" }); - } else if (raw.phrase !== undefined) { - if (typeof raw.phrase !== "string") { - violations.push({ path: "phrase", reason: "expected string" }); + let tier: 1 | 2 | 3 = undefined as unknown as 1 | 2 | 3; + if (raw.tier === undefined || raw.tier === null) { + violations.push({ path: "tier", reason: "required" }); } else { - phrase = raw.phrase; - if (!PATTERN_AF8AB992526D6283.test(raw.phrase)) { + if (typeof raw.tier !== "number") { + violations.push({ path: "tier", reason: "expected number" }); + } else if (raw.tier !== 1 && raw.tier !== 2 && raw.tier !== 3) { violations.push({ - path: "phrase", - reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(raw.phrase)}`, + path: "tier", + reason: `must be one of [1, 2, 3], got ${JSON.stringify(raw.tier)}`, }); + } else { + tier = raw.tier as 1 | 2 | 3; } } - } - let requestId: string | undefined = undefined as unknown as string | undefined; - if (raw.requestId === null) { - violations.push({ path: "requestId", reason: "explicit null not allowed" }); - } else if (raw.requestId !== undefined) { - if (typeof raw.requestId !== "string") { - violations.push({ path: "requestId", reason: "expected string" }); + let scale: 1.5 | 2.5 = undefined as unknown as 1.5 | 2.5; + if (raw.scale === undefined || raw.scale === null) { + violations.push({ path: "scale", reason: "required" }); } else { - requestId = raw.requestId; - if (!PATTERN_52CD3CCF2038430A.test(raw.requestId)) { + if (typeof raw.scale !== "number") { + violations.push({ path: "scale", reason: "expected number" }); + } else if (raw.scale !== 1.5 && raw.scale !== 2.5) { violations.push({ - path: "requestId", - reason: `must be a valid uuid, got ${JSON.stringify(raw.requestId)}`, + path: "scale", + reason: `must be one of [1.5, 2.5], got ${JSON.stringify(raw.scale)}`, }); + } else { + scale = raw.scale as 1.5 | 2.5; } } - } - let contactEmail: string | undefined = undefined as unknown as string | undefined; - if (raw.contactEmail === null) { - violations.push({ path: "contactEmail", reason: "explicit null not allowed" }); - } else if (raw.contactEmail !== undefined) { - if (typeof raw.contactEmail !== "string") { - violations.push({ path: "contactEmail", reason: "expected string" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - contactEmail = raw.contactEmail; - if ( - [...raw.contactEmail].length > 254 || - !PATTERN_E7C805FB9E8E4DC4.test(raw.contactEmail) - ) { - violations.push({ - path: "contactEmail", - reason: `must be a valid email, got ${JSON.stringify(raw.contactEmail)}`, - }); + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; + if ([...raw.name].length < 1) { + violations.push({ + path: "name", + reason: `must have length >= 1, got ${[...raw.name].length}`, + }); + } + if ([...raw.name].length > 64) { + violations.push({ + path: "name", + reason: `must have length <= 64, got ${[...raw.name].length}`, + }); + } } } - } - let host: string | undefined = undefined as unknown as string | undefined; - if (raw.host === null) { - violations.push({ path: "host", reason: "explicit null not allowed" }); - } else if (raw.host !== undefined) { - if (typeof raw.host !== "string") { - violations.push({ path: "host", reason: "expected string" }); + let count: number = undefined as unknown as number; + if (raw.count === undefined || raw.count === null) { + violations.push({ path: "count", reason: "required" }); } else { - host = raw.host; - if ([...raw.host].length > 253 || !PATTERN_BB674DB499542D4F.test(raw.host)) { - violations.push({ - path: "host", - reason: `must be a valid hostname, got ${JSON.stringify(raw.host)}`, - }); + if (typeof raw.count !== "number" || !Number.isSafeInteger(raw.count)) { + violations.push({ path: "count", reason: "expected integer" }); + } else { + count = raw.count; } } - } - let homepage: string | undefined = undefined as unknown as string | undefined; - if (raw.homepage === null) { - violations.push({ path: "homepage", reason: "explicit null not allowed" }); - } else if (raw.homepage !== undefined) { - if (typeof raw.homepage !== "string") { - violations.push({ path: "homepage", reason: "expected string" }); + let active: boolean = undefined as unknown as boolean; + if (raw.active === undefined || raw.active === null) { + violations.push({ path: "active", reason: "required" }); } else { - homepage = raw.homepage; - if (!PATTERN_2F0C822905CC055D.test(raw.homepage)) { - violations.push({ - path: "homepage", - reason: `must be a valid uri, got ${JSON.stringify(raw.homepage)}`, - }); + if (typeof raw.active !== "boolean") { + violations.push({ path: "active", reason: "expected boolean" }); + } else { + active = raw.active; } } - } - let gateway: string | undefined = undefined as unknown as string | undefined; - if (raw.gateway === null) { - violations.push({ path: "gateway", reason: "explicit null not allowed" }); - } else if (raw.gateway !== undefined) { - if (typeof raw.gateway !== "string") { - violations.push({ path: "gateway", reason: "expected string" }); - } else { - gateway = raw.gateway; - if (!PATTERN_F5FB862A44510B9D.test(raw.gateway)) { - violations.push({ - path: "gateway", - reason: `must be a valid ipv4, got ${JSON.stringify(raw.gateway)}`, - }); + let nickname: string | undefined = undefined as unknown as string | undefined; + if (raw.nickname === null) { + violations.push({ path: "nickname", reason: "explicit null not allowed" }); + } else if (raw.nickname !== undefined) { + if (typeof raw.nickname !== "string") { + violations.push({ path: "nickname", reason: "expected string" }); + } else { + nickname = raw.nickname; + if ([...raw.nickname].length > 12) { + violations.push({ + path: "nickname", + reason: `must have length <= 12, got ${[...raw.nickname].length}`, + }); + } } } - } - let blob: Uint8Array | undefined = undefined as unknown as Uint8Array | undefined; - if (raw.blob === null) { - violations.push({ path: "blob", reason: "explicit null not allowed" }); - } else if (raw.blob !== undefined) { - if (typeof raw.blob !== "string") { - violations.push({ path: "blob", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.base64ToBytes(raw.blob, "blob", violations); - if (parsed !== undefined) { - blob = parsed; + let code: string | undefined = undefined as unknown as string | undefined; + if (raw.code === null) { + violations.push({ path: "code", reason: "explicit null not allowed" }); + } else if (raw.code !== undefined) { + if (typeof raw.code !== "string") { + violations.push({ path: "code", reason: "expected string" }); + } else { + code = raw.code; + if ([...raw.code].length < 2) { + violations.push({ + path: "code", + reason: `must have length >= 2, got ${[...raw.code].length}`, + }); + } + if ([...raw.code].length > 5) { + violations.push({ + path: "code", + reason: `must have length <= 5, got ${[...raw.code].length}`, + }); + } } } - } - let urlBlob: Uint8Array | undefined = undefined as unknown as - | Uint8Array - | undefined; - if (raw.urlBlob === null) { - violations.push({ path: "urlBlob", reason: "explicit null not allowed" }); - } else if (raw.urlBlob !== undefined) { - if (typeof raw.urlBlob !== "string") { - violations.push({ path: "urlBlob", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.base64UrlToBytes( - raw.urlBlob, - "urlBlob", - violations, - ); - if (parsed !== undefined) { - urlBlob = parsed; + let sku: string | undefined = undefined as unknown as string | undefined; + if (raw.sku === null) { + violations.push({ path: "sku", reason: "explicit null not allowed" }); + } else if (raw.sku !== undefined) { + if (typeof raw.sku !== "string") { + violations.push({ path: "sku", reason: "expected string" }); + } else { + sku = raw.sku; + if (!PATTERN_821EF753B4B37A85.test(raw.sku)) { + violations.push({ + path: "sku", + reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(raw.sku)}`, + }); + } } } - } - let retries: number | undefined = undefined as unknown as number | undefined; - if (raw.retries === null) { - violations.push({ path: "retries", reason: "explicit null not allowed" }); - } else if (raw.retries !== undefined) { - if (typeof raw.retries !== "number" || !Number.isSafeInteger(raw.retries)) { - violations.push({ path: "retries", reason: "expected integer" }); - } else { - retries = raw.retries; + let phrase: string | undefined = undefined as unknown as string | undefined; + if (raw.phrase === null) { + violations.push({ path: "phrase", reason: "explicit null not allowed" }); + } else if (raw.phrase !== undefined) { + if (typeof raw.phrase !== "string") { + violations.push({ path: "phrase", reason: "expected string" }); + } else { + phrase = raw.phrase; + if (!PATTERN_AF8AB992526D6283.test(raw.phrase)) { + violations.push({ + path: "phrase", + reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(raw.phrase)}`, + }); + } + } } - } - let verbose: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.verbose === null) { - violations.push({ path: "verbose", reason: "explicit null not allowed" }); - } else if (raw.verbose !== undefined) { - if (typeof raw.verbose !== "boolean") { - violations.push({ path: "verbose", reason: "expected boolean" }); - } else { - verbose = raw.verbose; + let requestId: string | undefined = undefined as unknown as string | undefined; + if (raw.requestId === null) { + violations.push({ path: "requestId", reason: "explicit null not allowed" }); + } else if (raw.requestId !== undefined) { + if (typeof raw.requestId !== "string") { + violations.push({ path: "requestId", reason: "expected string" }); + } else { + requestId = raw.requestId; + if (!PATTERN_52CD3CCF2038430A.test(raw.requestId)) { + violations.push({ + path: "requestId", + reason: `must be a valid uuid, got ${JSON.stringify(raw.requestId)}`, + }); + } + } } - } - let greeting: string | undefined = undefined as unknown as string | undefined; - if (raw.greeting === null) { - violations.push({ path: "greeting", reason: "explicit null not allowed" }); - } else if (raw.greeting !== undefined) { - if (typeof raw.greeting !== "string") { - violations.push({ path: "greeting", reason: "expected string" }); - } else { - greeting = raw.greeting; + let contactEmail: string | undefined = undefined as unknown as string | undefined; + if (raw.contactEmail === null) { + violations.push({ path: "contactEmail", reason: "explicit null not allowed" }); + } else if (raw.contactEmail !== undefined) { + if (typeof raw.contactEmail !== "string") { + violations.push({ path: "contactEmail", reason: "expected string" }); + } else { + contactEmail = raw.contactEmail; + if ( + [...raw.contactEmail].length > 254 || + !PATTERN_E7C805FB9E8E4DC4.test(raw.contactEmail) + ) { + violations.push({ + path: "contactEmail", + reason: `must be a valid email, got ${JSON.stringify(raw.contactEmail)}`, + }); + } + } } - } - let debug: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.debug === null) { - violations.push({ path: "debug", reason: "explicit null not allowed" }); - } else if (raw.debug !== undefined) { - if (typeof raw.debug !== "boolean") { - violations.push({ path: "debug", reason: "expected boolean" }); - } else { - debug = raw.debug; + let host: string | undefined = undefined as unknown as string | undefined; + if (raw.host === null) { + violations.push({ path: "host", reason: "explicit null not allowed" }); + } else if (raw.host !== undefined) { + if (typeof raw.host !== "string") { + violations.push({ path: "host", reason: "expected string" }); + } else { + host = raw.host; + if ([...raw.host].length > 253 || !PATTERN_BB674DB499542D4F.test(raw.host)) { + violations.push({ + path: "host", + reason: `must be a valid hostname, got ${JSON.stringify(raw.host)}`, + }); + } + } } - } - let legacyIdTs: string | undefined = undefined as unknown as string | undefined; - if (raw.legacyId === null) { - violations.push({ path: "legacyId", reason: "explicit null not allowed" }); - } else if (raw.legacyId !== undefined) { - if (typeof raw.legacyId !== "string") { - violations.push({ path: "legacyId", reason: "expected string" }); - } else { - legacyIdTs = raw.legacyId; + let homepage: string | undefined = undefined as unknown as string | undefined; + if (raw.homepage === null) { + violations.push({ path: "homepage", reason: "explicit null not allowed" }); + } else if (raw.homepage !== undefined) { + if (typeof raw.homepage !== "string") { + violations.push({ path: "homepage", reason: "expected string" }); + } else { + homepage = raw.homepage; + if (!PATTERN_2F0C822905CC055D.test(raw.homepage)) { + violations.push({ + path: "homepage", + reason: `must be a valid uri, got ${JSON.stringify(raw.homepage)}`, + }); + } + } } - } - let middleName: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.middleName !== undefined) { - if (raw.middleName === null) { - middleName = null; - } else { - if (typeof raw.middleName !== "string") { - violations.push({ path: "middleName", reason: "expected string" }); + let gateway: string | undefined = undefined as unknown as string | undefined; + if (raw.gateway === null) { + violations.push({ path: "gateway", reason: "explicit null not allowed" }); + } else if (raw.gateway !== undefined) { + if (typeof raw.gateway !== "string") { + violations.push({ path: "gateway", reason: "expected string" }); } else { - middleName = raw.middleName; + gateway = raw.gateway; + if (!PATTERN_F5FB862A44510B9D.test(raw.gateway)) { + violations.push({ + path: "gateway", + reason: `must be a valid ipv4, got ${JSON.stringify(raw.gateway)}`, + }); + } } } - } - let category: string | null = undefined as unknown as string | null; - if (raw.category === undefined) { - violations.push({ path: "category", reason: "required" }); - } else { - if (raw.category === null) { - category = null; - } else { - if (typeof raw.category !== "string") { - violations.push({ path: "category", reason: "expected string" }); + let blob: Uint8Array | undefined = undefined as unknown as Uint8Array | undefined; + if (raw.blob === null) { + violations.push({ path: "blob", reason: "explicit null not allowed" }); + } else if (raw.blob !== undefined) { + if (typeof raw.blob !== "string") { + violations.push({ path: "blob", reason: "expected string" }); } else { - category = raw.category; + const parsed = __nexgenDefinitions.base64ToBytes( + raw.blob, + "blob", + violations, + ); + if (parsed !== undefined) { + blob = parsed; + } } } - } - let priority: number | undefined = undefined as unknown as number | undefined; - if (raw.priority === null) { - violations.push({ path: "priority", reason: "explicit null not allowed" }); - } else if (raw.priority !== undefined) { - if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { - violations.push({ path: "priority", reason: "expected integer" }); - } else { - priority = raw.priority; - if (raw.priority < 1) { - violations.push({ - path: "priority", - reason: `must be >= 1, got ${raw.priority}`, - }); + let urlBlob: Uint8Array | undefined = undefined as unknown as + | Uint8Array + | undefined; + if (raw.urlBlob === null) { + violations.push({ path: "urlBlob", reason: "explicit null not allowed" }); + } else if (raw.urlBlob !== undefined) { + if (typeof raw.urlBlob !== "string") { + violations.push({ path: "urlBlob", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.base64UrlToBytes( + raw.urlBlob, + "urlBlob", + violations, + ); + if (parsed !== undefined) { + urlBlob = parsed; + } } - if (raw.priority > 10) { - violations.push({ - path: "priority", - reason: `must be <= 10, got ${raw.priority}`, - }); + } + + let retries: number | undefined = undefined as unknown as number | undefined; + if (raw.retries === null) { + violations.push({ path: "retries", reason: "explicit null not allowed" }); + } else if (raw.retries !== undefined) { + if (typeof raw.retries !== "number" || !Number.isSafeInteger(raw.retries)) { + violations.push({ path: "retries", reason: "expected integer" }); + } else { + retries = raw.retries; } } - } - let level: number | undefined = undefined as unknown as number | undefined; - if (raw.level === null) { - violations.push({ path: "level", reason: "explicit null not allowed" }); - } else if (raw.level !== undefined) { - if (typeof raw.level !== "number" || !Number.isSafeInteger(raw.level)) { - violations.push({ path: "level", reason: "expected integer" }); - } else { - level = raw.level; - if (raw.level <= 0) { - violations.push({ path: "level", reason: `must be > 0, got ${raw.level}` }); + let verbose: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.verbose === null) { + violations.push({ path: "verbose", reason: "explicit null not allowed" }); + } else if (raw.verbose !== undefined) { + if (typeof raw.verbose !== "boolean") { + violations.push({ path: "verbose", reason: "expected boolean" }); + } else { + verbose = raw.verbose; } } - } - let ratio: number | undefined = undefined as unknown as number | undefined; - if (raw.ratio === null) { - violations.push({ path: "ratio", reason: "explicit null not allowed" }); - } else if (raw.ratio !== undefined) { - if (typeof raw.ratio !== "number") { - violations.push({ path: "ratio", reason: "expected number" }); - } else { - ratio = raw.ratio; - if (raw.ratio < 5) { - violations.push({ path: "ratio", reason: `must be >= 5, got ${raw.ratio}` }); + let greeting: string | undefined = undefined as unknown as string | undefined; + if (raw.greeting === null) { + violations.push({ path: "greeting", reason: "explicit null not allowed" }); + } else if (raw.greeting !== undefined) { + if (typeof raw.greeting !== "string") { + violations.push({ path: "greeting", reason: "expected string" }); + } else { + greeting = raw.greeting; } - if (raw.ratio % 5 !== 0) { - violations.push({ - path: "ratio", - reason: `must be a multiple of 5, got ${raw.ratio}`, - }); + } + + let debug: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.debug === null) { + violations.push({ path: "debug", reason: "explicit null not allowed" }); + } else if (raw.debug !== undefined) { + if (typeof raw.debug !== "boolean") { + violations.push({ path: "debug", reason: "expected boolean" }); + } else { + debug = raw.debug; } } - } - let step: number | undefined = undefined as unknown as number | undefined; - if (raw.step === null) { - violations.push({ path: "step", reason: "explicit null not allowed" }); - } else if (raw.step !== undefined) { - if (typeof raw.step !== "number" || !Number.isSafeInteger(raw.step)) { - violations.push({ path: "step", reason: "expected integer" }); - } else { - step = raw.step; - if (raw.step % 3 !== 0) { - violations.push({ - path: "step", - reason: `must be a multiple of 3, got ${raw.step}`, - }); + let legacyIdTs: string | undefined = undefined as unknown as string | undefined; + if (raw.legacyId === null) { + violations.push({ path: "legacyId", reason: "explicit null not allowed" }); + } else if (raw.legacyId !== undefined) { + if (typeof raw.legacyId !== "string") { + violations.push({ path: "legacyId", reason: "expected string" }); + } else { + legacyIdTs = raw.legacyId; } } - } - let tags: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.tags === null) { - violations.push({ path: "tags", reason: "explicit null not allowed" }); - } else if (raw.tags !== undefined) { - if (!Array.isArray(raw.tags)) { - violations.push({ path: "tags", reason: "expected array" }); - } else { - tags = []; - raw.tags.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `tags[${index}]`, reason: "expected element" }); + let middleName: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.middleName !== undefined) { + if (raw.middleName === null) { + middleName = null; + } else { + if (typeof raw.middleName !== "string") { + violations.push({ path: "middleName", reason: "expected string" }); } else { - item = element; + middleName = raw.middleName; } - if (item !== undefined) { - tags!.push(item); - } - }); - if (tags!.length < 1) { - violations.push({ - path: "tags", - reason: `must have at least 1 items, got ${tags!.length}`, - }); - } - if (tags!.length > 5) { - violations.push({ - path: "tags", - reason: `must have at most 5 items, got ${tags!.length}`, - }); } } - } - let aliases: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.aliases === null) { - violations.push({ path: "aliases", reason: "explicit null not allowed" }); - } else if (raw.aliases !== undefined) { - if (!Array.isArray(raw.aliases)) { - violations.push({ path: "aliases", reason: "expected array" }); + let category: string | null = undefined as unknown as string | null; + if (raw.category === undefined) { + violations.push({ path: "category", reason: "required" }); } else { - aliases = []; - raw.aliases.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `aliases[${index}]`, reason: "expected element" }); + if (raw.category === null) { + category = null; + } else { + if (typeof raw.category !== "string") { + violations.push({ path: "category", reason: "expected string" }); } else { - item = element; - } - if (item !== undefined) { - aliases!.push(item); + category = raw.category; } - }); - { - const seen = new Map(); - aliases!.forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "aliases", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); } } - } - let roles: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.roles === null) { - violations.push({ path: "roles", reason: "explicit null not allowed" }); - } else if (raw.roles !== undefined) { - if (!Array.isArray(raw.roles)) { - violations.push({ path: "roles", reason: "expected array" }); - } else { - roles = []; - raw.roles.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `roles[${index}]`, reason: "expected element" }); - } else { - item = element; - } - if (item !== undefined) { - roles!.push(item); - } - }); - { - const matchCount = roles!.filter((element) => element === "admin").length; - if (matchCount < 1) { + let priority: number | undefined = undefined as unknown as number | undefined; + if (raw.priority === null) { + violations.push({ path: "priority", reason: "explicit null not allowed" }); + } else if (raw.priority !== undefined) { + if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { + violations.push({ path: "priority", reason: "expected integer" }); + } else { + priority = raw.priority; + if (raw.priority < 1) { violations.push({ - path: "roles", - reason: `too few matching items: at least 1, got ${matchCount}`, + path: "priority", + reason: `must be >= 1, got ${raw.priority}`, }); } - if (matchCount > 2) { + if (raw.priority > 10) { violations.push({ - path: "roles", - reason: `too many matching items: at most 2, got ${matchCount}`, + path: "priority", + reason: `must be <= 10, got ${raw.priority}`, }); } } } - } - let idOrName: string | number | undefined = undefined as unknown as - | string - | number - | undefined; - if (raw.idOrName === null) { - violations.push({ path: "idOrName", reason: "explicit null not allowed" }); - } else if (raw.idOrName !== undefined) { - if (typeof raw.idOrName === "string") { - idOrName = raw.idOrName as string; - if ([...(idOrName as string)].length < 3) { - violations.push({ - path: "idOrName", - reason: `must have length >= 3, got ${[...(idOrName as string)].length}`, - }); - } - } else if ( - typeof raw.idOrName === "number" && - Number.isSafeInteger(raw.idOrName) - ) { - idOrName = raw.idOrName as number; - if ((idOrName as number) < 1) { - violations.push({ - path: "idOrName", - reason: `must be >= 1, got ${idOrName as number}`, - }); + let level: number | undefined = undefined as unknown as number | undefined; + if (raw.level === null) { + violations.push({ path: "level", reason: "explicit null not allowed" }); + } else if (raw.level !== undefined) { + if (typeof raw.level !== "number" || !Number.isSafeInteger(raw.level)) { + violations.push({ path: "level", reason: "expected integer" }); + } else { + level = raw.level; + if (raw.level <= 0) { + violations.push({ path: "level", reason: `must be > 0, got ${raw.level}` }); + } } - } else { - violations.push({ - path: "idOrName", - reason: "expected one of: string, integer", - }); } - } - let mode: "auto" | "manual" | number | undefined = undefined as unknown as - | "auto" - | "manual" - | number - | undefined; - if (raw.mode === null) { - violations.push({ path: "mode", reason: "explicit null not allowed" }); - } else if (raw.mode !== undefined) { - if (typeof raw.mode === "string") { - mode = raw.mode as "auto" | "manual"; - if ( - (mode as "auto" | "manual") !== "auto" && - (mode as "auto" | "manual") !== "manual" - ) { - violations.push({ - path: "mode", - reason: `must be one of ["auto", "manual"], got ${JSON.stringify(mode as "auto" | "manual")}`, - }); - } - } else if (typeof raw.mode === "number" && Number.isSafeInteger(raw.mode)) { - mode = raw.mode as number; - if ((mode as number) < 0) { - violations.push({ - path: "mode", - reason: `must be >= 0, got ${mode as number}`, - }); + let ratio: number | undefined = undefined as unknown as number | undefined; + if (raw.ratio === null) { + violations.push({ path: "ratio", reason: "explicit null not allowed" }); + } else if (raw.ratio !== undefined) { + if (typeof raw.ratio !== "number") { + violations.push({ path: "ratio", reason: "expected number" }); + } else { + ratio = raw.ratio; + if (raw.ratio < 5) { + violations.push({ + path: "ratio", + reason: `must be >= 5, got ${raw.ratio}`, + }); + } + if (raw.ratio % 5 !== 0) { + violations.push({ + path: "ratio", + reason: `must be a multiple of 5, got ${raw.ratio}`, + }); + } } - } else { - violations.push({ path: "mode", reason: "expected one of: string, integer" }); - } - } - - let payload: Record | string | undefined = undefined as unknown as - | Record - | string - | undefined; - if (raw.payload === null) { - violations.push({ path: "payload", reason: "explicit null not allowed" }); - } else if (raw.payload !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.payload)) { - payload = raw.payload as Record; - } else if (typeof raw.payload === "string") { - payload = raw.payload as string; - } else { - violations.push({ path: "payload", reason: "expected one of: object, string" }); } - } - let detail: ShowcaseDetailObject | string | undefined = undefined as unknown as - | ShowcaseDetailObject - | string - | undefined; - if (raw.detail === null) { - violations.push({ path: "detail", reason: "explicit null not allowed" }); - } else if (raw.detail !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.detail)) { - try { - detail = new ShowcaseDetailObjectMapper().fromIntermediate(raw.detail); - } catch (error) { - __nexgenDefinitions.collect(violations, "detail", error); + let step: number | undefined = undefined as unknown as number | undefined; + if (raw.step === null) { + violations.push({ path: "step", reason: "explicit null not allowed" }); + } else if (raw.step !== undefined) { + if (typeof raw.step !== "number" || !Number.isSafeInteger(raw.step)) { + violations.push({ path: "step", reason: "expected integer" }); + } else { + step = raw.step; + if (raw.step % 3 !== 0) { + violations.push({ + path: "step", + reason: `must be a multiple of 3, got ${raw.step}`, + }); + } } - } else if (typeof raw.detail === "string") { - detail = raw.detail as string; - } else { - violations.push({ - path: "detail", - reason: "expected one of: ShowcaseDetailObject, string", - }); } - } - let shapeOrName: Circle | Square | string | undefined = undefined as unknown as - | Circle - | Square - | string - | undefined; - if (raw.shapeOrName === null) { - violations.push({ path: "shapeOrName", reason: "explicit null not allowed" }); - } else if (raw.shapeOrName !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.shapeOrName)) { - switch ((raw.shapeOrName as Record)["kind"]) { - case "circle": - try { - shapeOrName = new CircleMapper().fromIntermediate(raw.shapeOrName); - } catch (error) { - __nexgenDefinitions.collect(violations, "shapeOrName", error); + let tags: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.tags === null) { + violations.push({ path: "tags", reason: "explicit null not allowed" }); + } else if (raw.tags !== undefined) { + if (!Array.isArray(raw.tags)) { + violations.push({ path: "tags", reason: "expected array" }); + } else { + tags = []; + raw.tags.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ path: `tags[${index}]`, reason: "expected element" }); + } else { + item = element; } - break; - case "square": - try { - shapeOrName = new SquareMapper().fromIntermediate(raw.shapeOrName); - } catch (error) { - __nexgenDefinitions.collect(violations, "shapeOrName", error); + if (item !== undefined) { + tags!.push(item); } - break; - default: + }); + if (tags!.length < 1) { violations.push({ - path: "shapeOrName", - reason: `unknown discriminator kind ${String((raw.shapeOrName as Record)["kind"])}: expected one of ["circle", "square"]`, + path: "tags", + reason: `must have at least 1 items, got ${tags!.length}`, }); + } + if (tags!.length > 5) { + violations.push({ + path: "tags", + reason: `must have at most 5 items, got ${tags!.length}`, + }); + } } - } else if (typeof raw.shapeOrName === "string") { - shapeOrName = raw.shapeOrName as string; - if ([...(shapeOrName as string)].length > 32) { - violations.push({ - path: "shapeOrName", - reason: `must have length <= 32, got ${[...(shapeOrName as string)].length}`, - }); - } - } else { - violations.push({ - path: "shapeOrName", - reason: "expected one of: Circle, Square, string", - }); } - } - let measurements: number[] | string | undefined = undefined as unknown as - | number[] - | string - | undefined; - if (raw.measurements === null) { - violations.push({ path: "measurements", reason: "explicit null not allowed" }); - } else if (raw.measurements !== undefined) { - if (Array.isArray(raw.measurements)) { - measurements = raw.measurements as number[]; - if ((measurements as number[]).length < 1) { - violations.push({ - path: "measurements", - reason: `must have at least 1 items, got ${(measurements as number[]).length}`, - }); - } - { - const seen = new Map(); - (measurements as number[]).forEach((element, index) => { - if (seen.has(element)) { + let aliases: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.aliases === null) { + violations.push({ path: "aliases", reason: "explicit null not allowed" }); + } else if (raw.aliases !== undefined) { + if (!Array.isArray(raw.aliases)) { + violations.push({ path: "aliases", reason: "expected array" }); + } else { + aliases = []; + raw.aliases.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { violations.push({ - path: "measurements", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + path: `aliases[${index}]`, + reason: "expected element", }); } else { - seen.set(element, index); + item = element; + } + if (item !== undefined) { + aliases!.push(item); } }); + { + const seen = new Map(); + aliases!.forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "aliases", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } } - } else if (typeof raw.measurements === "string") { - measurements = raw.measurements as string; - if (!PATTERN_C182F89FDB221836.test(measurements as string)) { - violations.push({ - path: "measurements", - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(measurements as string)}`, - }); - } - } else { - violations.push({ - path: "measurements", - reason: "expected one of: number[], string", - }); } - } - let shapes: Shape[] | undefined = undefined as unknown as Shape[] | undefined; - if (raw.shapes === null) { - violations.push({ path: "shapes", reason: "explicit null not allowed" }); - } else if (raw.shapes !== undefined) { - if (!Array.isArray(raw.shapes)) { - violations.push({ path: "shapes", reason: "expected array" }); - } else { - shapes = []; - raw.shapes.forEach((element: unknown, index: number) => { - let item: Shape = undefined as unknown as Shape; - try { - item = new ShapeMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `shapes[${index}]`, error); + let roles: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.roles === null) { + violations.push({ path: "roles", reason: "explicit null not allowed" }); + } else if (raw.roles !== undefined) { + if (!Array.isArray(raw.roles)) { + violations.push({ path: "roles", reason: "expected array" }); + } else { + roles = []; + raw.roles.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ path: `roles[${index}]`, reason: "expected element" }); + } else { + item = element; + } + if (item !== undefined) { + roles!.push(item); + } + }); + { + const matchCount = roles!.filter((element) => element === "admin").length; + if (matchCount < 1) { + violations.push({ + path: "roles", + reason: `too few matching items: at least 1, got ${matchCount}`, + }); + } + if (matchCount > 2) { + violations.push({ + path: "roles", + reason: `too many matching items: at most 2, got ${matchCount}`, + }); + } + } + } + } + + let idOrName: string | number | undefined = undefined as unknown as + | string + | number + | undefined; + if (raw.idOrName === null) { + violations.push({ path: "idOrName", reason: "explicit null not allowed" }); + } else if (raw.idOrName !== undefined) { + if (typeof raw.idOrName === "string") { + idOrName = raw.idOrName as string; + if ([...(idOrName as string)].length < 3) { + violations.push({ + path: "idOrName", + reason: `must have length >= 3, got ${[...(idOrName as string)].length}`, + }); } - if (item !== undefined) { - shapes!.push(item); + } else if ( + typeof raw.idOrName === "number" && + Number.isSafeInteger(raw.idOrName) + ) { + idOrName = raw.idOrName as number; + if ((idOrName as number) < 1) { + violations.push({ + path: "idOrName", + reason: `must be >= 1, got ${idOrName as number}`, + }); } - }); + } else { + violations.push({ + path: "idOrName", + reason: "expected one of: string, integer", + }); + } } - } - let segments: ShowcaseSegmentsItem[] | undefined = undefined as unknown as - | ShowcaseSegmentsItem[] - | undefined; - if (raw.segments === null) { - violations.push({ path: "segments", reason: "explicit null not allowed" }); - } else if (raw.segments !== undefined) { - if (!Array.isArray(raw.segments)) { - violations.push({ path: "segments", reason: "expected array" }); - } else { - segments = []; - raw.segments.forEach((element: unknown, index: number) => { - let item: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; + let mode: "auto" | "manual" | number | undefined = undefined as unknown as + | "auto" + | "manual" + | number + | undefined; + if (raw.mode === null) { + violations.push({ path: "mode", reason: "explicit null not allowed" }); + } else if (raw.mode !== undefined) { + if (typeof raw.mode === "string") { + mode = raw.mode as "auto" | "manual"; + if ( + (mode as "auto" | "manual") !== "auto" && + (mode as "auto" | "manual") !== "manual" + ) { + violations.push({ + path: "mode", + reason: `must be one of ["auto", "manual"], got ${JSON.stringify(mode as "auto" | "manual")}`, + }); + } + } else if (typeof raw.mode === "number" && Number.isSafeInteger(raw.mode)) { + mode = raw.mode as number; + if ((mode as number) < 0) { + violations.push({ + path: "mode", + reason: `must be >= 0, got ${mode as number}`, + }); + } + } else { + violations.push({ path: "mode", reason: "expected one of: string, integer" }); + } + } + + let payload: Record | string | undefined = + undefined as unknown as Record | string | undefined; + if (raw.payload === null) { + violations.push({ path: "payload", reason: "explicit null not allowed" }); + } else if (raw.payload !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.payload)) { + payload = raw.payload as Record; + } else if (typeof raw.payload === "string") { + payload = raw.payload as string; + } else { + violations.push({ + path: "payload", + reason: "expected one of: object, string", + }); + } + } + + let detail: ShowcaseDetailObject | string | undefined = undefined as unknown as + | ShowcaseDetailObject + | string + | undefined; + if (raw.detail === null) { + violations.push({ path: "detail", reason: "explicit null not allowed" }); + } else if (raw.detail !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.detail)) { try { - item = new ShowcaseSegmentsItemMapper().fromIntermediate(element); + detail = showcaseDetailObjectTransferTypeConverter.fromTransferType( + raw.detail, + ); } catch (error) { - __nexgenDefinitions.collect(violations, `segments[${index}]`, error); + __nexgenDefinitions.collect(violations, "detail", error); } - if (item !== undefined) { - segments!.push(item); - } - }); + } else if (typeof raw.detail === "string") { + detail = raw.detail as string; + } else { + violations.push({ + path: "detail", + reason: "expected one of: ShowcaseDetailObject, string", + }); + } } - } - let slots: (string | null)[] | undefined = undefined as unknown as - | (string | null)[] - | undefined; - if (raw.slots === null) { - violations.push({ path: "slots", reason: "explicit null not allowed" }); - } else if (raw.slots !== undefined) { - if (!Array.isArray(raw.slots)) { - violations.push({ path: "slots", reason: "expected array" }); - } else { - slots = []; - raw.slots.forEach((element: unknown, index: number) => { - let item: string | null = undefined as unknown as string | null; - if (element === null) { - item = null; - } else { - if (typeof element !== "string") { - violations.push({ path: `slots[${index}]`, reason: "expected string" }); - } else { - item = element; - } + let shapeOrName: Circle | Square | string | undefined = undefined as unknown as + | Circle + | Square + | string + | undefined; + if (raw.shapeOrName === null) { + violations.push({ path: "shapeOrName", reason: "explicit null not allowed" }); + } else if (raw.shapeOrName !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.shapeOrName)) { + switch ((raw.shapeOrName as Record)["kind"]) { + case "circle": + try { + shapeOrName = circleTransferTypeConverter.fromTransferType( + raw.shapeOrName, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "shapeOrName", error); + } + break; + case "square": + try { + shapeOrName = squareTransferTypeConverter.fromTransferType( + raw.shapeOrName, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "shapeOrName", error); + } + break; + default: + violations.push({ + path: "shapeOrName", + reason: `unknown discriminator kind ${String((raw.shapeOrName as Record)["kind"])}: expected one of ["circle", "square"]`, + }); } - if (item !== undefined) { - slots!.push(item); + } else if (typeof raw.shapeOrName === "string") { + shapeOrName = raw.shapeOrName as string; + if ([...(shapeOrName as string)].length > 32) { + violations.push({ + path: "shapeOrName", + reason: `must have length <= 32, got ${[...(shapeOrName as string)].length}`, + }); } - }); + } else { + violations.push({ + path: "shapeOrName", + reason: "expected one of: Circle, Square, string", + }); + } } - } - let grid: number[][] | undefined = undefined as unknown as number[][] | undefined; - if (raw.grid === null) { - violations.push({ path: "grid", reason: "explicit null not allowed" }); - } else if (raw.grid !== undefined) { - if (!Array.isArray(raw.grid)) { - violations.push({ path: "grid", reason: "expected array" }); - } else { - grid = []; - raw.grid.forEach((element: unknown, index: number) => { - let item: number[] = undefined as unknown as number[]; - if (!Array.isArray(element)) { - violations.push({ path: `grid[${index}]`, reason: "expected array" }); - } else { - item = []; - element.forEach((element1: unknown, index1: number) => { - let item1: number = undefined as unknown as number; - if (typeof element1 !== "number" || !Number.isSafeInteger(element1)) { + let measurements: number[] | string | undefined = undefined as unknown as + | number[] + | string + | undefined; + if (raw.measurements === null) { + violations.push({ path: "measurements", reason: "explicit null not allowed" }); + } else if (raw.measurements !== undefined) { + if (Array.isArray(raw.measurements)) { + measurements = raw.measurements as number[]; + if ((measurements as number[]).length < 1) { + violations.push({ + path: "measurements", + reason: `must have at least 1 items, got ${(measurements as number[]).length}`, + }); + } + { + const seen = new Map(); + (measurements as number[]).forEach((element, index) => { + if (seen.has(element)) { violations.push({ - path: `${`grid[${index}]`}[${index1}]`, - reason: "expected integer", + path: "measurements", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, }); } else { - item1 = element1; - } - if (item1 !== undefined) { - item!.push(item1); + seen.set(element, index); } }); } - if (item !== undefined) { - grid!.push(item); + } else if (typeof raw.measurements === "string") { + measurements = raw.measurements as string; + if (!PATTERN_C182F89FDB221836.test(measurements as string)) { + violations.push({ + path: "measurements", + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(measurements as string)}`, + }); } - }); + } else { + violations.push({ + path: "measurements", + reason: "expected one of: number[], string", + }); + } + } + + let shapes: Shape[] | undefined = undefined as unknown as Shape[] | undefined; + if (raw.shapes === null) { + violations.push({ path: "shapes", reason: "explicit null not allowed" }); + } else if (raw.shapes !== undefined) { + if (!Array.isArray(raw.shapes)) { + violations.push({ path: "shapes", reason: "expected array" }); + } else { + shapes = []; + raw.shapes.forEach((element: unknown, index: number) => { + let item: Shape = undefined as unknown as Shape; + try { + item = shapeTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `shapes[${index}]`, error); + } + if (item !== undefined) { + shapes!.push(item); + } + }); + } } - } - let location: ShowcaseLocation | undefined = undefined as unknown as - | ShowcaseLocation - | undefined; - if (raw.location === null) { - violations.push({ path: "location", reason: "explicit null not allowed" }); - } else if (raw.location !== undefined) { - try { - location = new ShowcaseLocationMapper().fromIntermediate(raw.location); - } catch (error) { - __nexgenDefinitions.collect(violations, "location", error); + let segments: ShowcaseSegmentsItem[] | undefined = undefined as unknown as + | ShowcaseSegmentsItem[] + | undefined; + if (raw.segments === null) { + violations.push({ path: "segments", reason: "explicit null not allowed" }); + } else if (raw.segments !== undefined) { + if (!Array.isArray(raw.segments)) { + violations.push({ path: "segments", reason: "expected array" }); + } else { + segments = []; + raw.segments.forEach((element: unknown, index: number) => { + let item: ShowcaseSegmentsItem = + undefined as unknown as ShowcaseSegmentsItem; + try { + item = + showcaseSegmentsItemTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `segments[${index}]`, error); + } + if (item !== undefined) { + segments!.push(item); + } + }); + } } - } - let audit: ShowcaseAudit | null | undefined = undefined as unknown as - | ShowcaseAudit - | null - | undefined; - if (raw.audit !== undefined) { - if (raw.audit === null) { - audit = null; - } else { + let slots: (string | null)[] | undefined = undefined as unknown as + | (string | null)[] + | undefined; + if (raw.slots === null) { + violations.push({ path: "slots", reason: "explicit null not allowed" }); + } else if (raw.slots !== undefined) { + if (!Array.isArray(raw.slots)) { + violations.push({ path: "slots", reason: "expected array" }); + } else { + slots = []; + raw.slots.forEach((element: unknown, index: number) => { + let item: string | null = undefined as unknown as string | null; + if (element === null) { + item = null; + } else { + if (typeof element !== "string") { + violations.push({ path: `slots[${index}]`, reason: "expected string" }); + } else { + item = element; + } + } + if (item !== undefined) { + slots!.push(item); + } + }); + } + } + + let grid: number[][] | undefined = undefined as unknown as number[][] | undefined; + if (raw.grid === null) { + violations.push({ path: "grid", reason: "explicit null not allowed" }); + } else if (raw.grid !== undefined) { + if (!Array.isArray(raw.grid)) { + violations.push({ path: "grid", reason: "expected array" }); + } else { + grid = []; + raw.grid.forEach((element: unknown, index: number) => { + let item: number[] = undefined as unknown as number[]; + if (!Array.isArray(element)) { + violations.push({ path: `grid[${index}]`, reason: "expected array" }); + } else { + item = []; + element.forEach((element1: unknown, index1: number) => { + let item1: number = undefined as unknown as number; + if (typeof element1 !== "number" || !Number.isSafeInteger(element1)) { + violations.push({ + path: `${`grid[${index}]`}[${index1}]`, + reason: "expected integer", + }); + } else { + item1 = element1; + } + if (item1 !== undefined) { + item!.push(item1); + } + }); + } + if (item !== undefined) { + grid!.push(item); + } + }); + } + } + + let location: ShowcaseLocation | undefined = undefined as unknown as + | ShowcaseLocation + | undefined; + if (raw.location === null) { + violations.push({ path: "location", reason: "explicit null not allowed" }); + } else if (raw.location !== undefined) { try { - audit = new ShowcaseAuditMapper().fromIntermediate(raw.audit); + location = showcaseLocationTransferTypeConverter.fromTransferType( + raw.location, + ); } catch (error) { - __nexgenDefinitions.collect(violations, "audit", error); + __nexgenDefinitions.collect(violations, "location", error); } } - } - let rows: ShowcaseRowsItem[] | undefined = undefined as unknown as - | ShowcaseRowsItem[] - | undefined; - if (raw.rows === null) { - violations.push({ path: "rows", reason: "explicit null not allowed" }); - } else if (raw.rows !== undefined) { - if (!Array.isArray(raw.rows)) { - violations.push({ path: "rows", reason: "expected array" }); - } else { - rows = []; - raw.rows.forEach((element: unknown, index: number) => { - let item: ShowcaseRowsItem = undefined as unknown as ShowcaseRowsItem; + let audit: ShowcaseAudit | null | undefined = undefined as unknown as + | ShowcaseAudit + | null + | undefined; + if (raw.audit !== undefined) { + if (raw.audit === null) { + audit = null; + } else { try { - item = new ShowcaseRowsItemMapper().fromIntermediate(element); + audit = showcaseAuditTransferTypeConverter.fromTransferType(raw.audit); } catch (error) { - __nexgenDefinitions.collect(violations, `rows[${index}]`, error); + __nexgenDefinitions.collect(violations, "audit", error); } - if (item !== undefined) { - rows!.push(item); - } - }); + } } - } - let ledgerTs: ShowcaseLedger | undefined = undefined as unknown as - | ShowcaseLedger - | undefined; - if (raw.ledger === null) { - violations.push({ path: "ledger", reason: "explicit null not allowed" }); - } else if (raw.ledger !== undefined) { - try { - ledgerTs = new ShowcaseLedgerMapper().fromIntermediate(raw.ledger); - } catch (error) { - __nexgenDefinitions.collect(violations, "ledger", error); + let rows: ShowcaseRowsItem[] | undefined = undefined as unknown as + | ShowcaseRowsItem[] + | undefined; + if (raw.rows === null) { + violations.push({ path: "rows", reason: "explicit null not allowed" }); + } else if (raw.rows !== undefined) { + if (!Array.isArray(raw.rows)) { + violations.push({ path: "rows", reason: "expected array" }); + } else { + rows = []; + raw.rows.forEach((element: unknown, index: number) => { + let item: ShowcaseRowsItem = undefined as unknown as ShowcaseRowsItem; + try { + item = showcaseRowsItemTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `rows[${index}]`, error); + } + if (item !== undefined) { + rows!.push(item); + } + }); + } } - } - let metadata: ShowcaseMetadata | undefined = undefined as unknown as - | ShowcaseMetadata - | undefined; - if (raw.metadata === null) { - violations.push({ path: "metadata", reason: "explicit null not allowed" }); - } else if (raw.metadata !== undefined) { - try { - metadata = new ShowcaseMetadataMapper().fromIntermediate(raw.metadata); - } catch (error) { - __nexgenDefinitions.collect(violations, "metadata", error); + let ledgerTs: ShowcaseLedger | undefined = undefined as unknown as + | ShowcaseLedger + | undefined; + if (raw.ledger === null) { + violations.push({ path: "ledger", reason: "explicit null not allowed" }); + } else if (raw.ledger !== undefined) { + try { + ledgerTs = showcaseLedgerTransferTypeConverter.fromTransferType(raw.ledger); + } catch (error) { + __nexgenDefinitions.collect(violations, "ledger", error); + } } - } - let quotas: Quotas | undefined = undefined as unknown as Quotas | undefined; - if (raw.quotas === null) { - violations.push({ path: "quotas", reason: "explicit null not allowed" }); - } else if (raw.quotas !== undefined) { - try { - quotas = new QuotasMapper().fromIntermediate(raw.quotas); - } catch (error) { - __nexgenDefinitions.collect(violations, "quotas", error); + let metadata: ShowcaseMetadata | undefined = undefined as unknown as + | ShowcaseMetadata + | undefined; + if (raw.metadata === null) { + violations.push({ path: "metadata", reason: "explicit null not allowed" }); + } else if (raw.metadata !== undefined) { + try { + metadata = showcaseMetadataTransferTypeConverter.fromTransferType( + raw.metadata, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "metadata", error); + } } - } - let tokens: Tokens | undefined = undefined as unknown as Tokens | undefined; - if (raw.tokens === null) { - violations.push({ path: "tokens", reason: "explicit null not allowed" }); - } else if (raw.tokens !== undefined) { - try { - tokens = new TokensMapper().fromIntermediate(raw.tokens); - } catch (error) { - __nexgenDefinitions.collect(violations, "tokens", error); + let quotas: Quotas | undefined = undefined as unknown as Quotas | undefined; + if (raw.quotas === null) { + violations.push({ path: "quotas", reason: "explicit null not allowed" }); + } else if (raw.quotas !== undefined) { + try { + quotas = quotasTransferTypeConverter.fromTransferType(raw.quotas); + } catch (error) { + __nexgenDefinitions.collect(violations, "quotas", error); + } } - } - let nicknames: Nicknames | undefined = undefined as unknown as - | Nicknames - | undefined; - if (raw.nicknames === null) { - violations.push({ path: "nicknames", reason: "explicit null not allowed" }); - } else if (raw.nicknames !== undefined) { - try { - nicknames = new NicknamesMapper().fromIntermediate(raw.nicknames); - } catch (error) { - __nexgenDefinitions.collect(violations, "nicknames", error); + let tokens: Tokens | undefined = undefined as unknown as Tokens | undefined; + if (raw.tokens === null) { + violations.push({ path: "tokens", reason: "explicit null not allowed" }); + } else if (raw.tokens !== undefined) { + try { + tokens = tokensTransferTypeConverter.fromTransferType(raw.tokens); + } catch (error) { + __nexgenDefinitions.collect(violations, "tokens", error); + } } - } - let choices: Choices | undefined = undefined as unknown as Choices | undefined; - if (raw.choices === null) { - violations.push({ path: "choices", reason: "explicit null not allowed" }); - } else if (raw.choices !== undefined) { - try { - choices = new ChoicesMapper().fromIntermediate(raw.choices); - } catch (error) { - __nexgenDefinitions.collect(violations, "choices", error); + let nicknames: Nicknames | undefined = undefined as unknown as + | Nicknames + | undefined; + if (raw.nicknames === null) { + violations.push({ path: "nicknames", reason: "explicit null not allowed" }); + } else if (raw.nicknames !== undefined) { + try { + nicknames = nicknamesTransferTypeConverter.fromTransferType(raw.nicknames); + } catch (error) { + __nexgenDefinitions.collect(violations, "nicknames", error); + } } - } - let extras: Extras | undefined = undefined as unknown as Extras | undefined; - if (raw.extras === null) { - violations.push({ path: "extras", reason: "explicit null not allowed" }); - } else if (raw.extras !== undefined) { - try { - extras = new ExtrasMapper().fromIntermediate(raw.extras); - } catch (error) { - __nexgenDefinitions.collect(violations, "extras", error); + let choices: Choices | undefined = undefined as unknown as Choices | undefined; + if (raw.choices === null) { + violations.push({ path: "choices", reason: "explicit null not allowed" }); + } else if (raw.choices !== undefined) { + try { + choices = choicesTransferTypeConverter.fromTransferType(raw.choices); + } catch (error) { + __nexgenDefinitions.collect(violations, "choices", error); + } } - } - let shape: Shape | undefined = undefined as unknown as Shape | undefined; - if (raw.shape === null) { - violations.push({ path: "shape", reason: "explicit null not allowed" }); - } else if (raw.shape !== undefined) { - try { - shape = new ShapeMapper().fromIntermediate(raw.shape); - } catch (error) { - __nexgenDefinitions.collect(violations, "shape", error); + let extras: Extras | undefined = undefined as unknown as Extras | undefined; + if (raw.extras === null) { + violations.push({ path: "extras", reason: "explicit null not allowed" }); + } else if (raw.extras !== undefined) { + try { + extras = extrasTransferTypeConverter.fromTransferType(raw.extras); + } catch (error) { + __nexgenDefinitions.collect(violations, "extras", error); + } } - } - let note: Note | undefined = undefined as unknown as Note | undefined; - if (raw.note === null) { - violations.push({ path: "note", reason: "explicit null not allowed" }); - } else if (raw.note !== undefined) { - try { - note = new NoteMapper().fromIntermediate(raw.note); - } catch (error) { - __nexgenDefinitions.collect(violations, "note", error); + let shape: Shape | undefined = undefined as unknown as Shape | undefined; + if (raw.shape === null) { + violations.push({ path: "shape", reason: "explicit null not allowed" }); + } else if (raw.shape !== undefined) { + try { + shape = shapeTransferTypeConverter.fromTransferType(raw.shape); + } catch (error) { + __nexgenDefinitions.collect(violations, "shape", error); + } } - } - let address: Address | undefined = undefined as unknown as Address | undefined; - if (raw.address === null) { - violations.push({ path: "address", reason: "explicit null not allowed" }); - } else if (raw.address !== undefined) { - try { - address = new AddressMapper().fromIntermediate(raw.address); - } catch (error) { - __nexgenDefinitions.collect(violations, "address", error); + let note: Note | undefined = undefined as unknown as Note | undefined; + if (raw.note === null) { + violations.push({ path: "note", reason: "explicit null not allowed" }); + } else if (raw.note !== undefined) { + try { + note = noteTransferTypeConverter.fromTransferType(raw.note); + } catch (error) { + __nexgenDefinitions.collect(violations, "note", error); + } } - } - let labels: Labels | undefined = undefined as unknown as Labels | undefined; - if (raw.labels === null) { - violations.push({ path: "labels", reason: "explicit null not allowed" }); - } else if (raw.labels !== undefined) { - try { - labels = new LabelsMapper().fromIntermediate(raw.labels); - } catch (error) { - __nexgenDefinitions.collect(violations, "labels", error); + let address: Address | undefined = undefined as unknown as Address | undefined; + if (raw.address === null) { + violations.push({ path: "address", reason: "explicit null not allowed" }); + } else if (raw.address !== undefined) { + try { + address = addressTransferTypeConverter.fromTransferType(raw.address); + } catch (error) { + __nexgenDefinitions.collect(violations, "address", error); + } } - } - let settings: Settings | undefined = undefined as unknown as Settings | undefined; - if (raw.settings === null) { - violations.push({ path: "settings", reason: "explicit null not allowed" }); - } else if (raw.settings !== undefined) { - try { - settings = new SettingsMapper().fromIntermediate(raw.settings); - } catch (error) { - __nexgenDefinitions.collect(violations, "settings", error); + let labels: Labels | undefined = undefined as unknown as Labels | undefined; + if (raw.labels === null) { + violations.push({ path: "labels", reason: "explicit null not allowed" }); + } else if (raw.labels !== undefined) { + try { + labels = labelsTransferTypeConverter.fromTransferType(raw.labels); + } catch (error) { + __nexgenDefinitions.collect(violations, "labels", error); + } } - } - let attributes: Attributes | undefined = undefined as unknown as - | Attributes - | undefined; - if (raw.attributes === null) { - violations.push({ path: "attributes", reason: "explicit null not allowed" }); - } else if (raw.attributes !== undefined) { - try { - attributes = new AttributesMapper().fromIntermediate(raw.attributes); - } catch (error) { - __nexgenDefinitions.collect(violations, "attributes", error); + let settings: Settings | undefined = undefined as unknown as Settings | undefined; + if (raw.settings === null) { + violations.push({ path: "settings", reason: "explicit null not allowed" }); + } else if (raw.settings !== undefined) { + try { + settings = settingsTransferTypeConverter.fromTransferType(raw.settings); + } catch (error) { + __nexgenDefinitions.collect(violations, "settings", error); + } } - } - let contact: ContactTs | undefined = undefined as unknown as ContactTs | undefined; - if (raw.contact === null) { - violations.push({ path: "contact", reason: "explicit null not allowed" }); - } else if (raw.contact !== undefined) { - try { - contact = new ContactTsMapper().fromIntermediate(raw.contact); - } catch (error) { - __nexgenDefinitions.collect(violations, "contact", error); + let attributes: Attributes | undefined = undefined as unknown as + | Attributes + | undefined; + if (raw.attributes === null) { + violations.push({ path: "attributes", reason: "explicit null not allowed" }); + } else if (raw.attributes !== undefined) { + try { + attributes = attributesTransferTypeConverter.fromTransferType(raw.attributes); + } catch (error) { + __nexgenDefinitions.collect(violations, "attributes", error); + } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "kind" && - key !== "revision" && - key !== "enabled" && - key !== "status" && - key !== "tier" && - key !== "scale" && - key !== "name" && - key !== "count" && - key !== "active" && - key !== "nickname" && - key !== "code" && - key !== "sku" && - key !== "phrase" && - key !== "requestId" && - key !== "contactEmail" && - key !== "host" && - key !== "homepage" && - key !== "gateway" && - key !== "blob" && - key !== "urlBlob" && - key !== "retries" && - key !== "verbose" && - key !== "greeting" && - key !== "debug" && - key !== "legacyId" && - key !== "middleName" && - key !== "category" && - key !== "priority" && - key !== "level" && - key !== "ratio" && - key !== "step" && - key !== "tags" && - key !== "aliases" && - key !== "roles" && - key !== "idOrName" && - key !== "mode" && - key !== "payload" && - key !== "detail" && - key !== "shapeOrName" && - key !== "measurements" && - key !== "shapes" && - key !== "segments" && - key !== "slots" && - key !== "grid" && - key !== "location" && - key !== "audit" && - key !== "rows" && - key !== "ledger" && - key !== "metadata" && - key !== "quotas" && - key !== "tokens" && - key !== "nicknames" && - key !== "choices" && - key !== "extras" && - key !== "shape" && - key !== "note" && - key !== "address" && - key !== "labels" && - key !== "settings" && - key !== "attributes" && - key !== "contact" - ) { - violations.push({ path: key, reason: "unknown field" }); + let contact: ContactTs | undefined = undefined as unknown as + | ContactTs + | undefined; + if (raw.contact === null) { + violations.push({ path: "contact", reason: "explicit null not allowed" }); + } else if (raw.contact !== undefined) { + try { + contact = contactTsTransferTypeConverter.fromTransferType(raw.contact); + } catch (error) { + __nexgenDefinitions.collect(violations, "contact", error); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Showcase = { - kind, - revision, - enabled, - status, - tier, - scale, - name, - count, - active, - category, - }; - if (nickname !== undefined) { - out.nickname = nickname; - } - if (code !== undefined) { - out.code = code; - } - if (sku !== undefined) { - out.sku = sku; - } - if (phrase !== undefined) { - out.phrase = phrase; - } - if (requestId !== undefined) { - out.requestId = requestId; - } - if (contactEmail !== undefined) { - out.contactEmail = contactEmail; - } - if (host !== undefined) { - out.host = host; - } - if (homepage !== undefined) { - out.homepage = homepage; - } - if (gateway !== undefined) { - out.gateway = gateway; - } - if (blob !== undefined) { - out.blob = blob; - } - if (urlBlob !== undefined) { - out.urlBlob = urlBlob; - } - if (retries !== undefined) { - out.retries = retries; - } - if (verbose !== undefined) { - out.verbose = verbose; - } - if (greeting !== undefined) { - out.greeting = greeting; - } - if (debug !== undefined) { - out.debug = debug; - } - if (legacyIdTs !== undefined) { - out.legacyIdTs = legacyIdTs; - } - if (middleName !== undefined) { - out.middleName = middleName; - } - if (priority !== undefined) { - out.priority = priority; - } - if (level !== undefined) { - out.level = level; - } - if (ratio !== undefined) { - out.ratio = ratio; - } - if (step !== undefined) { - out.step = step; - } - if (tags !== undefined) { - out.tags = tags; - } - if (aliases !== undefined) { - out.aliases = aliases; - } - if (roles !== undefined) { - out.roles = roles; - } - if (idOrName !== undefined) { - out.idOrName = idOrName; - } - if (mode !== undefined) { - out.mode = mode; - } - if (payload !== undefined) { - out.payload = payload; - } - if (detail !== undefined) { - out.detail = detail; - } - if (shapeOrName !== undefined) { - out.shapeOrName = shapeOrName; - } - if (measurements !== undefined) { - out.measurements = measurements; - } - if (shapes !== undefined) { - out.shapes = shapes; - } - if (segments !== undefined) { - out.segments = segments; - } - if (slots !== undefined) { - out.slots = slots; - } - if (grid !== undefined) { - out.grid = grid; - } - if (location !== undefined) { - out.location = location; - } - if (audit !== undefined) { - out.audit = audit; - } - if (rows !== undefined) { - out.rows = rows; - } - if (ledgerTs !== undefined) { - out.ledgerTs = ledgerTs; - } - if (metadata !== undefined) { - out.metadata = metadata; - } - if (quotas !== undefined) { - out.quotas = quotas; - } - if (tokens !== undefined) { - out.tokens = tokens; - } - if (nicknames !== undefined) { - out.nicknames = nicknames; - } - if (choices !== undefined) { - out.choices = choices; - } - if (extras !== undefined) { - out.extras = extras; - } - if (shape !== undefined) { - out.shape = shape; - } - if (note !== undefined) { - out.note = note; - } - if (address !== undefined) { - out.address = address; - } - if (labels !== undefined) { - out.labels = labels; - } - if (settings !== undefined) { - out.settings = settings; - } - if (attributes !== undefined) { - out.attributes = attributes; - } - if (contact !== undefined) { - out.contact = contact; - } - return out; - } + for (const key of Object.keys(raw)) { + if ( + key !== "kind" && + key !== "revision" && + key !== "enabled" && + key !== "status" && + key !== "tier" && + key !== "scale" && + key !== "name" && + key !== "count" && + key !== "active" && + key !== "nickname" && + key !== "code" && + key !== "sku" && + key !== "phrase" && + key !== "requestId" && + key !== "contactEmail" && + key !== "host" && + key !== "homepage" && + key !== "gateway" && + key !== "blob" && + key !== "urlBlob" && + key !== "retries" && + key !== "verbose" && + key !== "greeting" && + key !== "debug" && + key !== "legacyId" && + key !== "middleName" && + key !== "category" && + key !== "priority" && + key !== "level" && + key !== "ratio" && + key !== "step" && + key !== "tags" && + key !== "aliases" && + key !== "roles" && + key !== "idOrName" && + key !== "mode" && + key !== "payload" && + key !== "detail" && + key !== "shapeOrName" && + key !== "measurements" && + key !== "shapes" && + key !== "segments" && + key !== "slots" && + key !== "grid" && + key !== "location" && + key !== "audit" && + key !== "rows" && + key !== "ledger" && + key !== "metadata" && + key !== "quotas" && + key !== "tokens" && + key !== "nicknames" && + key !== "choices" && + key !== "extras" && + key !== "shape" && + key !== "note" && + key !== "address" && + key !== "labels" && + key !== "settings" && + key !== "attributes" && + key !== "contact" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } - public toIntermediate(value: Showcase): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "showcase") { - violations.push({ path: "kind", reason: `must equal "showcase"` }); - } - out.kind = value.kind; - if (value.revision !== 1) { - violations.push({ path: "revision", reason: `must equal 1` }); - } - out.revision = value.revision; - if (value.enabled !== true) { - violations.push({ path: "enabled", reason: `must equal true` }); - } - out.enabled = value.enabled; - if ( - value.status !== "active" && - value.status !== "inactive" && - value.status !== "pending" - ) { - violations.push({ - path: "status", - reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(value.status)}`, - }); - } - out.status = value.status; - if (value.tier !== 1 && value.tier !== 2 && value.tier !== 3) { - violations.push({ - path: "tier", - reason: `must be one of [1, 2, 3], got ${JSON.stringify(value.tier)}`, - }); - } - out.tier = value.tier; - if (value.scale !== 1.5 && value.scale !== 2.5) { - violations.push({ - path: "scale", - reason: `must be one of [1.5, 2.5], got ${JSON.stringify(value.scale)}`, - }); - } - out.scale = value.scale; - if ([...value.name].length < 1) { - violations.push({ - path: "name", - reason: `must have length >= 1, got ${[...value.name].length}`, - }); - } - if ([...value.name].length > 64) { - violations.push({ - path: "name", - reason: `must have length <= 64, got ${[...value.name].length}`, - }); - } - out.name = value.name; - out.count = value.count; - out.active = value.active; - if (value.nickname !== undefined) { - if ([...value.nickname].length > 12) { - violations.push({ - path: "nickname", - reason: `must have length <= 12, got ${[...value.nickname].length}`, - }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - out.nickname = value.nickname; - } - if (value.code !== undefined) { - if ([...value.code].length < 2) { - violations.push({ - path: "code", - reason: `must have length >= 2, got ${[...value.code].length}`, - }); + const out: Showcase = { + kind, + revision, + enabled, + status, + tier, + scale, + name, + count, + active, + category, + }; + if (nickname !== undefined) { + out.nickname = nickname; } - if ([...value.code].length > 5) { - violations.push({ - path: "code", - reason: `must have length <= 5, got ${[...value.code].length}`, - }); + if (code !== undefined) { + out.code = code; } - out.code = value.code; - } - if (value.sku !== undefined) { - if (!PATTERN_821EF753B4B37A85.test(value.sku)) { - violations.push({ - path: "sku", - reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(value.sku)}`, - }); + if (sku !== undefined) { + out.sku = sku; } - out.sku = value.sku; - } - if (value.phrase !== undefined) { - if (!PATTERN_AF8AB992526D6283.test(value.phrase)) { - violations.push({ - path: "phrase", - reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(value.phrase)}`, - }); + if (phrase !== undefined) { + out.phrase = phrase; } - out.phrase = value.phrase; - } - if (value.requestId !== undefined) { - if (!PATTERN_52CD3CCF2038430A.test(value.requestId)) { - violations.push({ - path: "requestId", - reason: `must be a valid uuid, got ${JSON.stringify(value.requestId)}`, - }); + if (requestId !== undefined) { + out.requestId = requestId; + } + if (contactEmail !== undefined) { + out.contactEmail = contactEmail; + } + if (host !== undefined) { + out.host = host; + } + if (homepage !== undefined) { + out.homepage = homepage; + } + if (gateway !== undefined) { + out.gateway = gateway; + } + if (blob !== undefined) { + out.blob = blob; + } + if (urlBlob !== undefined) { + out.urlBlob = urlBlob; + } + if (retries !== undefined) { + out.retries = retries; + } + if (verbose !== undefined) { + out.verbose = verbose; + } + if (greeting !== undefined) { + out.greeting = greeting; + } + if (debug !== undefined) { + out.debug = debug; + } + if (legacyIdTs !== undefined) { + out.legacyIdTs = legacyIdTs; + } + if (middleName !== undefined) { + out.middleName = middleName; + } + if (priority !== undefined) { + out.priority = priority; + } + if (level !== undefined) { + out.level = level; + } + if (ratio !== undefined) { + out.ratio = ratio; + } + if (step !== undefined) { + out.step = step; + } + if (tags !== undefined) { + out.tags = tags; + } + if (aliases !== undefined) { + out.aliases = aliases; + } + if (roles !== undefined) { + out.roles = roles; + } + if (idOrName !== undefined) { + out.idOrName = idOrName; + } + if (mode !== undefined) { + out.mode = mode; + } + if (payload !== undefined) { + out.payload = payload; + } + if (detail !== undefined) { + out.detail = detail; + } + if (shapeOrName !== undefined) { + out.shapeOrName = shapeOrName; + } + if (measurements !== undefined) { + out.measurements = measurements; + } + if (shapes !== undefined) { + out.shapes = shapes; + } + if (segments !== undefined) { + out.segments = segments; + } + if (slots !== undefined) { + out.slots = slots; + } + if (grid !== undefined) { + out.grid = grid; + } + if (location !== undefined) { + out.location = location; + } + if (audit !== undefined) { + out.audit = audit; + } + if (rows !== undefined) { + out.rows = rows; + } + if (ledgerTs !== undefined) { + out.ledgerTs = ledgerTs; + } + if (metadata !== undefined) { + out.metadata = metadata; + } + if (quotas !== undefined) { + out.quotas = quotas; + } + if (tokens !== undefined) { + out.tokens = tokens; + } + if (nicknames !== undefined) { + out.nicknames = nicknames; + } + if (choices !== undefined) { + out.choices = choices; + } + if (extras !== undefined) { + out.extras = extras; + } + if (shape !== undefined) { + out.shape = shape; } - out.requestId = value.requestId; + if (note !== undefined) { + out.note = note; + } + if (address !== undefined) { + out.address = address; + } + if (labels !== undefined) { + out.labels = labels; + } + if (settings !== undefined) { + out.settings = settings; + } + if (attributes !== undefined) { + out.attributes = attributes; + } + if (contact !== undefined) { + out.contact = contact; + } + return out; } - if (value.contactEmail !== undefined) { + + public toTransferType(value: Showcase): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "showcase") { + violations.push({ path: "kind", reason: `must equal "showcase"` }); + } + out.kind = value.kind; + if (value.revision !== 1) { + violations.push({ path: "revision", reason: `must equal 1` }); + } + out.revision = value.revision; + if (value.enabled !== true) { + violations.push({ path: "enabled", reason: `must equal true` }); + } + out.enabled = value.enabled; if ( - [...value.contactEmail].length > 254 || - !PATTERN_E7C805FB9E8E4DC4.test(value.contactEmail) + value.status !== "active" && + value.status !== "inactive" && + value.status !== "pending" ) { violations.push({ - path: "contactEmail", - reason: `must be a valid email, got ${JSON.stringify(value.contactEmail)}`, + path: "status", + reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(value.status)}`, }); } - out.contactEmail = value.contactEmail; - } - if (value.host !== undefined) { - if ([...value.host].length > 253 || !PATTERN_BB674DB499542D4F.test(value.host)) { + out.status = value.status; + if (value.tier !== 1 && value.tier !== 2 && value.tier !== 3) { violations.push({ - path: "host", - reason: `must be a valid hostname, got ${JSON.stringify(value.host)}`, + path: "tier", + reason: `must be one of [1, 2, 3], got ${JSON.stringify(value.tier)}`, }); } - out.host = value.host; - } - if (value.homepage !== undefined) { - if (!PATTERN_2F0C822905CC055D.test(value.homepage)) { + out.tier = value.tier; + if (value.scale !== 1.5 && value.scale !== 2.5) { violations.push({ - path: "homepage", - reason: `must be a valid uri, got ${JSON.stringify(value.homepage)}`, + path: "scale", + reason: `must be one of [1.5, 2.5], got ${JSON.stringify(value.scale)}`, }); } - out.homepage = value.homepage; - } - if (value.gateway !== undefined) { - if (!PATTERN_F5FB862A44510B9D.test(value.gateway)) { + out.scale = value.scale; + if ([...value.name].length < 1) { violations.push({ - path: "gateway", - reason: `must be a valid ipv4, got ${JSON.stringify(value.gateway)}`, + path: "name", + reason: `must have length >= 1, got ${[...value.name].length}`, }); } - out.gateway = value.gateway; - } - if (value.blob !== undefined) { - out.blob = __nexgenDefinitions.bytesToBase64(value.blob); - } - if (value.urlBlob !== undefined) { - out.urlBlob = __nexgenDefinitions.bytesToBase64Url(value.urlBlob); - } - if (value.retries !== undefined) { - out.retries = value.retries; - } - if (value.verbose !== undefined) { - out.verbose = value.verbose; - } - if (value.greeting !== undefined) { - out.greeting = value.greeting; - } - if (value.debug !== undefined) { - out.debug = value.debug; - } - if (value.legacyIdTs !== undefined) { - out.legacyId = value.legacyIdTs; - } - if (value.middleName !== undefined) { - out.middleName = value.middleName; - } - out.category = value.category; - if (value.priority !== undefined) { - if (value.priority < 1) { + if ([...value.name].length > 64) { violations.push({ - path: "priority", - reason: `must be >= 1, got ${value.priority}`, + path: "name", + reason: `must have length <= 64, got ${[...value.name].length}`, }); } - if (value.priority > 10) { - violations.push({ - path: "priority", - reason: `must be <= 10, got ${value.priority}`, - }); + out.name = value.name; + out.count = value.count; + out.active = value.active; + if (value.nickname !== undefined) { + if ([...value.nickname].length > 12) { + violations.push({ + path: "nickname", + reason: `must have length <= 12, got ${[...value.nickname].length}`, + }); + } + out.nickname = value.nickname; } - out.priority = value.priority; - } - if (value.level !== undefined) { - if (value.level <= 0) { - violations.push({ path: "level", reason: `must be > 0, got ${value.level}` }); + if (value.code !== undefined) { + if ([...value.code].length < 2) { + violations.push({ + path: "code", + reason: `must have length >= 2, got ${[...value.code].length}`, + }); + } + if ([...value.code].length > 5) { + violations.push({ + path: "code", + reason: `must have length <= 5, got ${[...value.code].length}`, + }); + } + out.code = value.code; } - out.level = value.level; - } - if (value.ratio !== undefined) { - if (value.ratio < 5) { - violations.push({ path: "ratio", reason: `must be >= 5, got ${value.ratio}` }); + if (value.sku !== undefined) { + if (!PATTERN_821EF753B4B37A85.test(value.sku)) { + violations.push({ + path: "sku", + reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(value.sku)}`, + }); + } + out.sku = value.sku; } - if (value.ratio % 5 !== 0) { - violations.push({ - path: "ratio", - reason: `must be a multiple of 5, got ${value.ratio}`, - }); + if (value.phrase !== undefined) { + if (!PATTERN_AF8AB992526D6283.test(value.phrase)) { + violations.push({ + path: "phrase", + reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(value.phrase)}`, + }); + } + out.phrase = value.phrase; } - out.ratio = value.ratio; - } - if (value.step !== undefined) { - if (value.step % 3 !== 0) { - violations.push({ - path: "step", - reason: `must be a multiple of 3, got ${value.step}`, - }); + if (value.requestId !== undefined) { + if (!PATTERN_52CD3CCF2038430A.test(value.requestId)) { + violations.push({ + path: "requestId", + reason: `must be a valid uuid, got ${JSON.stringify(value.requestId)}`, + }); + } + out.requestId = value.requestId; } - out.step = value.step; - } - if (value.tags !== undefined) { - if (value.tags.length < 1) { - violations.push({ - path: "tags", - reason: `must have at least 1 items, got ${value.tags.length}`, - }); + if (value.contactEmail !== undefined) { + if ( + [...value.contactEmail].length > 254 || + !PATTERN_E7C805FB9E8E4DC4.test(value.contactEmail) + ) { + violations.push({ + path: "contactEmail", + reason: `must be a valid email, got ${JSON.stringify(value.contactEmail)}`, + }); + } + out.contactEmail = value.contactEmail; + } + if (value.host !== undefined) { + if ( + [...value.host].length > 253 || + !PATTERN_BB674DB499542D4F.test(value.host) + ) { + violations.push({ + path: "host", + reason: `must be a valid hostname, got ${JSON.stringify(value.host)}`, + }); + } + out.host = value.host; + } + if (value.homepage !== undefined) { + if (!PATTERN_2F0C822905CC055D.test(value.homepage)) { + violations.push({ + path: "homepage", + reason: `must be a valid uri, got ${JSON.stringify(value.homepage)}`, + }); + } + out.homepage = value.homepage; + } + if (value.gateway !== undefined) { + if (!PATTERN_F5FB862A44510B9D.test(value.gateway)) { + violations.push({ + path: "gateway", + reason: `must be a valid ipv4, got ${JSON.stringify(value.gateway)}`, + }); + } + out.gateway = value.gateway; + } + if (value.blob !== undefined) { + out.blob = __nexgenDefinitions.bytesToBase64(value.blob); + } + if (value.urlBlob !== undefined) { + out.urlBlob = __nexgenDefinitions.bytesToBase64Url(value.urlBlob); + } + if (value.retries !== undefined) { + out.retries = value.retries; + } + if (value.verbose !== undefined) { + out.verbose = value.verbose; + } + if (value.greeting !== undefined) { + out.greeting = value.greeting; + } + if (value.debug !== undefined) { + out.debug = value.debug; + } + if (value.legacyIdTs !== undefined) { + out.legacyId = value.legacyIdTs; + } + if (value.middleName !== undefined) { + out.middleName = value.middleName; + } + out.category = value.category; + if (value.priority !== undefined) { + if (value.priority < 1) { + violations.push({ + path: "priority", + reason: `must be >= 1, got ${value.priority}`, + }); + } + if (value.priority > 10) { + violations.push({ + path: "priority", + reason: `must be <= 10, got ${value.priority}`, + }); + } + out.priority = value.priority; + } + if (value.level !== undefined) { + if (value.level <= 0) { + violations.push({ path: "level", reason: `must be > 0, got ${value.level}` }); + } + out.level = value.level; + } + if (value.ratio !== undefined) { + if (value.ratio < 5) { + violations.push({ + path: "ratio", + reason: `must be >= 5, got ${value.ratio}`, + }); + } + if (value.ratio % 5 !== 0) { + violations.push({ + path: "ratio", + reason: `must be a multiple of 5, got ${value.ratio}`, + }); + } + out.ratio = value.ratio; + } + if (value.step !== undefined) { + if (value.step % 3 !== 0) { + violations.push({ + path: "step", + reason: `must be a multiple of 3, got ${value.step}`, + }); + } + out.step = value.step; + } + if (value.tags !== undefined) { + if (value.tags.length < 1) { + violations.push({ + path: "tags", + reason: `must have at least 1 items, got ${value.tags.length}`, + }); + } + if (value.tags.length > 5) { + violations.push({ + path: "tags", + reason: `must have at most 5 items, got ${value.tags.length}`, + }); + } + out.tags = value.tags; + } + if (value.aliases !== undefined) { + { + const seen = new Map(); + value.aliases.forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "aliases", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } + out.aliases = value.aliases; + } + if (value.roles !== undefined) { + { + const matchCount = value.roles.filter( + (element) => element === "admin", + ).length; + if (matchCount < 1) { + violations.push({ + path: "roles", + reason: `too few matching items: at least 1, got ${matchCount}`, + }); + } + if (matchCount > 2) { + violations.push({ + path: "roles", + reason: `too many matching items: at most 2, got ${matchCount}`, + }); + } + } + out.roles = value.roles; + } + if (value.idOrName !== undefined) { + if (typeof value.idOrName === "string") { + if ([...(value.idOrName as string)].length < 3) { + violations.push({ + path: "idOrName", + reason: `must have length >= 3, got ${[...(value.idOrName as string)].length}`, + }); + } + } + if ( + typeof value.idOrName === "number" && + Number.isSafeInteger(value.idOrName) + ) { + if ((value.idOrName as number) < 1) { + violations.push({ + path: "idOrName", + reason: `must be >= 1, got ${value.idOrName as number}`, + }); + } + } + out.idOrName = value.idOrName; + } + if (value.mode !== undefined) { + if (typeof value.mode === "string") { + if ( + (value.mode as "auto" | "manual") !== "auto" && + (value.mode as "auto" | "manual") !== "manual" + ) { + violations.push({ + path: "mode", + reason: `must be one of ["auto", "manual"], got ${JSON.stringify(value.mode as "auto" | "manual")}`, + }); + } + } + if (typeof value.mode === "number" && Number.isSafeInteger(value.mode)) { + if ((value.mode as number) < 0) { + violations.push({ + path: "mode", + reason: `must be >= 0, got ${value.mode as number}`, + }); + } + } + out.mode = value.mode; + } + if (value.payload !== undefined) { + out.payload = value.payload; + } + if (value.detail !== undefined) { + out.detail = serializeShowcaseDetail(value.detail); + } + if (value.shapeOrName !== undefined) { + if (typeof value.shapeOrName === "string") { + if ([...(value.shapeOrName as string)].length > 32) { + violations.push({ + path: "shapeOrName", + reason: `must have length <= 32, got ${[...(value.shapeOrName as string)].length}`, + }); + } + } + out.shapeOrName = serializeShowcaseShapeOrName(value.shapeOrName); + } + if (value.measurements !== undefined) { + if (Array.isArray(value.measurements)) { + if ((value.measurements as number[]).length < 1) { + violations.push({ + path: "measurements", + reason: `must have at least 1 items, got ${(value.measurements as number[]).length}`, + }); + } + { + const seen = new Map(); + (value.measurements as number[]).forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "measurements", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } + } + if (typeof value.measurements === "string") { + if (!PATTERN_C182F89FDB221836.test(value.measurements as string)) { + violations.push({ + path: "measurements", + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(value.measurements as string)}`, + }); + } + } + out.measurements = value.measurements; + } + if (value.shapes !== undefined) { + out.shapes = value.shapes.map((element) => + shapeTransferTypeConverter.toTransferType(element), + ); + } + if (value.segments !== undefined) { + out.segments = value.segments.map((element) => + showcaseSegmentsItemTransferTypeConverter.toTransferType(element), + ); + } + if (value.slots !== undefined) { + out.slots = value.slots; + } + if (value.grid !== undefined) { + out.grid = value.grid; + } + if (value.location !== undefined) { + out.location = showcaseLocationTransferTypeConverter.toTransferType( + value.location, + ); + } + if (value.audit !== undefined) { + out.audit = + value.audit === null + ? null + : showcaseAuditTransferTypeConverter.toTransferType(value.audit); + } + if (value.rows !== undefined) { + out.rows = value.rows.map((element) => + showcaseRowsItemTransferTypeConverter.toTransferType(element), + ); + } + if (value.ledgerTs !== undefined) { + out.ledger = showcaseLedgerTransferTypeConverter.toTransferType(value.ledgerTs); + } + if (value.metadata !== undefined) { + out.metadata = showcaseMetadataTransferTypeConverter.toTransferType( + value.metadata, + ); + } + if (value.quotas !== undefined) { + out.quotas = quotasTransferTypeConverter.toTransferType(value.quotas); + } + if (value.tokens !== undefined) { + out.tokens = tokensTransferTypeConverter.toTransferType(value.tokens); } - if (value.tags.length > 5) { - violations.push({ - path: "tags", - reason: `must have at most 5 items, got ${value.tags.length}`, - }); + if (value.nicknames !== undefined) { + out.nicknames = nicknamesTransferTypeConverter.toTransferType(value.nicknames); } - out.tags = value.tags; - } - if (value.aliases !== undefined) { - { - const seen = new Map(); - value.aliases.forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "aliases", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); + if (value.choices !== undefined) { + out.choices = choicesTransferTypeConverter.toTransferType(value.choices); } - out.aliases = value.aliases; - } - if (value.roles !== undefined) { - { - const matchCount = value.roles.filter((element) => element === "admin").length; - if (matchCount < 1) { - violations.push({ - path: "roles", - reason: `too few matching items: at least 1, got ${matchCount}`, - }); - } - if (matchCount > 2) { - violations.push({ - path: "roles", - reason: `too many matching items: at most 2, got ${matchCount}`, - }); - } + if (value.extras !== undefined) { + out.extras = extrasTransferTypeConverter.toTransferType(value.extras); } - out.roles = value.roles; - } - if (value.idOrName !== undefined) { - if (typeof value.idOrName === "string") { - if ([...(value.idOrName as string)].length < 3) { - violations.push({ - path: "idOrName", - reason: `must have length >= 3, got ${[...(value.idOrName as string)].length}`, - }); - } + if (value.shape !== undefined) { + out.shape = shapeTransferTypeConverter.toTransferType(value.shape); } - if (typeof value.idOrName === "number" && Number.isSafeInteger(value.idOrName)) { - if ((value.idOrName as number) < 1) { - violations.push({ - path: "idOrName", - reason: `must be >= 1, got ${value.idOrName as number}`, - }); - } + if (value.note !== undefined) { + out.note = noteTransferTypeConverter.toTransferType(value.note); } - out.idOrName = value.idOrName; - } - if (value.mode !== undefined) { - if (typeof value.mode === "string") { - if ( - (value.mode as "auto" | "manual") !== "auto" && - (value.mode as "auto" | "manual") !== "manual" - ) { - violations.push({ - path: "mode", - reason: `must be one of ["auto", "manual"], got ${JSON.stringify(value.mode as "auto" | "manual")}`, - }); - } + if (value.address !== undefined) { + out.address = addressTransferTypeConverter.toTransferType(value.address); } - if (typeof value.mode === "number" && Number.isSafeInteger(value.mode)) { - if ((value.mode as number) < 0) { - violations.push({ - path: "mode", - reason: `must be >= 0, got ${value.mode as number}`, - }); - } + if (value.labels !== undefined) { + out.labels = labelsTransferTypeConverter.toTransferType(value.labels); } - out.mode = value.mode; - } - if (value.payload !== undefined) { - out.payload = value.payload; - } - if (value.detail !== undefined) { - out.detail = serializeShowcaseDetail(value.detail); - } - if (value.shapeOrName !== undefined) { - if (typeof value.shapeOrName === "string") { - if ([...(value.shapeOrName as string)].length > 32) { - violations.push({ - path: "shapeOrName", - reason: `must have length <= 32, got ${[...(value.shapeOrName as string)].length}`, - }); - } + if (value.settings !== undefined) { + out.settings = settingsTransferTypeConverter.toTransferType(value.settings); } - out.shapeOrName = serializeShowcaseShapeOrName(value.shapeOrName); - } - if (value.measurements !== undefined) { - if (Array.isArray(value.measurements)) { - if ((value.measurements as number[]).length < 1) { - violations.push({ - path: "measurements", - reason: `must have at least 1 items, got ${(value.measurements as number[]).length}`, - }); - } - { - const seen = new Map(); - (value.measurements as number[]).forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "measurements", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); - } + if (value.attributes !== undefined) { + out.attributes = attributesTransferTypeConverter.toTransferType( + value.attributes, + ); } - if (typeof value.measurements === "string") { - if (!PATTERN_C182F89FDB221836.test(value.measurements as string)) { - violations.push({ - path: "measurements", - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(value.measurements as string)}`, - }); - } + if (value.contact !== undefined) { + out.contact = contactTsTransferTypeConverter.toTransferType(value.contact); } - out.measurements = value.measurements; - } - if (value.shapes !== undefined) { - out.shapes = value.shapes.map((element) => - new ShapeMapper().toIntermediate(element), - ); - } - if (value.segments !== undefined) { - out.segments = value.segments.map((element) => - new ShowcaseSegmentsItemMapper().toIntermediate(element), - ); - } - if (value.slots !== undefined) { - out.slots = value.slots; - } - if (value.grid !== undefined) { - out.grid = value.grid; - } - if (value.location !== undefined) { - out.location = new ShowcaseLocationMapper().toIntermediate(value.location); - } - if (value.audit !== undefined) { - out.audit = - value.audit === null - ? null - : new ShowcaseAuditMapper().toIntermediate(value.audit); - } - if (value.rows !== undefined) { - out.rows = value.rows.map((element) => - new ShowcaseRowsItemMapper().toIntermediate(element), - ); - } - if (value.ledgerTs !== undefined) { - out.ledger = new ShowcaseLedgerMapper().toIntermediate(value.ledgerTs); - } - if (value.metadata !== undefined) { - out.metadata = new ShowcaseMetadataMapper().toIntermediate(value.metadata); - } - if (value.quotas !== undefined) { - out.quotas = new QuotasMapper().toIntermediate(value.quotas); - } - if (value.tokens !== undefined) { - out.tokens = new TokensMapper().toIntermediate(value.tokens); - } - if (value.nicknames !== undefined) { - out.nicknames = new NicknamesMapper().toIntermediate(value.nicknames); - } - if (value.choices !== undefined) { - out.choices = new ChoicesMapper().toIntermediate(value.choices); - } - if (value.extras !== undefined) { - out.extras = new ExtrasMapper().toIntermediate(value.extras); - } - if (value.shape !== undefined) { - out.shape = new ShapeMapper().toIntermediate(value.shape); - } - if (value.note !== undefined) { - out.note = new NoteMapper().toIntermediate(value.note); - } - if (value.address !== undefined) { - out.address = new AddressMapper().toIntermediate(value.address); - } - if (value.labels !== undefined) { - out.labels = new LabelsMapper().toIntermediate(value.labels); - } - if (value.settings !== undefined) { - out.settings = new SettingsMapper().toIntermediate(value.settings); - } - if (value.attributes !== undefined) { - out.attributes = new AttributesMapper().toIntermediate(value.attributes); - } - if (value.contact !== undefined) { - out.contact = new ContactTsMapper().toIntermediate(value.contact); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_AUDIT_DECLARED = new Set(["by"]); -export class ShowcaseAuditMapper { - public fromIntermediate(raw: unknown): ShowcaseAudit { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseAuditTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseAudit { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let by: string = undefined as unknown as string; - if (raw.by === undefined || raw.by === null) { - violations.push({ path: "by", reason: "required" }); - } else { - if (typeof raw.by !== "string") { - violations.push({ path: "by", reason: "expected string" }); + let by: string = undefined as unknown as string; + if (raw.by === undefined || raw.by === null) { + violations.push({ path: "by", reason: "required" }); } else { - by = raw.by; - if ([...raw.by].length < 1) { - violations.push({ - path: "by", - reason: `must have length >= 1, got ${[...raw.by].length}`, - }); + if (typeof raw.by !== "string") { + violations.push({ path: "by", reason: "expected string" }); + } else { + by = raw.by; + if ([...raw.by].length < 1) { + violations.push({ + path: "by", + reason: `must have length >= 1, got ${[...raw.by].length}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_AUDIT_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_AUDIT_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseAudit = { by, additionalProperties }; + return out; } - const out: ShowcaseAudit = { by, additionalProperties }; - return out; - } - public toIntermediate(value: ShowcaseAudit): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.by].length < 1) { - violations.push({ - path: "by", - reason: `must have length >= 1, got ${[...value.by].length}`, - }); - } - out.by = value.by; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseAudit): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.by].length < 1) { + violations.push({ + path: "by", + reason: `must have length >= 1, got ${[...value.by].length}`, + }); + } + out.by = value.by; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_DETAIL_OBJECT_DECLARED = new Set(["code", "hint"]); -export class ShowcaseDetailObjectMapper { - public fromIntermediate(raw: unknown): ShowcaseDetailObject { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseDetailObjectTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseDetailObject { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let code: string = undefined as unknown as string; - if (raw.code === undefined || raw.code === null) { - violations.push({ path: "code", reason: "required" }); - } else { - if (typeof raw.code !== "string") { - violations.push({ path: "code", reason: "expected string" }); + let code: string = undefined as unknown as string; + if (raw.code === undefined || raw.code === null) { + violations.push({ path: "code", reason: "required" }); } else { - code = raw.code; - if ([...raw.code].length < 1) { - violations.push({ - path: "code", - reason: `must have length >= 1, got ${[...raw.code].length}`, - }); + if (typeof raw.code !== "string") { + violations.push({ path: "code", reason: "expected string" }); + } else { + code = raw.code; + if ([...raw.code].length < 1) { + violations.push({ + path: "code", + reason: `must have length >= 1, got ${[...raw.code].length}`, + }); + } } } - } - let hint: string | undefined = undefined as unknown as string | undefined; - if (raw.hint === null) { - violations.push({ path: "hint", reason: "explicit null not allowed" }); - } else if (raw.hint !== undefined) { - if (typeof raw.hint !== "string") { - violations.push({ path: "hint", reason: "expected string" }); - } else { - hint = raw.hint; + let hint: string | undefined = undefined as unknown as string | undefined; + if (raw.hint === null) { + violations.push({ path: "hint", reason: "explicit null not allowed" }); + } else if (raw.hint !== undefined) { + if (typeof raw.hint !== "string") { + violations.push({ path: "hint", reason: "expected string" }); + } else { + hint = raw.hint; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_DETAIL_OBJECT_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_DETAIL_OBJECT_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseDetailObject = { code, additionalProperties }; - if (hint !== undefined) { - out.hint = hint; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseDetailObject = { code, additionalProperties }; + if (hint !== undefined) { + out.hint = hint; + } + return out; } - return out; - } - public toIntermediate(value: ShowcaseDetailObject): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.code].length < 1) { - violations.push({ - path: "code", - reason: `must have length >= 1, got ${[...value.code].length}`, - }); - } - out.code = value.code; - if (value.hint !== undefined) { - out.hint = value.hint; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseDetailObject): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.code].length < 1) { + violations.push({ + path: "code", + reason: `must have length >= 1, got ${[...value.code].length}`, + }); + } + out.code = value.code; + if (value.hint !== undefined) { + out.hint = value.hint; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class ShowcaseLedgerMapper { - public fromIntermediate(raw: unknown): ShowcaseLedger { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLedgerTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLedger { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: ShowcaseLedgerValue | undefined = undefined; - try { - entry = new ShowcaseLedgerValueMapper().fromIntermediate(raw[key]); - } catch (error) { - __nexgenDefinitions.collect(violations, key, error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: ShowcaseLedgerValue | undefined = undefined; + try { + entry = showcaseLedgerValueTransferTypeConverter.fromTransferType(raw[key]); + } catch (error) { + __nexgenDefinitions.collect(violations, key, error); + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: ShowcaseLedger): unknown { - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = new ShowcaseLedgerValueMapper().toIntermediate(entry); + public toTransferType(value: ShowcaseLedger): unknown { + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = showcaseLedgerValueTransferTypeConverter.toTransferType(entry); + } + return out; } - return out; - } -} + })(); const SHOWCASE_LEDGER_VALUE_DECLARED = new Set(["amount"]); -export class ShowcaseLedgerValueMapper { - public fromIntermediate(raw: unknown): ShowcaseLedgerValue { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLedgerValueTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLedgerValue { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let amount: number = undefined as unknown as number; - if (raw.amount === undefined || raw.amount === null) { - violations.push({ path: "amount", reason: "required" }); - } else { - if (typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount)) { - violations.push({ path: "amount", reason: "expected integer" }); + let amount: number = undefined as unknown as number; + if (raw.amount === undefined || raw.amount === null) { + violations.push({ path: "amount", reason: "required" }); } else { - amount = raw.amount; - if (raw.amount < 0) { - violations.push({ - path: "amount", - reason: `must be >= 0, got ${raw.amount}`, - }); + if (typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount)) { + violations.push({ path: "amount", reason: "expected integer" }); + } else { + amount = raw.amount; + if (raw.amount < 0) { + violations.push({ + path: "amount", + reason: `must be >= 0, got ${raw.amount}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LEDGER_VALUE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LEDGER_VALUE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseLedgerValue = { amount, additionalProperties }; + return out; } - const out: ShowcaseLedgerValue = { amount, additionalProperties }; - return out; - } - public toIntermediate(value: ShowcaseLedgerValue): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.amount < 0) { - violations.push({ path: "amount", reason: `must be >= 0, got ${value.amount}` }); - } - out.amount = value.amount; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseLedgerValue): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.amount < 0) { + violations.push({ + path: "amount", + reason: `must be >= 0, got ${value.amount}`, + }); + } + out.amount = value.amount; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_LOCATION_DECLARED = new Set(["city", "geo"]); -export class ShowcaseLocationMapper { - public fromIntermediate(raw: unknown): ShowcaseLocation { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLocationTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLocation { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let city: string = undefined as unknown as string; - if (raw.city === undefined || raw.city === null) { - violations.push({ path: "city", reason: "required" }); - } else { - if (typeof raw.city !== "string") { - violations.push({ path: "city", reason: "expected string" }); + let city: string = undefined as unknown as string; + if (raw.city === undefined || raw.city === null) { + violations.push({ path: "city", reason: "required" }); } else { - city = raw.city; - if ([...raw.city].length < 1) { - violations.push({ - path: "city", - reason: `must have length >= 1, got ${[...raw.city].length}`, - }); + if (typeof raw.city !== "string") { + violations.push({ path: "city", reason: "expected string" }); + } else { + city = raw.city; + if ([...raw.city].length < 1) { + violations.push({ + path: "city", + reason: `must have length >= 1, got ${[...raw.city].length}`, + }); + } } } - } - let geo: ShowcaseLocationGeo | undefined = undefined as unknown as - | ShowcaseLocationGeo - | undefined; - if (raw.geo === null) { - violations.push({ path: "geo", reason: "explicit null not allowed" }); - } else if (raw.geo !== undefined) { - try { - geo = new ShowcaseLocationGeoMapper().fromIntermediate(raw.geo); - } catch (error) { - __nexgenDefinitions.collect(violations, "geo", error); + let geo: ShowcaseLocationGeo | undefined = undefined as unknown as + | ShowcaseLocationGeo + | undefined; + if (raw.geo === null) { + violations.push({ path: "geo", reason: "explicit null not allowed" }); + } else if (raw.geo !== undefined) { + try { + geo = showcaseLocationGeoTransferTypeConverter.fromTransferType(raw.geo); + } catch (error) { + __nexgenDefinitions.collect(violations, "geo", error); + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LOCATION_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LOCATION_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseLocation = { city, additionalProperties }; - if (geo !== undefined) { - out.geo = geo; - } - return out; - } - - public toIntermediate(value: ShowcaseLocation): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.city].length < 1) { - violations.push({ - path: "city", - reason: `must have length >= 1, got ${[...value.city].length}`, - }); - } - out.city = value.city; - if (value.geo !== undefined) { - out.geo = new ShowcaseLocationGeoMapper().toIntermediate(value.geo); - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -const SHOWCASE_LOCATION_GEO_DECLARED = new Set(["lat", "lon"]); -export class ShowcaseLocationGeoMapper { - public fromIntermediate(raw: unknown): ShowcaseLocationGeo { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let lat: number | undefined = undefined as unknown as number | undefined; - if (raw.lat === null) { - violations.push({ path: "lat", reason: "explicit null not allowed" }); - } else if (raw.lat !== undefined) { - if (typeof raw.lat !== "number") { - violations.push({ path: "lat", reason: "expected number" }); - } else { - lat = raw.lat; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - } - - let lon: number | undefined = undefined as unknown as number | undefined; - if (raw.lon === null) { - violations.push({ path: "lon", reason: "explicit null not allowed" }); - } else if (raw.lon !== undefined) { - if (typeof raw.lon !== "number") { - violations.push({ path: "lon", reason: "expected number" }); - } else { - lon = raw.lon; + const out: ShowcaseLocation = { city, additionalProperties }; + if (geo !== undefined) { + out.geo = geo; } + return out; } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LOCATION_GEO_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + public toTransferType(value: ShowcaseLocation): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.city].length < 1) { + violations.push({ + path: "city", + reason: `must have length >= 1, got ${[...value.city].length}`, + }); } + out.city = value.city; + if (value.geo !== undefined) { + out.geo = showcaseLocationGeoTransferTypeConverter.toTransferType(value.geo); + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } + })(); - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseLocationGeo = { additionalProperties }; - if (lat !== undefined) { - out.lat = lat; - } - if (lon !== undefined) { - out.lon = lon; - } - return out; - } - - public toIntermediate(value: ShowcaseLocationGeo): unknown { - const out: Record = {}; - if (value.lat !== undefined) { - out.lat = value.lat; - } - if (value.lon !== undefined) { - out.lon = value.lon; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - return out; - } -} - -export class ShowcaseMetadataMapper { - public fromIntermediate(raw: unknown): ShowcaseMetadata { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - additionalProperties[key] = raw[key]; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: ShowcaseMetadata): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} +const SHOWCASE_LOCATION_GEO_DECLARED = new Set(["lat", "lon"]); -const SHOWCASE_ROWS_ITEM_DECLARED = new Set(["cell"]); +export const showcaseLocationGeoTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLocationGeo { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let lat: number | undefined = undefined as unknown as number | undefined; + if (raw.lat === null) { + violations.push({ path: "lat", reason: "explicit null not allowed" }); + } else if (raw.lat !== undefined) { + if (typeof raw.lat !== "number") { + violations.push({ path: "lat", reason: "expected number" }); + } else { + lat = raw.lat; + } + } -export class ShowcaseRowsItemMapper { - public fromIntermediate(raw: unknown): ShowcaseRowsItem { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + let lon: number | undefined = undefined as unknown as number | undefined; + if (raw.lon === null) { + violations.push({ path: "lon", reason: "explicit null not allowed" }); + } else if (raw.lon !== undefined) { + if (typeof raw.lon !== "number") { + violations.push({ path: "lon", reason: "expected number" }); + } else { + lon = raw.lon; + } + } - let cell: string = undefined as unknown as string; - if (raw.cell === undefined || raw.cell === null) { - violations.push({ path: "cell", reason: "required" }); - } else { - if (typeof raw.cell !== "string") { - violations.push({ path: "cell", reason: "expected string" }); - } else { - cell = raw.cell; - if ([...raw.cell].length < 1) { - violations.push({ - path: "cell", - reason: `must have length >= 1, got ${[...raw.cell].length}`, - }); + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LOCATION_GEO_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_ROWS_ITEM_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + const out: ShowcaseLocationGeo = { additionalProperties }; + if (lat !== undefined) { + out.lat = lat; + } + if (lon !== undefined) { + out.lon = lon; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseLocationGeo): unknown { + const out: Record = {}; + if (value.lat !== undefined) { + out.lat = value.lat; + } + if (value.lon !== undefined) { + out.lon = value.lon; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - const out: ShowcaseRowsItem = { cell, additionalProperties }; - return out; - } + })(); - public toIntermediate(value: ShowcaseRowsItem): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.cell].length < 1) { - violations.push({ - path: "cell", - reason: `must have length >= 1, got ${[...value.cell].length}`, - }); - } - out.cell = value.cell; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} +export const showcaseMetadataTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseMetadata { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } -export class ShowcaseSegmentsItemMapper { - public fromIntermediate(raw: unknown): ShowcaseSegmentsItem { - const violations: __nexgenDefinitions.Violation[] = []; - let out: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; - if (typeof raw === "string") { - out = raw as string; - if ([...(out as string)].length < 2) { + const keys = Object.keys(raw); + if (keys.length > 3) { violations.push({ path: "", - reason: `must have length >= 2, got ${[...(out as string)].length}`, + reason: `must have at most 3 properties, got ${keys.length}`, }); } - } else if (typeof raw === "number" && Number.isSafeInteger(raw)) { - out = raw as number; - if ((out as number) < 0) { - violations.push({ path: "", reason: `must be >= 0, got ${out as number}` }); + const additionalProperties: Record = {}; + for (const key of keys) { + additionalProperties[key] = raw[key]; } - } else { - violations.push({ path: "", reason: "expected one of: string, integer" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - return out; - } - public toIntermediate(value: ShowcaseSegmentsItem): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - if (typeof value === "string") { - if ([...(value as string)].length < 2) { + public toTransferType(value: ShowcaseMetadata): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 3) { violations.push({ path: "", - reason: `must have length >= 2, got ${[...(value as string)].length}`, + reason: `must have at most 3 properties, got ${keys.length}`, }); } - } - if (typeof value === "number" && Number.isSafeInteger(value)) { - if ((value as number) < 0) { - violations.push({ path: "", reason: `must be >= 0, got ${value as number}` }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + })(); + +const SHOWCASE_ROWS_ITEM_DECLARED = new Set(["cell"]); + +export const showcaseRowsItemTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseRowsItem { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let cell: string = undefined as unknown as string; + if (raw.cell === undefined || raw.cell === null) { + violations.push({ path: "cell", reason: "required" }); + } else { + if (typeof raw.cell !== "string") { + violations.push({ path: "cell", reason: "expected string" }); + } else { + cell = raw.cell; + if ([...raw.cell].length < 1) { + violations.push({ + path: "cell", + reason: `must have length >= 1, got ${[...raw.cell].length}`, + }); + } + } + } + + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_ROWS_ITEM_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseRowsItem = { cell, additionalProperties }; + return out; } - if (typeof value === "string") { - return value; + + public toTransferType(value: ShowcaseRowsItem): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.cell].length < 1) { + violations.push({ + path: "cell", + reason: `must have length >= 1, got ${[...value.cell].length}`, + }); + } + out.cell = value.cell; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - if (typeof value === "number" && Number.isSafeInteger(value)) { - return value; + })(); + +export const showcaseSegmentsItemTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseSegmentsItem { + const violations: __nexgenDefinitions.Violation[] = []; + let out: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; + if (typeof raw === "string") { + out = raw as string; + if ([...(out as string)].length < 2) { + violations.push({ + path: "", + reason: `must have length >= 2, got ${[...(out as string)].length}`, + }); + } + } else if (typeof raw === "number" && Number.isSafeInteger(raw)) { + out = raw as number; + if ((out as number) < 0) { + violations.push({ path: "", reason: `must be >= 0, got ${out as number}` }); + } + } else { + violations.push({ path: "", reason: "expected one of: string, integer" }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: string, integer" }, - ]); - } -} -export class GetShowcaseInputMapper { - public fromIntermediate(raw: unknown): GetShowcaseInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { + public toTransferType(value: ShowcaseSegmentsItem): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + if (typeof value === "string") { + if ([...(value as string)].length < 2) { + violations.push({ + path: "", + reason: `must have length >= 2, got ${[...(value as string)].length}`, + }); + } + } + if (typeof value === "number" && Number.isSafeInteger(value)) { + if ((value as number) < 0) { + violations.push({ path: "", reason: `must be >= 0, got ${value as number}` }); + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + if (typeof value === "string") { + return value; + } + if (typeof value === "number" && Number.isSafeInteger(value)) { + return value; + } throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, + { path: "", reason: "expected one of: string, integer" }, ]); } + })(); + +export const getShowcaseInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetShowcaseInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - id = raw.id; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "id") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "id") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetShowcaseInput = { id }; + return out; } - const out: GetShowcaseInput = { id }; - return out; - } - public toIntermediate(value: GetShowcaseInput): unknown { - const out: Record = {}; - out.id = value.id; - return out; - } -} + public toTransferType(value: GetShowcaseInput): unknown { + const out: Record = {}; + out.id = value.id; + return out; + } + })(); const SQUARE_DECLARED = new Set(["kind", "side"]); -export class SquareMapper { - public fromIntermediate(raw: unknown): Square { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const squareTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Square { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "square" = undefined as unknown as "square"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== SQUARE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "square"` }); + let kind: "square" = undefined as unknown as "square"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "square"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== SQUARE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "square"` }); + } else { + kind = raw.kind as "square"; + } } - } - let side: number = undefined as unknown as number; - if (raw.side === undefined || raw.side === null) { - violations.push({ path: "side", reason: "required" }); - } else { - if (typeof raw.side !== "number") { - violations.push({ path: "side", reason: "expected number" }); + let side: number = undefined as unknown as number; + if (raw.side === undefined || raw.side === null) { + violations.push({ path: "side", reason: "required" }); } else { - side = raw.side; + if (typeof raw.side !== "number") { + violations.push({ path: "side", reason: "expected number" }); + } else { + side = raw.side; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SQUARE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SQUARE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Square = { kind, side, additionalProperties }; + return out; } - const out: Square = { kind, side, additionalProperties }; - return out; - } - public toIntermediate(value: Square): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "square") { - violations.push({ path: "kind", reason: `must equal "square"` }); - } - out.kind = value.kind; - out.side = value.side; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Square): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "square") { + violations.push({ path: "kind", reason: `must equal "square"` }); + } + out.kind = value.kind; + out.side = value.side; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const TEXT_NOTE_DECLARED = new Set(["kind", "body"]); -export class TextNoteMapper { - public fromIntermediate(raw: unknown): TextNote { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const textNoteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): TextNote { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "text" = undefined as unknown as "text"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== TEXT_NOTE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "text"` }); + let kind: "text" = undefined as unknown as "text"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "text"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== TEXT_NOTE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "text"` }); + } else { + kind = raw.kind as "text"; + } } - } - let body: string = undefined as unknown as string; - if (raw.body === undefined || raw.body === null) { - violations.push({ path: "body", reason: "required" }); - } else { - if (typeof raw.body !== "string") { - violations.push({ path: "body", reason: "expected string" }); + let body: string = undefined as unknown as string; + if (raw.body === undefined || raw.body === null) { + violations.push({ path: "body", reason: "required" }); } else { - body = raw.body; - if ([...raw.body].length < 1) { - violations.push({ - path: "body", - reason: `must have length >= 1, got ${[...raw.body].length}`, - }); + if (typeof raw.body !== "string") { + violations.push({ path: "body", reason: "expected string" }); + } else { + body = raw.body; + if ([...raw.body].length < 1) { + violations.push({ + path: "body", + reason: `must have length >= 1, got ${[...raw.body].length}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!TEXT_NOTE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!TEXT_NOTE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: TextNote = { kind, body, additionalProperties }; + return out; } - const out: TextNote = { kind, body, additionalProperties }; - return out; - } - public toIntermediate(value: TextNote): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "text") { - violations.push({ path: "kind", reason: `must equal "text"` }); - } - out.kind = value.kind; - if ([...value.body].length < 1) { - violations.push({ - path: "body", - reason: `must have length >= 1, got ${[...value.body].length}`, - }); - } - out.body = value.body; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: TextNote): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "text") { + violations.push({ path: "kind", reason: `must equal "text"` }); + } + out.kind = value.kind; + if ([...value.body].length < 1) { + violations.push({ + path: "body", + reason: `must have length >= 1, got ${[...value.body].length}`, + }); + } + out.body = value.body; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class TokensMapper { - public fromIntermediate(raw: unknown): Tokens { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); +export const tokensTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Tokens { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + if ([...raw[key]].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...raw[key]].length}`, + }); + } + if ([...raw[key]].length > 8) { + violations.push({ + path: key, + reason: `must have length <= 8, got ${[...raw[key]].length}`, + }); + } + if (!PATTERN_C182F89FDB221836.test(raw[key])) { + violations.push({ + path: key, + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(raw[key])}`, + }); + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; - if ([...raw[key]].length < 2) { + public toTransferType(value: Tokens): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if ([...entry].length < 2) { violations.push({ path: key, - reason: `must have length >= 2, got ${[...raw[key]].length}`, + reason: `must have length >= 2, got ${[...entry].length}`, }); } - if ([...raw[key]].length > 8) { + if ([...entry].length > 8) { violations.push({ path: key, - reason: `must have length <= 8, got ${[...raw[key]].length}`, + reason: `must have length <= 8, got ${[...entry].length}`, }); } - if (!PATTERN_C182F89FDB221836.test(raw[key])) { + if (!PATTERN_C182F89FDB221836.test(entry)) { violations.push({ path: key, - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(raw[key])}`, + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(entry)}`, }); } + out[key] = entry; } - if (entry !== undefined) { - additionalProperties[key] = entry; - } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Tokens): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if ([...entry].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...entry].length}`, - }); - } - if ([...entry].length > 8) { - violations.push({ - path: key, - reason: `must have length <= 8, got ${[...entry].length}`, - }); - } - if (!PATTERN_C182F89FDB221836.test(entry)) { - violations.push({ - path: key, - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(entry)}`, - }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - out[key] = entry; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + })(); const WIDGET_DECLARED = new Set(["id", "kind", "name", "size"]); -export class WidgetMapper { - public fromIntermediate(raw: unknown): Widget { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); - } else { - id = raw.id; +export const widgetTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Widget { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let kind: string | undefined = undefined as unknown as string | undefined; - if (raw.kind === null) { - violations.push({ path: "kind", reason: "explicit null not allowed" }); - } else if (raw.kind !== undefined) { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - kind = raw.kind; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); - } else { - name = raw.name; + let kind: string | undefined = undefined as unknown as string | undefined; + if (raw.kind === null) { + violations.push({ path: "kind", reason: "explicit null not allowed" }); + } else if (raw.kind !== undefined) { + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else { + kind = raw.kind; + } } - } - let size: number | undefined = undefined as unknown as number | undefined; - if (raw.size === null) { - violations.push({ path: "size", reason: "explicit null not allowed" }); - } else if (raw.size !== undefined) { - if (typeof raw.size !== "number" || !Number.isSafeInteger(raw.size)) { - violations.push({ path: "size", reason: "expected integer" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - size = raw.size; - if (raw.size < 10) { - violations.push({ path: "size", reason: `must be >= 10, got ${raw.size}` }); + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; } - if (raw.size > 20) { - violations.push({ path: "size", reason: `must be <= 20, got ${raw.size}` }); + } + + let size: number | undefined = undefined as unknown as number | undefined; + if (raw.size === null) { + violations.push({ path: "size", reason: "explicit null not allowed" }); + } else if (raw.size !== undefined) { + if (typeof raw.size !== "number" || !Number.isSafeInteger(raw.size)) { + violations.push({ path: "size", reason: "expected integer" }); + } else { + size = raw.size; + if (raw.size < 10) { + violations.push({ path: "size", reason: `must be >= 10, got ${raw.size}` }); + } + if (raw.size > 20) { + violations.push({ path: "size", reason: `must be <= 20, got ${raw.size}` }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!WIDGET_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!WIDGET_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Widget = { id, name, additionalProperties }; - if (kind !== undefined) { - out.kind = kind; - } - if (size !== undefined) { - out.size = size; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Widget = { id, name, additionalProperties }; + if (kind !== undefined) { + out.kind = kind; + } + if (size !== undefined) { + out.size = size; + } + return out; } - return out; - } - public toIntermediate(value: Widget): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.id = value.id; - if (value.kind !== undefined) { - out.kind = value.kind; - } - out.name = value.name; - if (value.size !== undefined) { - if (value.size < 10) { - violations.push({ path: "size", reason: `must be >= 10, got ${value.size}` }); + public toTransferType(value: Widget): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.id = value.id; + if (value.kind !== undefined) { + out.kind = value.kind; } - if (value.size > 20) { - violations.push({ path: "size", reason: `must be <= 20, got ${value.size}` }); + out.name = value.name; + if (value.size !== undefined) { + if (value.size < 10) { + violations.push({ path: "size", reason: `must be >= 10, got ${value.size}` }); + } + if (value.size > 20) { + violations.push({ path: "size", reason: `must be <= 20, got ${value.size}` }); + } + out.size = value.size; } - out.size = value.size; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const WIDGET_BASE_DECLARED = new Set(["id", "kind"]); -export class WidgetBaseMapper { - public fromIntermediate(raw: unknown): WidgetBase { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const widgetBaseTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): WidgetBase { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - id = raw.id; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let kind: string | undefined = undefined as unknown as string | undefined; - if (raw.kind === null) { - violations.push({ path: "kind", reason: "explicit null not allowed" }); - } else if (raw.kind !== undefined) { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else { - kind = raw.kind; + let kind: string | undefined = undefined as unknown as string | undefined; + if (raw.kind === null) { + violations.push({ path: "kind", reason: "explicit null not allowed" }); + } else if (raw.kind !== undefined) { + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else { + kind = raw.kind; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!WIDGET_BASE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!WIDGET_BASE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: WidgetBase = { id, additionalProperties }; - if (kind !== undefined) { - out.kind = kind; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: WidgetBase = { id, additionalProperties }; + if (kind !== undefined) { + out.kind = kind; + } + return out; } - return out; - } - public toIntermediate(value: WidgetBase): unknown { - const out: Record = {}; - out.id = value.id; - if (value.kind !== undefined) { - out.kind = value.kind; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; + public toTransferType(value: WidgetBase): unknown { + const out: Record = {}; + out.id = value.id; + if (value.kind !== undefined) { + out.kind = value.kind; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - return out; - } -} + })(); diff --git a/architecture.md b/architecture.md index 6d19a5ba..9cbd4515 100644 --- a/architecture.md +++ b/architecture.md @@ -98,4 +98,7 @@ flowchart LR result records. - `TypePlanningPass` materializes target-ready type metadata. - `ReachabilityPass` removes declarations outside the generated surface. -- `EmittedNameResolutionPass` resolves final emitted JSON model identifiers. +- `EmittedNameResolutionPass` resolves final emitted JSON model identifiers. Its + name manifest spans the whole tree, not one leaf: a `$ref` across input files + names a model whose `x--name` override is declared in the other file, so + the consuming module can only resolve it from the tree-wide manifest. diff --git a/samples/typescript/kb/content/page/models.ts b/samples/typescript/kb/content/page/models.ts index 70ce0300..9329d618 100644 --- a/samples/typescript/kb/content/page/models.ts +++ b/samples/typescript/kb/content/page/models.ts @@ -1,7 +1,8 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; -import { BlockMapper } from "../block/models"; +import { blockTransferTypeConverter } from "../block/models"; import type { Block } from "../block/models"; /** @@ -25,153 +26,155 @@ export interface PageMeta { wordCount?: number; } -export class PageMapper { - public fromIntermediate(raw: unknown): Page { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let pageId: string = undefined as unknown as string; - if (raw.pageId === undefined || raw.pageId === null) { - violations.push({ path: "pageId", reason: "required" }); - } else { - if (typeof raw.pageId !== "string") { - violations.push({ path: "pageId", reason: "expected string" }); - } else { - pageId = raw.pageId; +export const pageTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Page { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let title: string = undefined as unknown as string; - if (raw.title === undefined || raw.title === null) { - violations.push({ path: "title", reason: "required" }); - } else { - if (typeof raw.title !== "string") { - violations.push({ path: "title", reason: "expected string" }); + let pageId: string = undefined as unknown as string; + if (raw.pageId === undefined || raw.pageId === null) { + violations.push({ path: "pageId", reason: "required" }); } else { - title = raw.title; + if (typeof raw.pageId !== "string") { + violations.push({ path: "pageId", reason: "expected string" }); + } else { + pageId = raw.pageId; + } } - } - let meta: PageMeta = undefined as unknown as PageMeta; - if (raw.meta === undefined || raw.meta === null) { - violations.push({ path: "meta", reason: "required" }); - } else { - try { - meta = new PageMetaMapper().fromIntermediate(raw.meta); - } catch (error) { - __nexgenDefinitions.collect(violations, "meta", error); + let title: string = undefined as unknown as string; + if (raw.title === undefined || raw.title === null) { + violations.push({ path: "title", reason: "required" }); + } else { + if (typeof raw.title !== "string") { + violations.push({ path: "title", reason: "expected string" }); + } else { + title = raw.title; + } } - } - let blocks: Block[] | undefined = undefined as unknown as Block[] | undefined; - if (raw.blocks === null) { - violations.push({ path: "blocks", reason: "explicit null not allowed" }); - } else if (raw.blocks !== undefined) { - if (!Array.isArray(raw.blocks)) { - violations.push({ path: "blocks", reason: "expected array" }); + let meta: PageMeta = undefined as unknown as PageMeta; + if (raw.meta === undefined || raw.meta === null) { + violations.push({ path: "meta", reason: "required" }); } else { - blocks = []; - raw.blocks.forEach((element: unknown, index: number) => { - let item: Block = undefined as unknown as Block; - try { - item = new BlockMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `blocks[${index}]`, error); - } - if (item !== undefined) { - blocks!.push(item); - } - }); + try { + meta = pageMetaTransferTypeConverter.fromTransferType(raw.meta); + } catch (error) { + __nexgenDefinitions.collect(violations, "meta", error); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "pageId" && key !== "title" && key !== "meta" && key !== "blocks") { - violations.push({ path: key, reason: "unknown field" }); + let blocks: Block[] | undefined = undefined as unknown as Block[] | undefined; + if (raw.blocks === null) { + violations.push({ path: "blocks", reason: "explicit null not allowed" }); + } else if (raw.blocks !== undefined) { + if (!Array.isArray(raw.blocks)) { + violations.push({ path: "blocks", reason: "expected array" }); + } else { + blocks = []; + raw.blocks.forEach((element: unknown, index: number) => { + let item: Block = undefined as unknown as Block; + try { + item = blockTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `blocks[${index}]`, error); + } + if (item !== undefined) { + blocks!.push(item); + } + }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Page = { pageId, title, meta }; - if (blocks !== undefined) { - out.blocks = blocks; - } - return out; - } - - public toIntermediate(value: Page): unknown { - const out: Record = {}; - out.pageId = value.pageId; - out.title = value.title; - out.meta = new PageMetaMapper().toIntermediate(value.meta); - if (value.blocks !== undefined) { - out.blocks = value.blocks.map((element) => - new BlockMapper().toIntermediate(element), - ); - } - return out; - } -} + for (const key of Object.keys(raw)) { + if (key !== "pageId" && key !== "title" && key !== "meta" && key !== "blocks") { + violations.push({ path: key, reason: "unknown field" }); + } + } -export class PageMetaMapper { - public fromIntermediate(raw: unknown): PageMeta { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Page = { pageId, title, meta }; + if (blocks !== undefined) { + out.blocks = blocks; + } + return out; + } + + public toTransferType(value: Page): unknown { + const out: Record = {}; + out.pageId = value.pageId; + out.title = value.title; + out.meta = pageMetaTransferTypeConverter.toTransferType(value.meta); + if (value.blocks !== undefined) { + out.blocks = value.blocks.map((element) => + blockTransferTypeConverter.toTransferType(element), + ); + } + return out; + } + })(); + +export const pageMetaTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): PageMeta { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let author: string = undefined as unknown as string; - if (raw.author === undefined || raw.author === null) { - violations.push({ path: "author", reason: "required" }); - } else { - if (typeof raw.author !== "string") { - violations.push({ path: "author", reason: "expected string" }); + let author: string = undefined as unknown as string; + if (raw.author === undefined || raw.author === null) { + violations.push({ path: "author", reason: "required" }); } else { - author = raw.author; + if (typeof raw.author !== "string") { + violations.push({ path: "author", reason: "expected string" }); + } else { + author = raw.author; + } } - } - let wordCount: number | undefined = undefined as unknown as number | undefined; - if (raw.wordCount === null) { - violations.push({ path: "wordCount", reason: "explicit null not allowed" }); - } else if (raw.wordCount !== undefined) { - if (typeof raw.wordCount !== "number" || !Number.isSafeInteger(raw.wordCount)) { - violations.push({ path: "wordCount", reason: "expected integer" }); - } else { - wordCount = raw.wordCount; + let wordCount: number | undefined = undefined as unknown as number | undefined; + if (raw.wordCount === null) { + violations.push({ path: "wordCount", reason: "explicit null not allowed" }); + } else if (raw.wordCount !== undefined) { + if (typeof raw.wordCount !== "number" || !Number.isSafeInteger(raw.wordCount)) { + violations.push({ path: "wordCount", reason: "expected integer" }); + } else { + wordCount = raw.wordCount; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "author" && key !== "wordCount") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "author" && key !== "wordCount") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: PageMeta = { author }; - if (wordCount !== undefined) { - out.wordCount = wordCount; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: PageMeta = { author }; + if (wordCount !== undefined) { + out.wordCount = wordCount; + } + return out; } - return out; - } - - public toIntermediate(value: PageMeta): unknown { - const out: Record = {}; - out.author = value.author; - if (value.wordCount !== undefined) { - out.wordCount = value.wordCount; + + public toTransferType(value: PageMeta): unknown { + const out: Record = {}; + out.author = value.author; + if (value.wordCount !== undefined) { + out.wordCount = value.wordCount; + } + return out; } - return out; - } -} + })(); diff --git a/samples/typescript/kb/tree/category/models.ts b/samples/typescript/kb/tree/category/models.ts index cb189cdb..120f0b65 100644 --- a/samples/typescript/kb/tree/category/models.ts +++ b/samples/typescript/kb/tree/category/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "../../definitions"; /** @@ -21,137 +22,142 @@ export interface Palette { swatches: string[]; } -export class CategoryMapper { - public fromIntermediate(raw: unknown): Category { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const categoryTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Category { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - id = raw.id; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - name = raw.name; + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; + } } - } - let children: Category[] | undefined = undefined as unknown as - | Category[] - | undefined; - if (raw.children === null) { - violations.push({ path: "children", reason: "explicit null not allowed" }); - } else if (raw.children !== undefined) { - if (!Array.isArray(raw.children)) { - violations.push({ path: "children", reason: "expected array" }); - } else { - children = []; - raw.children.forEach((element: unknown, index: number) => { - let item: Category = undefined as unknown as Category; - try { - item = new CategoryMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `children[${index}]`, error); - } - if (item !== undefined) { - children!.push(item); - } - }); + let children: Category[] | undefined = undefined as unknown as + | Category[] + | undefined; + if (raw.children === null) { + violations.push({ path: "children", reason: "explicit null not allowed" }); + } else if (raw.children !== undefined) { + if (!Array.isArray(raw.children)) { + violations.push({ path: "children", reason: "expected array" }); + } else { + children = []; + raw.children.forEach((element: unknown, index: number) => { + let item: Category = undefined as unknown as Category; + try { + item = categoryTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `children[${index}]`, error); + } + if (item !== undefined) { + children!.push(item); + } + }); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "id" && key !== "name" && key !== "children") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "id" && key !== "name" && key !== "children") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Category = { id, name }; - if (children !== undefined) { - out.children = children; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Category = { id, name }; + if (children !== undefined) { + out.children = children; + } + return out; } - return out; - } - public toIntermediate(value: Category): unknown { - const out: Record = {}; - out.id = value.id; - out.name = value.name; - if (value.children !== undefined) { - out.children = value.children.map((element) => - new CategoryMapper().toIntermediate(element), - ); + public toTransferType(value: Category): unknown { + const out: Record = {}; + out.id = value.id; + out.name = value.name; + if (value.children !== undefined) { + out.children = value.children.map((element) => + categoryTransferTypeConverter.toTransferType(element), + ); + } + return out; } - return out; - } -} + })(); -export class PaletteMapper { - public fromIntermediate(raw: unknown): Palette { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const paletteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Palette { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let swatches: string[] = undefined as unknown as string[]; - if (raw.swatches === undefined || raw.swatches === null) { - violations.push({ path: "swatches", reason: "required" }); - } else { - if (!Array.isArray(raw.swatches)) { - violations.push({ path: "swatches", reason: "expected array" }); + let swatches: string[] = undefined as unknown as string[]; + if (raw.swatches === undefined || raw.swatches === null) { + violations.push({ path: "swatches", reason: "required" }); } else { - swatches = []; - raw.swatches.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `swatches[${index}]`, reason: "expected element" }); - } else { - item = element; - } - if (item !== undefined) { - swatches!.push(item); - } - }); + if (!Array.isArray(raw.swatches)) { + violations.push({ path: "swatches", reason: "expected array" }); + } else { + swatches = []; + raw.swatches.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ + path: `swatches[${index}]`, + reason: "expected element", + }); + } else { + item = element; + } + if (item !== undefined) { + swatches!.push(item); + } + }); + } } - } - for (const key of Object.keys(raw)) { - if (key !== "swatches") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "swatches") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Palette = { swatches }; + return out; } - const out: Palette = { swatches }; - return out; - } - public toIntermediate(value: Palette): unknown { - const out: Record = {}; - out.swatches = value.swatches; - return out; - } -} + public toTransferType(value: Palette): unknown { + const out: Record = {}; + out.swatches = value.swatches; + return out; + } + })(); diff --git a/samples/typescript/showcase/models.ts b/samples/typescript/showcase/models.ts index 79d20e11..d4b5d9a0 100644 --- a/samples/typescript/showcase/models.ts +++ b/samples/typescript/showcase/models.ts @@ -1,5 +1,6 @@ // Generated by nexgen. DO NOT EDIT! +import type { TransferTypeConverter } from "nexus-rpc"; import * as __nexgenDefinitions from "./definitions"; const CIRCLE_KIND_CONST = "circle"; @@ -454,7 +455,7 @@ export interface WidgetBase { function serializeShowcaseDetail(value: ShowcaseDetailObject | string): unknown { if (__nexgenDefinitions.isPlainObject(value)) { - return new ShowcaseDetailObjectMapper().toIntermediate( + return showcaseDetailObjectTransferTypeConverter.toTransferType( value as unknown as ShowcaseDetailObject, ); } @@ -468,10 +469,10 @@ function serializeShowcaseDetail(value: ShowcaseDetailObject | string): unknown function serializeShowcaseShapeOrName(value: Circle | Square | string): unknown { if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); + return circleTransferTypeConverter.toTransferType(value as Circle); } if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + return squareTransferTypeConverter.toTransferType(value as Square); } if (typeof value === "string") { return value; @@ -483,3684 +484,3767 @@ function serializeShowcaseShapeOrName(value: Circle | Square | string): unknown const ADDRESS_DECLARED = new Set(["street", "city", "zip"]); -export class AddressMapper { - public fromIntermediate(raw: unknown): Address { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const addressTransferTypeConverter = + new (class implements TransferTypeConverter
{ + public fromTransferType(raw: unknown): Address { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let street: string = undefined as unknown as string; - if (raw.street === undefined || raw.street === null) { - violations.push({ path: "street", reason: "required" }); - } else { - if (typeof raw.street !== "string") { - violations.push({ path: "street", reason: "expected string" }); + let street: string = undefined as unknown as string; + if (raw.street === undefined || raw.street === null) { + violations.push({ path: "street", reason: "required" }); } else { - street = raw.street; + if (typeof raw.street !== "string") { + violations.push({ path: "street", reason: "expected string" }); + } else { + street = raw.street; + } } - } - let city: string | undefined = undefined as unknown as string | undefined; - if (raw.city === null) { - violations.push({ path: "city", reason: "explicit null not allowed" }); - } else if (raw.city !== undefined) { - if (typeof raw.city !== "string") { - violations.push({ path: "city", reason: "expected string" }); - } else { - city = raw.city; + let city: string | undefined = undefined as unknown as string | undefined; + if (raw.city === null) { + violations.push({ path: "city", reason: "explicit null not allowed" }); + } else if (raw.city !== undefined) { + if (typeof raw.city !== "string") { + violations.push({ path: "city", reason: "expected string" }); + } else { + city = raw.city; + } } - } - let zip: number | undefined = undefined as unknown as number | undefined; - if (raw.zip === null) { - violations.push({ path: "zip", reason: "explicit null not allowed" }); - } else if (raw.zip !== undefined) { - if (typeof raw.zip !== "number" || !Number.isSafeInteger(raw.zip)) { - violations.push({ path: "zip", reason: "expected integer" }); - } else { - zip = raw.zip; + let zip: number | undefined = undefined as unknown as number | undefined; + if (raw.zip === null) { + violations.push({ path: "zip", reason: "explicit null not allowed" }); + } else if (raw.zip !== undefined) { + if (typeof raw.zip !== "number" || !Number.isSafeInteger(raw.zip)) { + violations.push({ path: "zip", reason: "expected integer" }); + } else { + zip = raw.zip; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!ADDRESS_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!ADDRESS_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Address = { street, additionalProperties }; - if (city !== undefined) { - out.city = city; - } - if (zip !== undefined) { - out.zip = zip; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Address = { street, additionalProperties }; + if (city !== undefined) { + out.city = city; + } + if (zip !== undefined) { + out.zip = zip; + } + return out; } - return out; - } - public toIntermediate(value: Address): unknown { - const out: Record = {}; - out.street = value.street; - if (value.city !== undefined) { - out.city = value.city; - } - if (value.zip !== undefined) { - out.zip = value.zip; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; + public toTransferType(value: Address): unknown { + const out: Record = {}; + out.street = value.street; + if (value.city !== undefined) { + out.city = value.city; + } + if (value.zip !== undefined) { + out.zip = value.zip; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - return out; - } -} + })(); -export class AttributesMapper { - public fromIntermediate(raw: unknown): Attributes { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const attributesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Attributes { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - if (keys.length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${keys.length}`, - }); - } - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - for (const key of keys) { - if ([...key].length > 8) { + const keys = Object.keys(raw); + if (keys.length < 1) { violations.push({ - path: key, - reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + path: "", + reason: `must have at least 1 properties, got ${keys.length}`, }); } - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; + if (keys.length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${keys.length}`, + }); + } + for (const key of keys) { + if ([...key].length > 8) { + violations.push({ + path: key, + reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + }); + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Attributes): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${keys.length}`, - }); - } - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - for (const key of keys) { - if ([...key].length > 8) { + public toTransferType(value: Attributes): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length < 1) { violations.push({ - path: key, - reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + path: "", + reason: `must have at least 1 properties, got ${keys.length}`, }); } + if (keys.length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${keys.length}`, + }); + } + for (const key of keys) { + if ([...key].length > 8) { + violations.push({ + path: key, + reason: `invalid property name "${key}": must have length <= 8, got ${[...key].length}`, + }); + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + })(); -export class ChoicesMapper { - public fromIntermediate(raw: unknown): Choices { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const choicesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Choices { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: ChoicesValue | undefined = undefined; - try { - entry = new ChoicesValueMapper().fromIntermediate(raw[key]); - } catch (error) { - __nexgenDefinitions.collect(violations, key, error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: ChoicesValue | undefined = undefined; + try { + entry = choicesValueTransferTypeConverter.fromTransferType(raw[key]); + } catch (error) { + __nexgenDefinitions.collect(violations, key, error); + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Choices): unknown { - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = new ChoicesValueMapper().toIntermediate(entry); + public toTransferType(value: Choices): unknown { + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = choicesValueTransferTypeConverter.toTransferType(entry); + } + return out; } - return out; - } -} + })(); -export class ChoicesValueMapper { - public fromIntermediate(raw: unknown): ChoicesValue { - const violations: __nexgenDefinitions.Violation[] = []; - let out: ChoicesValue = undefined as unknown as ChoicesValue; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "circle": - try { - out = new CircleMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - case "square": - try { - out = new SquareMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, - }); +export const choicesValueTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ChoicesValue { + const violations: __nexgenDefinitions.Violation[] = []; + let out: ChoicesValue = undefined as unknown as ChoicesValue; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "circle": + try { + out = circleTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "square": + try { + out = squareTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, + }); + } + } else { + violations.push({ path: "", reason: "expected one of: Circle, Square" }); } - } else { - violations.push({ path: "", reason: "expected one of: Circle, Square" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } - public toIntermediate(value: ChoicesValue): unknown { - if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); - } - if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + public toTransferType(value: ChoicesValue): unknown { + if ((value as unknown as Record)["kind"] === "circle") { + return circleTransferTypeConverter.toTransferType(value as Circle); + } + if ((value as unknown as Record)["kind"] === "square") { + return squareTransferTypeConverter.toTransferType(value as Square); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: Circle, Square" }, + ]); } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: Circle, Square" }, - ]); - } -} + })(); const CIRCLE_DECLARED = new Set(["kind", "radius"]); -export class CircleMapper { - public fromIntermediate(raw: unknown): Circle { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const circleTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Circle { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "circle" = undefined as unknown as "circle"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== CIRCLE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "circle"` }); + let kind: "circle" = undefined as unknown as "circle"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "circle"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== CIRCLE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "circle"` }); + } else { + kind = raw.kind as "circle"; + } } - } - let radius: number = undefined as unknown as number; - if (raw.radius === undefined || raw.radius === null) { - violations.push({ path: "radius", reason: "required" }); - } else { - if (typeof raw.radius !== "number") { - violations.push({ path: "radius", reason: "expected number" }); + let radius: number = undefined as unknown as number; + if (raw.radius === undefined || raw.radius === null) { + violations.push({ path: "radius", reason: "required" }); } else { - radius = raw.radius; + if (typeof raw.radius !== "number") { + violations.push({ path: "radius", reason: "expected number" }); + } else { + radius = raw.radius; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!CIRCLE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!CIRCLE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Circle = { kind, radius, additionalProperties }; + return out; } - const out: Circle = { kind, radius, additionalProperties }; - return out; - } - public toIntermediate(value: Circle): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "circle") { - violations.push({ path: "kind", reason: `must equal "circle"` }); - } - out.kind = value.kind; - out.radius = value.radius; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Circle): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "circle") { + violations.push({ path: "kind", reason: `must equal "circle"` }); + } + out.kind = value.kind; + out.radius = value.radius; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const CONTACT_TS_DECLARED = new Set(["email", "shippingStreet", "shippingZip"]); -export class ContactTsMapper { - public fromIntermediate(raw: unknown): ContactTs { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let email: string | undefined = undefined as unknown as string | undefined; - if (raw.email === null) { - violations.push({ path: "email", reason: "explicit null not allowed" }); - } else if (raw.email !== undefined) { - if (typeof raw.email !== "string") { - violations.push({ path: "email", reason: "expected string" }); - } else { - email = raw.email; +export const contactTsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ContactTs { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let email: string | undefined = undefined as unknown as string | undefined; + if (raw.email === null) { + violations.push({ path: "email", reason: "explicit null not allowed" }); + } else if (raw.email !== undefined) { + if (typeof raw.email !== "string") { + violations.push({ path: "email", reason: "expected string" }); + } else { + email = raw.email; + } } - } - let shippingStreet: string | undefined = undefined as unknown as string | undefined; - if (raw.shippingStreet === null) { - violations.push({ path: "shippingStreet", reason: "explicit null not allowed" }); - } else if (raw.shippingStreet !== undefined) { - if (typeof raw.shippingStreet !== "string") { - violations.push({ path: "shippingStreet", reason: "expected string" }); - } else { - shippingStreet = raw.shippingStreet; + let shippingStreet: string | undefined = undefined as unknown as + | string + | undefined; + if (raw.shippingStreet === null) { + violations.push({ + path: "shippingStreet", + reason: "explicit null not allowed", + }); + } else if (raw.shippingStreet !== undefined) { + if (typeof raw.shippingStreet !== "string") { + violations.push({ path: "shippingStreet", reason: "expected string" }); + } else { + shippingStreet = raw.shippingStreet; + } } - } - let shippingZip: string | undefined = undefined as unknown as string | undefined; - if (raw.shippingZip === null) { - violations.push({ path: "shippingZip", reason: "explicit null not allowed" }); - } else if (raw.shippingZip !== undefined) { - if (typeof raw.shippingZip !== "string") { - violations.push({ path: "shippingZip", reason: "expected string" }); - } else { - shippingZip = raw.shippingZip; + let shippingZip: string | undefined = undefined as unknown as string | undefined; + if (raw.shippingZip === null) { + violations.push({ path: "shippingZip", reason: "explicit null not allowed" }); + } else if (raw.shippingZip !== undefined) { + if (typeof raw.shippingZip !== "string") { + violations.push({ path: "shippingZip", reason: "expected string" }); + } else { + shippingZip = raw.shippingZip; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!CONTACT_TS_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!CONTACT_TS_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (Object.keys(raw).length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${Object.keys(raw).length}`, - }); - } - if (Object.keys(raw).length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${Object.keys(raw).length}`, - }); - } - if (raw["shippingStreet"] !== undefined) { - if (raw["shippingZip"] === undefined) { + if (Object.keys(raw).length < 1) { violations.push({ - path: "shippingZip", - reason: `property "shippingZip" is required when "shippingStreet" is present`, + path: "", + reason: `must have at least 1 properties, got ${Object.keys(raw).length}`, }); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ContactTs = { additionalProperties }; - if (email !== undefined) { - out.email = email; - } - if (shippingStreet !== undefined) { - out.shippingStreet = shippingStreet; - } - if (shippingZip !== undefined) { - out.shippingZip = shippingZip; - } - return out; - } - - public toIntermediate(value: ContactTs): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.email !== undefined) { - out.email = value.email; - } - if (value.shippingStreet !== undefined) { - out.shippingStreet = value.shippingStreet; - } - if (value.shippingZip !== undefined) { - out.shippingZip = value.shippingZip; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (Object.keys(out).length < 1) { - violations.push({ - path: "", - reason: `must have at least 1 properties, got ${Object.keys(out).length}`, - }); - } - if (Object.keys(out).length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${Object.keys(out).length}`, - }); - } - if (out["shippingStreet"] !== undefined) { - if (out["shippingZip"] === undefined) { + if (Object.keys(raw).length > 3) { violations.push({ - path: "shippingZip", - reason: `property "shippingZip" is required when "shippingStreet" is present`, + path: "", + reason: `must have at most 3 properties, got ${Object.keys(raw).length}`, }); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class ExtrasMapper { - public fromIntermediate(raw: unknown): Extras { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 4) { - violations.push({ - path: "", - reason: `must have at most 4 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - additionalProperties[key] = raw[key]; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Extras): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 4) { - violations.push({ - path: "", - reason: `must have at most 4 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class LabelsMapper { - public fromIntermediate(raw: unknown): Labels { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; + if (raw["shippingStreet"] !== undefined) { + if (raw["shippingZip"] === undefined) { + violations.push({ + path: "shippingZip", + reason: `property "shippingZip" is required when "shippingStreet" is present`, + }); + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Labels): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 50) { - violations.push({ - path: "", - reason: `must have at most 50 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -const LINK_NOTE_DECLARED = new Set(["kind", "href"]); - -export class LinkNoteMapper { - public fromIntermediate(raw: unknown): LinkNote { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let kind: "link" = undefined as unknown as "link"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== LINK_NOTE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "link"` }); - } else { - kind = raw.kind as "link"; + const out: ContactTs = { additionalProperties }; + if (email !== undefined) { + out.email = email; + } + if (shippingStreet !== undefined) { + out.shippingStreet = shippingStreet; } + if (shippingZip !== undefined) { + out.shippingZip = shippingZip; + } + return out; } - let href: string = undefined as unknown as string; - if (raw.href === undefined || raw.href === null) { - violations.push({ path: "href", reason: "required" }); - } else { - if (typeof raw.href !== "string") { - violations.push({ path: "href", reason: "expected string" }); - } else { - href = raw.href; - if ([...raw.href].length < 1) { + public toTransferType(value: ContactTs): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.email !== undefined) { + out.email = value.email; + } + if (value.shippingStreet !== undefined) { + out.shippingStreet = value.shippingStreet; + } + if (value.shippingZip !== undefined) { + out.shippingZip = value.shippingZip; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (Object.keys(out).length < 1) { + violations.push({ + path: "", + reason: `must have at least 1 properties, got ${Object.keys(out).length}`, + }); + } + if (Object.keys(out).length > 3) { + violations.push({ + path: "", + reason: `must have at most 3 properties, got ${Object.keys(out).length}`, + }); + } + if (out["shippingStreet"] !== undefined) { + if (out["shippingZip"] === undefined) { violations.push({ - path: "href", - reason: `must have length >= 1, got ${[...raw.href].length}`, + path: "shippingZip", + reason: `property "shippingZip" is required when "shippingStreet" is present`, }); } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } + })(); + +export const extrasTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Extras { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!LINK_NOTE_DECLARED.has(key)) { + const keys = Object.keys(raw); + if (keys.length > 4) { + violations.push({ + path: "", + reason: `must have at most 4 properties, got ${keys.length}`, + }); + } + const additionalProperties: Record = {}; + for (const key of keys) { additionalProperties[key] = raw[key]; } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: LinkNote = { kind, href, additionalProperties }; - return out; - } - - public toIntermediate(value: LinkNote): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "link") { - violations.push({ path: "kind", reason: `must equal "link"` }); - } - out.kind = value.kind; - if ([...value.href].length < 1) { - violations.push({ - path: "href", - reason: `must have length >= 1, got ${[...value.href].length}`, - }); - } - out.href = value.href; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Extras): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 4) { + violations.push({ + path: "", + reason: `must have at most 4 properties, got ${keys.length}`, + }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class NicknamesMapper { - public fromIntermediate(raw: unknown): Nicknames { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const labelsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Labels { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | null | undefined = undefined; - if (raw[key] === null) { - entry = null; - } else { + const keys = Object.keys(raw); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); + } + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; if (typeof raw[key] !== "string") { violations.push({ path: key, reason: "expected string" }); } else { entry = raw[key]; - if ([...raw[key]].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...raw[key]].length}`, - }); - } + } + if (entry !== undefined) { + additionalProperties[key] = entry; } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Nicknames): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if (entry !== null) { - if ([...entry].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...entry].length}`, - }); - } + public toTransferType(value: Labels): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; } - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -export class NoteMapper { - public fromIntermediate(raw: unknown): Note { - const violations: __nexgenDefinitions.Violation[] = []; - let out: Note = undefined as unknown as Note; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "text": - try { - out = new TextNoteMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - case "link": - try { - out = new LinkNoteMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); - } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["text", "link"]`, - }); + const keys = Object.keys(out); + if (keys.length > 50) { + violations.push({ + path: "", + reason: `must have at most 50 properties, got ${keys.length}`, + }); } - } else { - violations.push({ path: "", reason: "expected one of: TextNote, LinkNote" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } + })(); - public toIntermediate(value: Note): unknown { - if ((value as unknown as Record)["kind"] === "text") { - return new TextNoteMapper().toIntermediate(value as TextNote); - } - if ((value as unknown as Record)["kind"] === "link") { - return new LinkNoteMapper().toIntermediate(value as LinkNote); - } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: TextNote, LinkNote" }, - ]); - } -} +const LINK_NOTE_DECLARED = new Set(["kind", "href"]); -export class QuotasMapper { - public fromIntermediate(raw: unknown): Quotas { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const linkNoteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): LinkNote { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: number | undefined = undefined; - if (typeof raw[key] !== "number" || !Number.isSafeInteger(raw[key])) { - violations.push({ path: key, reason: "expected integer" }); + let kind: "link" = undefined as unknown as "link"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - entry = raw[key]; - if (raw[key] < 0) { - violations.push({ path: key, reason: `must be >= 0, got ${raw[key]}` }); + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== LINK_NOTE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "link"` }); + } else { + kind = raw.kind as "link"; } - if (raw[key] > 100) { - violations.push({ path: key, reason: `must be <= 100, got ${raw[key]}` }); + } + + let href: string = undefined as unknown as string; + if (raw.href === undefined || raw.href === null) { + violations.push({ path: "href", reason: "required" }); + } else { + if (typeof raw.href !== "string") { + violations.push({ path: "href", reason: "expected string" }); + } else { + href = raw.href; + if ([...raw.href].length < 1) { + violations.push({ + path: "href", + reason: `must have length >= 1, got ${[...raw.href].length}`, + }); + } } - if (raw[key] % 5 !== 0) { - violations.push({ - path: key, - reason: `must be a multiple of 5, got ${raw[key]}`, - }); + } + + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!LINK_NOTE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; } } - if (entry !== undefined) { - additionalProperties[key] = entry; + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + const out: LinkNote = { kind, href, additionalProperties }; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: Quotas): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if (entry < 0) { - violations.push({ path: key, reason: `must be >= 0, got ${entry}` }); + public toTransferType(value: LinkNote): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "link") { + violations.push({ path: "kind", reason: `must equal "link"` }); } - if (entry > 100) { - violations.push({ path: key, reason: `must be <= 100, got ${entry}` }); + out.kind = value.kind; + if ([...value.href].length < 1) { + violations.push({ + path: "href", + reason: `must have length >= 1, got ${[...value.href].length}`, + }); } - if (entry % 5 !== 0) { - violations.push({ path: key, reason: `must be a multiple of 5, got ${entry}` }); + out.href = value.href; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; } - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class SettingsMapper { - public fromIntermediate(raw: unknown): Settings { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const nicknamesTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Nicknames { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let theme: string | undefined = undefined as unknown as string | undefined; - if (raw.theme === null) { - violations.push({ path: "theme", reason: "explicit null not allowed" }); - } else if (raw.theme !== undefined) { - if (typeof raw.theme !== "string") { - violations.push({ path: "theme", reason: "expected string" }); - } else { - theme = raw.theme; + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | null | undefined = undefined; + if (raw[key] === null) { + entry = null; + } else { + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + if ([...raw[key]].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...raw[key]].length}`, + }); + } + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - let fontSize: number | undefined = undefined as unknown as number | undefined; - if (raw.fontSize === null) { - violations.push({ path: "fontSize", reason: "explicit null not allowed" }); - } else if (raw.fontSize !== undefined) { - if (typeof raw.fontSize !== "number" || !Number.isSafeInteger(raw.fontSize)) { - violations.push({ path: "fontSize", reason: "expected integer" }); - } else { - fontSize = raw.fontSize; + public toTransferType(value: Nicknames): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if (entry !== null) { + if ([...entry].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...entry].length}`, + }); + } + } + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } + })(); - for (const key of Object.keys(raw)) { - if (key !== "theme" && key !== "fontSize") { - violations.push({ path: key, reason: "unknown field" }); +export const noteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Note { + const violations: __nexgenDefinitions.Violation[] = []; + let out: Note = undefined as unknown as Note; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "text": + try { + out = textNoteTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "link": + try { + out = linkNoteTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["text", "link"]`, + }); + } + } else { + violations.push({ path: "", reason: "expected one of: TextNote, LinkNote" }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Settings = {}; - if (theme !== undefined) { - out.theme = theme; - } - if (fontSize !== undefined) { - out.fontSize = fontSize; + public toTransferType(value: Note): unknown { + if ((value as unknown as Record)["kind"] === "text") { + return textNoteTransferTypeConverter.toTransferType(value as TextNote); + } + if ((value as unknown as Record)["kind"] === "link") { + return linkNoteTransferTypeConverter.toTransferType(value as LinkNote); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: TextNote, LinkNote" }, + ]); } - return out; - } + })(); - public toIntermediate(value: Settings): unknown { - const out: Record = {}; - if (value.theme !== undefined) { - out.theme = value.theme; - } - if (value.fontSize !== undefined) { - out.fontSize = value.fontSize; - } - return out; - } -} +export const quotasTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Quotas { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } -export class ShapeMapper { - public fromIntermediate(raw: unknown): Shape { - const violations: __nexgenDefinitions.Violation[] = []; - let out: Shape = undefined as unknown as Shape; - if (__nexgenDefinitions.isPlainObject(raw)) { - switch ((raw as Record)["kind"]) { - case "circle": - try { - out = new CircleMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: number | undefined = undefined; + if (typeof raw[key] !== "number" || !Number.isSafeInteger(raw[key])) { + violations.push({ path: key, reason: "expected integer" }); + } else { + entry = raw[key]; + if (raw[key] < 0) { + violations.push({ path: key, reason: `must be >= 0, got ${raw[key]}` }); } - break; - case "square": - try { - out = new SquareMapper().fromIntermediate(raw); - } catch (error) { - __nexgenDefinitions.collect(violations, "", error); + if (raw[key] > 100) { + violations.push({ path: key, reason: `must be <= 100, got ${raw[key]}` }); } - break; - default: - violations.push({ - path: "", - reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, - }); + if (raw[key] % 5 !== 0) { + violations.push({ + path: key, + reason: `must be a multiple of 5, got ${raw[key]}`, + }); + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - } else { - violations.push({ path: "", reason: "expected one of: Circle, Square" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - return out; - } - public toIntermediate(value: Shape): unknown { - if ((value as unknown as Record)["kind"] === "circle") { - return new CircleMapper().toIntermediate(value as Circle); - } - if ((value as unknown as Record)["kind"] === "square") { - return new SquareMapper().toIntermediate(value as Square); + public toTransferType(value: Quotas): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if (entry < 0) { + violations.push({ path: key, reason: `must be >= 0, got ${entry}` }); + } + if (entry > 100) { + violations.push({ path: key, reason: `must be <= 100, got ${entry}` }); + } + if (entry % 5 !== 0) { + violations.push({ + path: key, + reason: `must be a multiple of 5, got ${entry}`, + }); + } + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: Circle, Square" }, - ]); - } -} + })(); -export class ShowcaseMapper { - public fromIntermediate(raw: unknown): Showcase { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const settingsTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Settings { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "showcase" = undefined as unknown as "showcase"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== SHOWCASE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "showcase"` }); - } else { - kind = raw.kind as "showcase"; + let theme: string | undefined = undefined as unknown as string | undefined; + if (raw.theme === null) { + violations.push({ path: "theme", reason: "explicit null not allowed" }); + } else if (raw.theme !== undefined) { + if (typeof raw.theme !== "string") { + violations.push({ path: "theme", reason: "expected string" }); + } else { + theme = raw.theme; + } } - } - let revision: 1 = undefined as unknown as 1; - if (raw.revision === undefined || raw.revision === null) { - violations.push({ path: "revision", reason: "required" }); - } else { - if (typeof raw.revision !== "number") { - violations.push({ path: "revision", reason: "expected number" }); - } else if (raw.revision !== REVISION_CONST) { - violations.push({ path: "revision", reason: `must equal 1` }); - } else { - revision = raw.revision as 1; + let fontSize: number | undefined = undefined as unknown as number | undefined; + if (raw.fontSize === null) { + violations.push({ path: "fontSize", reason: "explicit null not allowed" }); + } else if (raw.fontSize !== undefined) { + if (typeof raw.fontSize !== "number" || !Number.isSafeInteger(raw.fontSize)) { + violations.push({ path: "fontSize", reason: "expected integer" }); + } else { + fontSize = raw.fontSize; + } } - } - let enabled: true = undefined as unknown as true; - if (raw.enabled === undefined || raw.enabled === null) { - violations.push({ path: "enabled", reason: "required" }); - } else { - if (typeof raw.enabled !== "boolean") { - violations.push({ path: "enabled", reason: "expected boolean" }); - } else if (raw.enabled !== ENABLED_CONST) { - violations.push({ path: "enabled", reason: `must equal true` }); - } else { - enabled = raw.enabled as true; + for (const key of Object.keys(raw)) { + if (key !== "theme" && key !== "fontSize") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - let status: "active" | "inactive" | "pending" = undefined as unknown as - | "active" - | "inactive" - | "pending"; - if (raw.status === undefined || raw.status === null) { - violations.push({ path: "status", reason: "required" }); - } else { - if (typeof raw.status !== "string") { - violations.push({ path: "status", reason: "expected string" }); - } else if ( - raw.status !== "active" && - raw.status !== "inactive" && - raw.status !== "pending" - ) { - violations.push({ - path: "status", - reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(raw.status)}`, - }); - } else { - status = raw.status as "active" | "inactive" | "pending"; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Settings = {}; + if (theme !== undefined) { + out.theme = theme; + } + if (fontSize !== undefined) { + out.fontSize = fontSize; } + return out; } - let tier: 1 | 2 | 3 = undefined as unknown as 1 | 2 | 3; - if (raw.tier === undefined || raw.tier === null) { - violations.push({ path: "tier", reason: "required" }); - } else { - if (typeof raw.tier !== "number") { - violations.push({ path: "tier", reason: "expected number" }); - } else if (raw.tier !== 1 && raw.tier !== 2 && raw.tier !== 3) { - violations.push({ - path: "tier", - reason: `must be one of [1, 2, 3], got ${JSON.stringify(raw.tier)}`, - }); - } else { - tier = raw.tier as 1 | 2 | 3; + public toTransferType(value: Settings): unknown { + const out: Record = {}; + if (value.theme !== undefined) { + out.theme = value.theme; } + if (value.fontSize !== undefined) { + out.fontSize = value.fontSize; + } + return out; } + })(); - let scale: 1.5 | 2.5 = undefined as unknown as 1.5 | 2.5; - if (raw.scale === undefined || raw.scale === null) { - violations.push({ path: "scale", reason: "required" }); - } else { - if (typeof raw.scale !== "number") { - violations.push({ path: "scale", reason: "expected number" }); - } else if (raw.scale !== 1.5 && raw.scale !== 2.5) { - violations.push({ - path: "scale", - reason: `must be one of [1.5, 2.5], got ${JSON.stringify(raw.scale)}`, - }); +export const shapeTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Shape { + const violations: __nexgenDefinitions.Violation[] = []; + let out: Shape = undefined as unknown as Shape; + if (__nexgenDefinitions.isPlainObject(raw)) { + switch ((raw as Record)["kind"]) { + case "circle": + try { + out = circleTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + case "square": + try { + out = squareTransferTypeConverter.fromTransferType(raw); + } catch (error) { + __nexgenDefinitions.collect(violations, "", error); + } + break; + default: + violations.push({ + path: "", + reason: `unknown discriminator kind ${String((raw as Record)["kind"])}: expected one of ["circle", "square"]`, + }); + } } else { - scale = raw.scale as 1.5 | 2.5; + violations.push({ path: "", reason: "expected one of: Circle, Square" }); } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); - } else { - name = raw.name; - if ([...raw.name].length < 1) { - violations.push({ - path: "name", - reason: `must have length >= 1, got ${[...raw.name].length}`, - }); - } - if ([...raw.name].length > 64) { - violations.push({ - path: "name", - reason: `must have length <= 64, got ${[...raw.name].length}`, - }); - } + public toTransferType(value: Shape): unknown { + if ((value as unknown as Record)["kind"] === "circle") { + return circleTransferTypeConverter.toTransferType(value as Circle); } + if ((value as unknown as Record)["kind"] === "square") { + return squareTransferTypeConverter.toTransferType(value as Square); + } + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected one of: Circle, Square" }, + ]); } + })(); - let count: number = undefined as unknown as number; - if (raw.count === undefined || raw.count === null) { - violations.push({ path: "count", reason: "required" }); - } else { - if (typeof raw.count !== "number" || !Number.isSafeInteger(raw.count)) { - violations.push({ path: "count", reason: "expected integer" }); - } else { - count = raw.count; +export const showcaseTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Showcase { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let active: boolean = undefined as unknown as boolean; - if (raw.active === undefined || raw.active === null) { - violations.push({ path: "active", reason: "required" }); - } else { - if (typeof raw.active !== "boolean") { - violations.push({ path: "active", reason: "expected boolean" }); + let kind: "showcase" = undefined as unknown as "showcase"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - active = raw.active; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== SHOWCASE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "showcase"` }); + } else { + kind = raw.kind as "showcase"; + } } - } - let nickname: string | undefined = undefined as unknown as string | undefined; - if (raw.nickname === null) { - violations.push({ path: "nickname", reason: "explicit null not allowed" }); - } else if (raw.nickname !== undefined) { - if (typeof raw.nickname !== "string") { - violations.push({ path: "nickname", reason: "expected string" }); + let revision: 1 = undefined as unknown as 1; + if (raw.revision === undefined || raw.revision === null) { + violations.push({ path: "revision", reason: "required" }); } else { - nickname = raw.nickname; - if ([...raw.nickname].length > 12) { - violations.push({ - path: "nickname", - reason: `must have length <= 12, got ${[...raw.nickname].length}`, - }); + if (typeof raw.revision !== "number") { + violations.push({ path: "revision", reason: "expected number" }); + } else if (raw.revision !== REVISION_CONST) { + violations.push({ path: "revision", reason: `must equal 1` }); + } else { + revision = raw.revision as 1; } } - } - let code: string | undefined = undefined as unknown as string | undefined; - if (raw.code === null) { - violations.push({ path: "code", reason: "explicit null not allowed" }); - } else if (raw.code !== undefined) { - if (typeof raw.code !== "string") { - violations.push({ path: "code", reason: "expected string" }); + let enabled: true = undefined as unknown as true; + if (raw.enabled === undefined || raw.enabled === null) { + violations.push({ path: "enabled", reason: "required" }); } else { - code = raw.code; - if ([...raw.code].length < 2) { - violations.push({ - path: "code", - reason: `must have length >= 2, got ${[...raw.code].length}`, - }); - } - if ([...raw.code].length > 5) { - violations.push({ - path: "code", - reason: `must have length <= 5, got ${[...raw.code].length}`, - }); + if (typeof raw.enabled !== "boolean") { + violations.push({ path: "enabled", reason: "expected boolean" }); + } else if (raw.enabled !== ENABLED_CONST) { + violations.push({ path: "enabled", reason: `must equal true` }); + } else { + enabled = raw.enabled as true; } } - } - let sku: string | undefined = undefined as unknown as string | undefined; - if (raw.sku === null) { - violations.push({ path: "sku", reason: "explicit null not allowed" }); - } else if (raw.sku !== undefined) { - if (typeof raw.sku !== "string") { - violations.push({ path: "sku", reason: "expected string" }); + let status: "active" | "inactive" | "pending" = undefined as unknown as + | "active" + | "inactive" + | "pending"; + if (raw.status === undefined || raw.status === null) { + violations.push({ path: "status", reason: "required" }); } else { - sku = raw.sku; - if (!PATTERN_821EF753B4B37A85.test(raw.sku)) { + if (typeof raw.status !== "string") { + violations.push({ path: "status", reason: "expected string" }); + } else if ( + raw.status !== "active" && + raw.status !== "inactive" && + raw.status !== "pending" + ) { violations.push({ - path: "sku", - reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(raw.sku)}`, + path: "status", + reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(raw.status)}`, }); + } else { + status = raw.status as "active" | "inactive" | "pending"; } } - } - let phrase: string | undefined = undefined as unknown as string | undefined; - if (raw.phrase === null) { - violations.push({ path: "phrase", reason: "explicit null not allowed" }); - } else if (raw.phrase !== undefined) { - if (typeof raw.phrase !== "string") { - violations.push({ path: "phrase", reason: "expected string" }); + let tier: 1 | 2 | 3 = undefined as unknown as 1 | 2 | 3; + if (raw.tier === undefined || raw.tier === null) { + violations.push({ path: "tier", reason: "required" }); } else { - phrase = raw.phrase; - if (!PATTERN_AF8AB992526D6283.test(raw.phrase)) { + if (typeof raw.tier !== "number") { + violations.push({ path: "tier", reason: "expected number" }); + } else if (raw.tier !== 1 && raw.tier !== 2 && raw.tier !== 3) { violations.push({ - path: "phrase", - reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(raw.phrase)}`, + path: "tier", + reason: `must be one of [1, 2, 3], got ${JSON.stringify(raw.tier)}`, }); + } else { + tier = raw.tier as 1 | 2 | 3; } } - } - let requestId: string | undefined = undefined as unknown as string | undefined; - if (raw.requestId === null) { - violations.push({ path: "requestId", reason: "explicit null not allowed" }); - } else if (raw.requestId !== undefined) { - if (typeof raw.requestId !== "string") { - violations.push({ path: "requestId", reason: "expected string" }); + let scale: 1.5 | 2.5 = undefined as unknown as 1.5 | 2.5; + if (raw.scale === undefined || raw.scale === null) { + violations.push({ path: "scale", reason: "required" }); } else { - requestId = raw.requestId; - if (!PATTERN_52CD3CCF2038430A.test(raw.requestId)) { + if (typeof raw.scale !== "number") { + violations.push({ path: "scale", reason: "expected number" }); + } else if (raw.scale !== 1.5 && raw.scale !== 2.5) { violations.push({ - path: "requestId", - reason: `must be a valid uuid, got ${JSON.stringify(raw.requestId)}`, + path: "scale", + reason: `must be one of [1.5, 2.5], got ${JSON.stringify(raw.scale)}`, }); + } else { + scale = raw.scale as 1.5 | 2.5; } } - } - let contactEmail: string | undefined = undefined as unknown as string | undefined; - if (raw.contactEmail === null) { - violations.push({ path: "contactEmail", reason: "explicit null not allowed" }); - } else if (raw.contactEmail !== undefined) { - if (typeof raw.contactEmail !== "string") { - violations.push({ path: "contactEmail", reason: "expected string" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - contactEmail = raw.contactEmail; - if ( - [...raw.contactEmail].length > 254 || - !PATTERN_E7C805FB9E8E4DC4.test(raw.contactEmail) - ) { - violations.push({ - path: "contactEmail", - reason: `must be a valid email, got ${JSON.stringify(raw.contactEmail)}`, - }); + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; + if ([...raw.name].length < 1) { + violations.push({ + path: "name", + reason: `must have length >= 1, got ${[...raw.name].length}`, + }); + } + if ([...raw.name].length > 64) { + violations.push({ + path: "name", + reason: `must have length <= 64, got ${[...raw.name].length}`, + }); + } } } - } - let host: string | undefined = undefined as unknown as string | undefined; - if (raw.host === null) { - violations.push({ path: "host", reason: "explicit null not allowed" }); - } else if (raw.host !== undefined) { - if (typeof raw.host !== "string") { - violations.push({ path: "host", reason: "expected string" }); + let count: number = undefined as unknown as number; + if (raw.count === undefined || raw.count === null) { + violations.push({ path: "count", reason: "required" }); } else { - host = raw.host; - if ([...raw.host].length > 253 || !PATTERN_BB674DB499542D4F.test(raw.host)) { - violations.push({ - path: "host", - reason: `must be a valid hostname, got ${JSON.stringify(raw.host)}`, - }); + if (typeof raw.count !== "number" || !Number.isSafeInteger(raw.count)) { + violations.push({ path: "count", reason: "expected integer" }); + } else { + count = raw.count; } } - } - let homepage: string | undefined = undefined as unknown as string | undefined; - if (raw.homepage === null) { - violations.push({ path: "homepage", reason: "explicit null not allowed" }); - } else if (raw.homepage !== undefined) { - if (typeof raw.homepage !== "string") { - violations.push({ path: "homepage", reason: "expected string" }); + let active: boolean = undefined as unknown as boolean; + if (raw.active === undefined || raw.active === null) { + violations.push({ path: "active", reason: "required" }); } else { - homepage = raw.homepage; - if (!PATTERN_2F0C822905CC055D.test(raw.homepage)) { - violations.push({ - path: "homepage", - reason: `must be a valid uri, got ${JSON.stringify(raw.homepage)}`, - }); + if (typeof raw.active !== "boolean") { + violations.push({ path: "active", reason: "expected boolean" }); + } else { + active = raw.active; } } - } - let gateway: string | undefined = undefined as unknown as string | undefined; - if (raw.gateway === null) { - violations.push({ path: "gateway", reason: "explicit null not allowed" }); - } else if (raw.gateway !== undefined) { - if (typeof raw.gateway !== "string") { - violations.push({ path: "gateway", reason: "expected string" }); - } else { - gateway = raw.gateway; - if (!PATTERN_F5FB862A44510B9D.test(raw.gateway)) { - violations.push({ - path: "gateway", - reason: `must be a valid ipv4, got ${JSON.stringify(raw.gateway)}`, - }); + let nickname: string | undefined = undefined as unknown as string | undefined; + if (raw.nickname === null) { + violations.push({ path: "nickname", reason: "explicit null not allowed" }); + } else if (raw.nickname !== undefined) { + if (typeof raw.nickname !== "string") { + violations.push({ path: "nickname", reason: "expected string" }); + } else { + nickname = raw.nickname; + if ([...raw.nickname].length > 12) { + violations.push({ + path: "nickname", + reason: `must have length <= 12, got ${[...raw.nickname].length}`, + }); + } } } - } - let blob: Uint8Array | undefined = undefined as unknown as Uint8Array | undefined; - if (raw.blob === null) { - violations.push({ path: "blob", reason: "explicit null not allowed" }); - } else if (raw.blob !== undefined) { - if (typeof raw.blob !== "string") { - violations.push({ path: "blob", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.base64ToBytes(raw.blob, "blob", violations); - if (parsed !== undefined) { - blob = parsed; + let code: string | undefined = undefined as unknown as string | undefined; + if (raw.code === null) { + violations.push({ path: "code", reason: "explicit null not allowed" }); + } else if (raw.code !== undefined) { + if (typeof raw.code !== "string") { + violations.push({ path: "code", reason: "expected string" }); + } else { + code = raw.code; + if ([...raw.code].length < 2) { + violations.push({ + path: "code", + reason: `must have length >= 2, got ${[...raw.code].length}`, + }); + } + if ([...raw.code].length > 5) { + violations.push({ + path: "code", + reason: `must have length <= 5, got ${[...raw.code].length}`, + }); + } } } - } - let urlBlob: Uint8Array | undefined = undefined as unknown as - | Uint8Array - | undefined; - if (raw.urlBlob === null) { - violations.push({ path: "urlBlob", reason: "explicit null not allowed" }); - } else if (raw.urlBlob !== undefined) { - if (typeof raw.urlBlob !== "string") { - violations.push({ path: "urlBlob", reason: "expected string" }); - } else { - const parsed = __nexgenDefinitions.base64UrlToBytes( - raw.urlBlob, - "urlBlob", - violations, - ); - if (parsed !== undefined) { - urlBlob = parsed; + let sku: string | undefined = undefined as unknown as string | undefined; + if (raw.sku === null) { + violations.push({ path: "sku", reason: "explicit null not allowed" }); + } else if (raw.sku !== undefined) { + if (typeof raw.sku !== "string") { + violations.push({ path: "sku", reason: "expected string" }); + } else { + sku = raw.sku; + if (!PATTERN_821EF753B4B37A85.test(raw.sku)) { + violations.push({ + path: "sku", + reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(raw.sku)}`, + }); + } } } - } - let retries: number | undefined = undefined as unknown as number | undefined; - if (raw.retries === null) { - violations.push({ path: "retries", reason: "explicit null not allowed" }); - } else if (raw.retries !== undefined) { - if (typeof raw.retries !== "number" || !Number.isSafeInteger(raw.retries)) { - violations.push({ path: "retries", reason: "expected integer" }); - } else { - retries = raw.retries; + let phrase: string | undefined = undefined as unknown as string | undefined; + if (raw.phrase === null) { + violations.push({ path: "phrase", reason: "explicit null not allowed" }); + } else if (raw.phrase !== undefined) { + if (typeof raw.phrase !== "string") { + violations.push({ path: "phrase", reason: "expected string" }); + } else { + phrase = raw.phrase; + if (!PATTERN_AF8AB992526D6283.test(raw.phrase)) { + violations.push({ + path: "phrase", + reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(raw.phrase)}`, + }); + } + } } - } - let verbose: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.verbose === null) { - violations.push({ path: "verbose", reason: "explicit null not allowed" }); - } else if (raw.verbose !== undefined) { - if (typeof raw.verbose !== "boolean") { - violations.push({ path: "verbose", reason: "expected boolean" }); - } else { - verbose = raw.verbose; + let requestId: string | undefined = undefined as unknown as string | undefined; + if (raw.requestId === null) { + violations.push({ path: "requestId", reason: "explicit null not allowed" }); + } else if (raw.requestId !== undefined) { + if (typeof raw.requestId !== "string") { + violations.push({ path: "requestId", reason: "expected string" }); + } else { + requestId = raw.requestId; + if (!PATTERN_52CD3CCF2038430A.test(raw.requestId)) { + violations.push({ + path: "requestId", + reason: `must be a valid uuid, got ${JSON.stringify(raw.requestId)}`, + }); + } + } } - } - let greeting: string | undefined = undefined as unknown as string | undefined; - if (raw.greeting === null) { - violations.push({ path: "greeting", reason: "explicit null not allowed" }); - } else if (raw.greeting !== undefined) { - if (typeof raw.greeting !== "string") { - violations.push({ path: "greeting", reason: "expected string" }); - } else { - greeting = raw.greeting; + let contactEmail: string | undefined = undefined as unknown as string | undefined; + if (raw.contactEmail === null) { + violations.push({ path: "contactEmail", reason: "explicit null not allowed" }); + } else if (raw.contactEmail !== undefined) { + if (typeof raw.contactEmail !== "string") { + violations.push({ path: "contactEmail", reason: "expected string" }); + } else { + contactEmail = raw.contactEmail; + if ( + [...raw.contactEmail].length > 254 || + !PATTERN_E7C805FB9E8E4DC4.test(raw.contactEmail) + ) { + violations.push({ + path: "contactEmail", + reason: `must be a valid email, got ${JSON.stringify(raw.contactEmail)}`, + }); + } + } } - } - let debug: boolean | undefined = undefined as unknown as boolean | undefined; - if (raw.debug === null) { - violations.push({ path: "debug", reason: "explicit null not allowed" }); - } else if (raw.debug !== undefined) { - if (typeof raw.debug !== "boolean") { - violations.push({ path: "debug", reason: "expected boolean" }); - } else { - debug = raw.debug; + let host: string | undefined = undefined as unknown as string | undefined; + if (raw.host === null) { + violations.push({ path: "host", reason: "explicit null not allowed" }); + } else if (raw.host !== undefined) { + if (typeof raw.host !== "string") { + violations.push({ path: "host", reason: "expected string" }); + } else { + host = raw.host; + if ([...raw.host].length > 253 || !PATTERN_BB674DB499542D4F.test(raw.host)) { + violations.push({ + path: "host", + reason: `must be a valid hostname, got ${JSON.stringify(raw.host)}`, + }); + } + } } - } - let legacyIdTs: string | undefined = undefined as unknown as string | undefined; - if (raw.legacyId === null) { - violations.push({ path: "legacyId", reason: "explicit null not allowed" }); - } else if (raw.legacyId !== undefined) { - if (typeof raw.legacyId !== "string") { - violations.push({ path: "legacyId", reason: "expected string" }); - } else { - legacyIdTs = raw.legacyId; + let homepage: string | undefined = undefined as unknown as string | undefined; + if (raw.homepage === null) { + violations.push({ path: "homepage", reason: "explicit null not allowed" }); + } else if (raw.homepage !== undefined) { + if (typeof raw.homepage !== "string") { + violations.push({ path: "homepage", reason: "expected string" }); + } else { + homepage = raw.homepage; + if (!PATTERN_2F0C822905CC055D.test(raw.homepage)) { + violations.push({ + path: "homepage", + reason: `must be a valid uri, got ${JSON.stringify(raw.homepage)}`, + }); + } + } } - } - let middleName: string | null | undefined = undefined as unknown as - | string - | null - | undefined; - if (raw.middleName !== undefined) { - if (raw.middleName === null) { - middleName = null; - } else { - if (typeof raw.middleName !== "string") { - violations.push({ path: "middleName", reason: "expected string" }); + let gateway: string | undefined = undefined as unknown as string | undefined; + if (raw.gateway === null) { + violations.push({ path: "gateway", reason: "explicit null not allowed" }); + } else if (raw.gateway !== undefined) { + if (typeof raw.gateway !== "string") { + violations.push({ path: "gateway", reason: "expected string" }); } else { - middleName = raw.middleName; + gateway = raw.gateway; + if (!PATTERN_F5FB862A44510B9D.test(raw.gateway)) { + violations.push({ + path: "gateway", + reason: `must be a valid ipv4, got ${JSON.stringify(raw.gateway)}`, + }); + } } } - } - let category: string | null = undefined as unknown as string | null; - if (raw.category === undefined) { - violations.push({ path: "category", reason: "required" }); - } else { - if (raw.category === null) { - category = null; - } else { - if (typeof raw.category !== "string") { - violations.push({ path: "category", reason: "expected string" }); + let blob: Uint8Array | undefined = undefined as unknown as Uint8Array | undefined; + if (raw.blob === null) { + violations.push({ path: "blob", reason: "explicit null not allowed" }); + } else if (raw.blob !== undefined) { + if (typeof raw.blob !== "string") { + violations.push({ path: "blob", reason: "expected string" }); } else { - category = raw.category; + const parsed = __nexgenDefinitions.base64ToBytes( + raw.blob, + "blob", + violations, + ); + if (parsed !== undefined) { + blob = parsed; + } } } - } - let priority: number | undefined = undefined as unknown as number | undefined; - if (raw.priority === null) { - violations.push({ path: "priority", reason: "explicit null not allowed" }); - } else if (raw.priority !== undefined) { - if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { - violations.push({ path: "priority", reason: "expected integer" }); - } else { - priority = raw.priority; - if (raw.priority < 1) { - violations.push({ - path: "priority", - reason: `must be >= 1, got ${raw.priority}`, - }); + let urlBlob: Uint8Array | undefined = undefined as unknown as + | Uint8Array + | undefined; + if (raw.urlBlob === null) { + violations.push({ path: "urlBlob", reason: "explicit null not allowed" }); + } else if (raw.urlBlob !== undefined) { + if (typeof raw.urlBlob !== "string") { + violations.push({ path: "urlBlob", reason: "expected string" }); + } else { + const parsed = __nexgenDefinitions.base64UrlToBytes( + raw.urlBlob, + "urlBlob", + violations, + ); + if (parsed !== undefined) { + urlBlob = parsed; + } } - if (raw.priority > 10) { - violations.push({ - path: "priority", - reason: `must be <= 10, got ${raw.priority}`, - }); + } + + let retries: number | undefined = undefined as unknown as number | undefined; + if (raw.retries === null) { + violations.push({ path: "retries", reason: "explicit null not allowed" }); + } else if (raw.retries !== undefined) { + if (typeof raw.retries !== "number" || !Number.isSafeInteger(raw.retries)) { + violations.push({ path: "retries", reason: "expected integer" }); + } else { + retries = raw.retries; } } - } - let level: number | undefined = undefined as unknown as number | undefined; - if (raw.level === null) { - violations.push({ path: "level", reason: "explicit null not allowed" }); - } else if (raw.level !== undefined) { - if (typeof raw.level !== "number" || !Number.isSafeInteger(raw.level)) { - violations.push({ path: "level", reason: "expected integer" }); - } else { - level = raw.level; - if (raw.level <= 0) { - violations.push({ path: "level", reason: `must be > 0, got ${raw.level}` }); + let verbose: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.verbose === null) { + violations.push({ path: "verbose", reason: "explicit null not allowed" }); + } else if (raw.verbose !== undefined) { + if (typeof raw.verbose !== "boolean") { + violations.push({ path: "verbose", reason: "expected boolean" }); + } else { + verbose = raw.verbose; } } - } - let ratio: number | undefined = undefined as unknown as number | undefined; - if (raw.ratio === null) { - violations.push({ path: "ratio", reason: "explicit null not allowed" }); - } else if (raw.ratio !== undefined) { - if (typeof raw.ratio !== "number") { - violations.push({ path: "ratio", reason: "expected number" }); - } else { - ratio = raw.ratio; - if (raw.ratio < 5) { - violations.push({ path: "ratio", reason: `must be >= 5, got ${raw.ratio}` }); + let greeting: string | undefined = undefined as unknown as string | undefined; + if (raw.greeting === null) { + violations.push({ path: "greeting", reason: "explicit null not allowed" }); + } else if (raw.greeting !== undefined) { + if (typeof raw.greeting !== "string") { + violations.push({ path: "greeting", reason: "expected string" }); + } else { + greeting = raw.greeting; } - if (raw.ratio % 5 !== 0) { - violations.push({ - path: "ratio", - reason: `must be a multiple of 5, got ${raw.ratio}`, - }); + } + + let debug: boolean | undefined = undefined as unknown as boolean | undefined; + if (raw.debug === null) { + violations.push({ path: "debug", reason: "explicit null not allowed" }); + } else if (raw.debug !== undefined) { + if (typeof raw.debug !== "boolean") { + violations.push({ path: "debug", reason: "expected boolean" }); + } else { + debug = raw.debug; } } - } - let step: number | undefined = undefined as unknown as number | undefined; - if (raw.step === null) { - violations.push({ path: "step", reason: "explicit null not allowed" }); - } else if (raw.step !== undefined) { - if (typeof raw.step !== "number" || !Number.isSafeInteger(raw.step)) { - violations.push({ path: "step", reason: "expected integer" }); - } else { - step = raw.step; - if (raw.step % 3 !== 0) { - violations.push({ - path: "step", - reason: `must be a multiple of 3, got ${raw.step}`, - }); + let legacyIdTs: string | undefined = undefined as unknown as string | undefined; + if (raw.legacyId === null) { + violations.push({ path: "legacyId", reason: "explicit null not allowed" }); + } else if (raw.legacyId !== undefined) { + if (typeof raw.legacyId !== "string") { + violations.push({ path: "legacyId", reason: "expected string" }); + } else { + legacyIdTs = raw.legacyId; } } - } - let tags: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.tags === null) { - violations.push({ path: "tags", reason: "explicit null not allowed" }); - } else if (raw.tags !== undefined) { - if (!Array.isArray(raw.tags)) { - violations.push({ path: "tags", reason: "expected array" }); - } else { - tags = []; - raw.tags.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `tags[${index}]`, reason: "expected element" }); + let middleName: string | null | undefined = undefined as unknown as + | string + | null + | undefined; + if (raw.middleName !== undefined) { + if (raw.middleName === null) { + middleName = null; + } else { + if (typeof raw.middleName !== "string") { + violations.push({ path: "middleName", reason: "expected string" }); } else { - item = element; + middleName = raw.middleName; } - if (item !== undefined) { - tags!.push(item); - } - }); - if (tags!.length < 1) { - violations.push({ - path: "tags", - reason: `must have at least 1 items, got ${tags!.length}`, - }); - } - if (tags!.length > 5) { - violations.push({ - path: "tags", - reason: `must have at most 5 items, got ${tags!.length}`, - }); } } - } - let aliases: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.aliases === null) { - violations.push({ path: "aliases", reason: "explicit null not allowed" }); - } else if (raw.aliases !== undefined) { - if (!Array.isArray(raw.aliases)) { - violations.push({ path: "aliases", reason: "expected array" }); + let category: string | null = undefined as unknown as string | null; + if (raw.category === undefined) { + violations.push({ path: "category", reason: "required" }); } else { - aliases = []; - raw.aliases.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `aliases[${index}]`, reason: "expected element" }); + if (raw.category === null) { + category = null; + } else { + if (typeof raw.category !== "string") { + violations.push({ path: "category", reason: "expected string" }); } else { - item = element; - } - if (item !== undefined) { - aliases!.push(item); + category = raw.category; } - }); - { - const seen = new Map(); - aliases!.forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "aliases", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); } } - } - let roles: string[] | undefined = undefined as unknown as string[] | undefined; - if (raw.roles === null) { - violations.push({ path: "roles", reason: "explicit null not allowed" }); - } else if (raw.roles !== undefined) { - if (!Array.isArray(raw.roles)) { - violations.push({ path: "roles", reason: "expected array" }); - } else { - roles = []; - raw.roles.forEach((element: unknown, index: number) => { - let item: string = undefined as unknown as string; - if (typeof element !== "string") { - violations.push({ path: `roles[${index}]`, reason: "expected element" }); - } else { - item = element; - } - if (item !== undefined) { - roles!.push(item); - } - }); - { - const matchCount = roles!.filter((element) => element === "admin").length; - if (matchCount < 1) { + let priority: number | undefined = undefined as unknown as number | undefined; + if (raw.priority === null) { + violations.push({ path: "priority", reason: "explicit null not allowed" }); + } else if (raw.priority !== undefined) { + if (typeof raw.priority !== "number" || !Number.isSafeInteger(raw.priority)) { + violations.push({ path: "priority", reason: "expected integer" }); + } else { + priority = raw.priority; + if (raw.priority < 1) { violations.push({ - path: "roles", - reason: `too few matching items: at least 1, got ${matchCount}`, + path: "priority", + reason: `must be >= 1, got ${raw.priority}`, }); } - if (matchCount > 2) { + if (raw.priority > 10) { violations.push({ - path: "roles", - reason: `too many matching items: at most 2, got ${matchCount}`, + path: "priority", + reason: `must be <= 10, got ${raw.priority}`, }); } } } - } - let idOrName: string | number | undefined = undefined as unknown as - | string - | number - | undefined; - if (raw.idOrName === null) { - violations.push({ path: "idOrName", reason: "explicit null not allowed" }); - } else if (raw.idOrName !== undefined) { - if (typeof raw.idOrName === "string") { - idOrName = raw.idOrName as string; - if ([...(idOrName as string)].length < 3) { - violations.push({ - path: "idOrName", - reason: `must have length >= 3, got ${[...(idOrName as string)].length}`, - }); - } - } else if ( - typeof raw.idOrName === "number" && - Number.isSafeInteger(raw.idOrName) - ) { - idOrName = raw.idOrName as number; - if ((idOrName as number) < 1) { - violations.push({ - path: "idOrName", - reason: `must be >= 1, got ${idOrName as number}`, - }); + let level: number | undefined = undefined as unknown as number | undefined; + if (raw.level === null) { + violations.push({ path: "level", reason: "explicit null not allowed" }); + } else if (raw.level !== undefined) { + if (typeof raw.level !== "number" || !Number.isSafeInteger(raw.level)) { + violations.push({ path: "level", reason: "expected integer" }); + } else { + level = raw.level; + if (raw.level <= 0) { + violations.push({ path: "level", reason: `must be > 0, got ${raw.level}` }); + } } - } else { - violations.push({ - path: "idOrName", - reason: "expected one of: string, integer", - }); } - } - let mode: "auto" | "manual" | number | undefined = undefined as unknown as - | "auto" - | "manual" - | number - | undefined; - if (raw.mode === null) { - violations.push({ path: "mode", reason: "explicit null not allowed" }); - } else if (raw.mode !== undefined) { - if (typeof raw.mode === "string") { - mode = raw.mode as "auto" | "manual"; - if ( - (mode as "auto" | "manual") !== "auto" && - (mode as "auto" | "manual") !== "manual" - ) { - violations.push({ - path: "mode", - reason: `must be one of ["auto", "manual"], got ${JSON.stringify(mode as "auto" | "manual")}`, - }); - } - } else if (typeof raw.mode === "number" && Number.isSafeInteger(raw.mode)) { - mode = raw.mode as number; - if ((mode as number) < 0) { - violations.push({ - path: "mode", - reason: `must be >= 0, got ${mode as number}`, - }); + let ratio: number | undefined = undefined as unknown as number | undefined; + if (raw.ratio === null) { + violations.push({ path: "ratio", reason: "explicit null not allowed" }); + } else if (raw.ratio !== undefined) { + if (typeof raw.ratio !== "number") { + violations.push({ path: "ratio", reason: "expected number" }); + } else { + ratio = raw.ratio; + if (raw.ratio < 5) { + violations.push({ + path: "ratio", + reason: `must be >= 5, got ${raw.ratio}`, + }); + } + if (raw.ratio % 5 !== 0) { + violations.push({ + path: "ratio", + reason: `must be a multiple of 5, got ${raw.ratio}`, + }); + } } - } else { - violations.push({ path: "mode", reason: "expected one of: string, integer" }); - } - } - - let payload: Record | string | undefined = undefined as unknown as - | Record - | string - | undefined; - if (raw.payload === null) { - violations.push({ path: "payload", reason: "explicit null not allowed" }); - } else if (raw.payload !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.payload)) { - payload = raw.payload as Record; - } else if (typeof raw.payload === "string") { - payload = raw.payload as string; - } else { - violations.push({ path: "payload", reason: "expected one of: object, string" }); } - } - let detail: ShowcaseDetailObject | string | undefined = undefined as unknown as - | ShowcaseDetailObject - | string - | undefined; - if (raw.detail === null) { - violations.push({ path: "detail", reason: "explicit null not allowed" }); - } else if (raw.detail !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.detail)) { - try { - detail = new ShowcaseDetailObjectMapper().fromIntermediate(raw.detail); - } catch (error) { - __nexgenDefinitions.collect(violations, "detail", error); + let step: number | undefined = undefined as unknown as number | undefined; + if (raw.step === null) { + violations.push({ path: "step", reason: "explicit null not allowed" }); + } else if (raw.step !== undefined) { + if (typeof raw.step !== "number" || !Number.isSafeInteger(raw.step)) { + violations.push({ path: "step", reason: "expected integer" }); + } else { + step = raw.step; + if (raw.step % 3 !== 0) { + violations.push({ + path: "step", + reason: `must be a multiple of 3, got ${raw.step}`, + }); + } } - } else if (typeof raw.detail === "string") { - detail = raw.detail as string; - } else { - violations.push({ - path: "detail", - reason: "expected one of: ShowcaseDetailObject, string", - }); } - } - let shapeOrName: Circle | Square | string | undefined = undefined as unknown as - | Circle - | Square - | string - | undefined; - if (raw.shapeOrName === null) { - violations.push({ path: "shapeOrName", reason: "explicit null not allowed" }); - } else if (raw.shapeOrName !== undefined) { - if (__nexgenDefinitions.isPlainObject(raw.shapeOrName)) { - switch ((raw.shapeOrName as Record)["kind"]) { - case "circle": - try { - shapeOrName = new CircleMapper().fromIntermediate(raw.shapeOrName); - } catch (error) { - __nexgenDefinitions.collect(violations, "shapeOrName", error); + let tags: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.tags === null) { + violations.push({ path: "tags", reason: "explicit null not allowed" }); + } else if (raw.tags !== undefined) { + if (!Array.isArray(raw.tags)) { + violations.push({ path: "tags", reason: "expected array" }); + } else { + tags = []; + raw.tags.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ path: `tags[${index}]`, reason: "expected element" }); + } else { + item = element; } - break; - case "square": - try { - shapeOrName = new SquareMapper().fromIntermediate(raw.shapeOrName); - } catch (error) { - __nexgenDefinitions.collect(violations, "shapeOrName", error); + if (item !== undefined) { + tags!.push(item); } - break; - default: + }); + if (tags!.length < 1) { violations.push({ - path: "shapeOrName", - reason: `unknown discriminator kind ${String((raw.shapeOrName as Record)["kind"])}: expected one of ["circle", "square"]`, + path: "tags", + reason: `must have at least 1 items, got ${tags!.length}`, }); + } + if (tags!.length > 5) { + violations.push({ + path: "tags", + reason: `must have at most 5 items, got ${tags!.length}`, + }); + } } - } else if (typeof raw.shapeOrName === "string") { - shapeOrName = raw.shapeOrName as string; - if ([...(shapeOrName as string)].length > 32) { - violations.push({ - path: "shapeOrName", - reason: `must have length <= 32, got ${[...(shapeOrName as string)].length}`, - }); - } - } else { - violations.push({ - path: "shapeOrName", - reason: "expected one of: Circle, Square, string", - }); } - } - let measurements: number[] | string | undefined = undefined as unknown as - | number[] - | string - | undefined; - if (raw.measurements === null) { - violations.push({ path: "measurements", reason: "explicit null not allowed" }); - } else if (raw.measurements !== undefined) { - if (Array.isArray(raw.measurements)) { - measurements = raw.measurements as number[]; - if ((measurements as number[]).length < 1) { - violations.push({ - path: "measurements", - reason: `must have at least 1 items, got ${(measurements as number[]).length}`, - }); - } - { - const seen = new Map(); - (measurements as number[]).forEach((element, index) => { - if (seen.has(element)) { + let aliases: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.aliases === null) { + violations.push({ path: "aliases", reason: "explicit null not allowed" }); + } else if (raw.aliases !== undefined) { + if (!Array.isArray(raw.aliases)) { + violations.push({ path: "aliases", reason: "expected array" }); + } else { + aliases = []; + raw.aliases.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { violations.push({ - path: "measurements", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + path: `aliases[${index}]`, + reason: "expected element", }); } else { - seen.set(element, index); + item = element; + } + if (item !== undefined) { + aliases!.push(item); } }); + { + const seen = new Map(); + aliases!.forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "aliases", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } } - } else if (typeof raw.measurements === "string") { - measurements = raw.measurements as string; - if (!PATTERN_C182F89FDB221836.test(measurements as string)) { - violations.push({ - path: "measurements", - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(measurements as string)}`, - }); - } - } else { - violations.push({ - path: "measurements", - reason: "expected one of: number[], string", - }); } - } - let shapes: Shape[] | undefined = undefined as unknown as Shape[] | undefined; - if (raw.shapes === null) { - violations.push({ path: "shapes", reason: "explicit null not allowed" }); - } else if (raw.shapes !== undefined) { - if (!Array.isArray(raw.shapes)) { - violations.push({ path: "shapes", reason: "expected array" }); - } else { - shapes = []; - raw.shapes.forEach((element: unknown, index: number) => { - let item: Shape = undefined as unknown as Shape; - try { - item = new ShapeMapper().fromIntermediate(element); - } catch (error) { - __nexgenDefinitions.collect(violations, `shapes[${index}]`, error); + let roles: string[] | undefined = undefined as unknown as string[] | undefined; + if (raw.roles === null) { + violations.push({ path: "roles", reason: "explicit null not allowed" }); + } else if (raw.roles !== undefined) { + if (!Array.isArray(raw.roles)) { + violations.push({ path: "roles", reason: "expected array" }); + } else { + roles = []; + raw.roles.forEach((element: unknown, index: number) => { + let item: string = undefined as unknown as string; + if (typeof element !== "string") { + violations.push({ path: `roles[${index}]`, reason: "expected element" }); + } else { + item = element; + } + if (item !== undefined) { + roles!.push(item); + } + }); + { + const matchCount = roles!.filter((element) => element === "admin").length; + if (matchCount < 1) { + violations.push({ + path: "roles", + reason: `too few matching items: at least 1, got ${matchCount}`, + }); + } + if (matchCount > 2) { + violations.push({ + path: "roles", + reason: `too many matching items: at most 2, got ${matchCount}`, + }); + } + } + } + } + + let idOrName: string | number | undefined = undefined as unknown as + | string + | number + | undefined; + if (raw.idOrName === null) { + violations.push({ path: "idOrName", reason: "explicit null not allowed" }); + } else if (raw.idOrName !== undefined) { + if (typeof raw.idOrName === "string") { + idOrName = raw.idOrName as string; + if ([...(idOrName as string)].length < 3) { + violations.push({ + path: "idOrName", + reason: `must have length >= 3, got ${[...(idOrName as string)].length}`, + }); } - if (item !== undefined) { - shapes!.push(item); + } else if ( + typeof raw.idOrName === "number" && + Number.isSafeInteger(raw.idOrName) + ) { + idOrName = raw.idOrName as number; + if ((idOrName as number) < 1) { + violations.push({ + path: "idOrName", + reason: `must be >= 1, got ${idOrName as number}`, + }); } - }); + } else { + violations.push({ + path: "idOrName", + reason: "expected one of: string, integer", + }); + } } - } - let segments: ShowcaseSegmentsItem[] | undefined = undefined as unknown as - | ShowcaseSegmentsItem[] - | undefined; - if (raw.segments === null) { - violations.push({ path: "segments", reason: "explicit null not allowed" }); - } else if (raw.segments !== undefined) { - if (!Array.isArray(raw.segments)) { - violations.push({ path: "segments", reason: "expected array" }); - } else { - segments = []; - raw.segments.forEach((element: unknown, index: number) => { - let item: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; + let mode: "auto" | "manual" | number | undefined = undefined as unknown as + | "auto" + | "manual" + | number + | undefined; + if (raw.mode === null) { + violations.push({ path: "mode", reason: "explicit null not allowed" }); + } else if (raw.mode !== undefined) { + if (typeof raw.mode === "string") { + mode = raw.mode as "auto" | "manual"; + if ( + (mode as "auto" | "manual") !== "auto" && + (mode as "auto" | "manual") !== "manual" + ) { + violations.push({ + path: "mode", + reason: `must be one of ["auto", "manual"], got ${JSON.stringify(mode as "auto" | "manual")}`, + }); + } + } else if (typeof raw.mode === "number" && Number.isSafeInteger(raw.mode)) { + mode = raw.mode as number; + if ((mode as number) < 0) { + violations.push({ + path: "mode", + reason: `must be >= 0, got ${mode as number}`, + }); + } + } else { + violations.push({ path: "mode", reason: "expected one of: string, integer" }); + } + } + + let payload: Record | string | undefined = + undefined as unknown as Record | string | undefined; + if (raw.payload === null) { + violations.push({ path: "payload", reason: "explicit null not allowed" }); + } else if (raw.payload !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.payload)) { + payload = raw.payload as Record; + } else if (typeof raw.payload === "string") { + payload = raw.payload as string; + } else { + violations.push({ + path: "payload", + reason: "expected one of: object, string", + }); + } + } + + let detail: ShowcaseDetailObject | string | undefined = undefined as unknown as + | ShowcaseDetailObject + | string + | undefined; + if (raw.detail === null) { + violations.push({ path: "detail", reason: "explicit null not allowed" }); + } else if (raw.detail !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.detail)) { try { - item = new ShowcaseSegmentsItemMapper().fromIntermediate(element); + detail = showcaseDetailObjectTransferTypeConverter.fromTransferType( + raw.detail, + ); } catch (error) { - __nexgenDefinitions.collect(violations, `segments[${index}]`, error); + __nexgenDefinitions.collect(violations, "detail", error); } - if (item !== undefined) { - segments!.push(item); - } - }); + } else if (typeof raw.detail === "string") { + detail = raw.detail as string; + } else { + violations.push({ + path: "detail", + reason: "expected one of: ShowcaseDetailObject, string", + }); + } } - } - let slots: (string | null)[] | undefined = undefined as unknown as - | (string | null)[] - | undefined; - if (raw.slots === null) { - violations.push({ path: "slots", reason: "explicit null not allowed" }); - } else if (raw.slots !== undefined) { - if (!Array.isArray(raw.slots)) { - violations.push({ path: "slots", reason: "expected array" }); - } else { - slots = []; - raw.slots.forEach((element: unknown, index: number) => { - let item: string | null = undefined as unknown as string | null; - if (element === null) { - item = null; - } else { - if (typeof element !== "string") { - violations.push({ path: `slots[${index}]`, reason: "expected string" }); - } else { - item = element; - } + let shapeOrName: Circle | Square | string | undefined = undefined as unknown as + | Circle + | Square + | string + | undefined; + if (raw.shapeOrName === null) { + violations.push({ path: "shapeOrName", reason: "explicit null not allowed" }); + } else if (raw.shapeOrName !== undefined) { + if (__nexgenDefinitions.isPlainObject(raw.shapeOrName)) { + switch ((raw.shapeOrName as Record)["kind"]) { + case "circle": + try { + shapeOrName = circleTransferTypeConverter.fromTransferType( + raw.shapeOrName, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "shapeOrName", error); + } + break; + case "square": + try { + shapeOrName = squareTransferTypeConverter.fromTransferType( + raw.shapeOrName, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "shapeOrName", error); + } + break; + default: + violations.push({ + path: "shapeOrName", + reason: `unknown discriminator kind ${String((raw.shapeOrName as Record)["kind"])}: expected one of ["circle", "square"]`, + }); } - if (item !== undefined) { - slots!.push(item); + } else if (typeof raw.shapeOrName === "string") { + shapeOrName = raw.shapeOrName as string; + if ([...(shapeOrName as string)].length > 32) { + violations.push({ + path: "shapeOrName", + reason: `must have length <= 32, got ${[...(shapeOrName as string)].length}`, + }); } - }); + } else { + violations.push({ + path: "shapeOrName", + reason: "expected one of: Circle, Square, string", + }); + } } - } - let grid: number[][] | undefined = undefined as unknown as number[][] | undefined; - if (raw.grid === null) { - violations.push({ path: "grid", reason: "explicit null not allowed" }); - } else if (raw.grid !== undefined) { - if (!Array.isArray(raw.grid)) { - violations.push({ path: "grid", reason: "expected array" }); - } else { - grid = []; - raw.grid.forEach((element: unknown, index: number) => { - let item: number[] = undefined as unknown as number[]; - if (!Array.isArray(element)) { - violations.push({ path: `grid[${index}]`, reason: "expected array" }); - } else { - item = []; - element.forEach((element1: unknown, index1: number) => { - let item1: number = undefined as unknown as number; - if (typeof element1 !== "number" || !Number.isSafeInteger(element1)) { + let measurements: number[] | string | undefined = undefined as unknown as + | number[] + | string + | undefined; + if (raw.measurements === null) { + violations.push({ path: "measurements", reason: "explicit null not allowed" }); + } else if (raw.measurements !== undefined) { + if (Array.isArray(raw.measurements)) { + measurements = raw.measurements as number[]; + if ((measurements as number[]).length < 1) { + violations.push({ + path: "measurements", + reason: `must have at least 1 items, got ${(measurements as number[]).length}`, + }); + } + { + const seen = new Map(); + (measurements as number[]).forEach((element, index) => { + if (seen.has(element)) { violations.push({ - path: `${`grid[${index}]`}[${index1}]`, - reason: "expected integer", + path: "measurements", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, }); } else { - item1 = element1; - } - if (item1 !== undefined) { - item!.push(item1); + seen.set(element, index); } }); } - if (item !== undefined) { - grid!.push(item); + } else if (typeof raw.measurements === "string") { + measurements = raw.measurements as string; + if (!PATTERN_C182F89FDB221836.test(measurements as string)) { + violations.push({ + path: "measurements", + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(measurements as string)}`, + }); } - }); + } else { + violations.push({ + path: "measurements", + reason: "expected one of: number[], string", + }); + } + } + + let shapes: Shape[] | undefined = undefined as unknown as Shape[] | undefined; + if (raw.shapes === null) { + violations.push({ path: "shapes", reason: "explicit null not allowed" }); + } else if (raw.shapes !== undefined) { + if (!Array.isArray(raw.shapes)) { + violations.push({ path: "shapes", reason: "expected array" }); + } else { + shapes = []; + raw.shapes.forEach((element: unknown, index: number) => { + let item: Shape = undefined as unknown as Shape; + try { + item = shapeTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `shapes[${index}]`, error); + } + if (item !== undefined) { + shapes!.push(item); + } + }); + } } - } - let location: ShowcaseLocation | undefined = undefined as unknown as - | ShowcaseLocation - | undefined; - if (raw.location === null) { - violations.push({ path: "location", reason: "explicit null not allowed" }); - } else if (raw.location !== undefined) { - try { - location = new ShowcaseLocationMapper().fromIntermediate(raw.location); - } catch (error) { - __nexgenDefinitions.collect(violations, "location", error); + let segments: ShowcaseSegmentsItem[] | undefined = undefined as unknown as + | ShowcaseSegmentsItem[] + | undefined; + if (raw.segments === null) { + violations.push({ path: "segments", reason: "explicit null not allowed" }); + } else if (raw.segments !== undefined) { + if (!Array.isArray(raw.segments)) { + violations.push({ path: "segments", reason: "expected array" }); + } else { + segments = []; + raw.segments.forEach((element: unknown, index: number) => { + let item: ShowcaseSegmentsItem = + undefined as unknown as ShowcaseSegmentsItem; + try { + item = + showcaseSegmentsItemTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `segments[${index}]`, error); + } + if (item !== undefined) { + segments!.push(item); + } + }); + } } - } - let audit: ShowcaseAudit | null | undefined = undefined as unknown as - | ShowcaseAudit - | null - | undefined; - if (raw.audit !== undefined) { - if (raw.audit === null) { - audit = null; - } else { + let slots: (string | null)[] | undefined = undefined as unknown as + | (string | null)[] + | undefined; + if (raw.slots === null) { + violations.push({ path: "slots", reason: "explicit null not allowed" }); + } else if (raw.slots !== undefined) { + if (!Array.isArray(raw.slots)) { + violations.push({ path: "slots", reason: "expected array" }); + } else { + slots = []; + raw.slots.forEach((element: unknown, index: number) => { + let item: string | null = undefined as unknown as string | null; + if (element === null) { + item = null; + } else { + if (typeof element !== "string") { + violations.push({ path: `slots[${index}]`, reason: "expected string" }); + } else { + item = element; + } + } + if (item !== undefined) { + slots!.push(item); + } + }); + } + } + + let grid: number[][] | undefined = undefined as unknown as number[][] | undefined; + if (raw.grid === null) { + violations.push({ path: "grid", reason: "explicit null not allowed" }); + } else if (raw.grid !== undefined) { + if (!Array.isArray(raw.grid)) { + violations.push({ path: "grid", reason: "expected array" }); + } else { + grid = []; + raw.grid.forEach((element: unknown, index: number) => { + let item: number[] = undefined as unknown as number[]; + if (!Array.isArray(element)) { + violations.push({ path: `grid[${index}]`, reason: "expected array" }); + } else { + item = []; + element.forEach((element1: unknown, index1: number) => { + let item1: number = undefined as unknown as number; + if (typeof element1 !== "number" || !Number.isSafeInteger(element1)) { + violations.push({ + path: `${`grid[${index}]`}[${index1}]`, + reason: "expected integer", + }); + } else { + item1 = element1; + } + if (item1 !== undefined) { + item!.push(item1); + } + }); + } + if (item !== undefined) { + grid!.push(item); + } + }); + } + } + + let location: ShowcaseLocation | undefined = undefined as unknown as + | ShowcaseLocation + | undefined; + if (raw.location === null) { + violations.push({ path: "location", reason: "explicit null not allowed" }); + } else if (raw.location !== undefined) { try { - audit = new ShowcaseAuditMapper().fromIntermediate(raw.audit); + location = showcaseLocationTransferTypeConverter.fromTransferType( + raw.location, + ); } catch (error) { - __nexgenDefinitions.collect(violations, "audit", error); + __nexgenDefinitions.collect(violations, "location", error); } } - } - let rows: ShowcaseRowsItem[] | undefined = undefined as unknown as - | ShowcaseRowsItem[] - | undefined; - if (raw.rows === null) { - violations.push({ path: "rows", reason: "explicit null not allowed" }); - } else if (raw.rows !== undefined) { - if (!Array.isArray(raw.rows)) { - violations.push({ path: "rows", reason: "expected array" }); - } else { - rows = []; - raw.rows.forEach((element: unknown, index: number) => { - let item: ShowcaseRowsItem = undefined as unknown as ShowcaseRowsItem; + let audit: ShowcaseAudit | null | undefined = undefined as unknown as + | ShowcaseAudit + | null + | undefined; + if (raw.audit !== undefined) { + if (raw.audit === null) { + audit = null; + } else { try { - item = new ShowcaseRowsItemMapper().fromIntermediate(element); + audit = showcaseAuditTransferTypeConverter.fromTransferType(raw.audit); } catch (error) { - __nexgenDefinitions.collect(violations, `rows[${index}]`, error); + __nexgenDefinitions.collect(violations, "audit", error); } - if (item !== undefined) { - rows!.push(item); - } - }); + } } - } - let ledgerTs: ShowcaseLedger | undefined = undefined as unknown as - | ShowcaseLedger - | undefined; - if (raw.ledger === null) { - violations.push({ path: "ledger", reason: "explicit null not allowed" }); - } else if (raw.ledger !== undefined) { - try { - ledgerTs = new ShowcaseLedgerMapper().fromIntermediate(raw.ledger); - } catch (error) { - __nexgenDefinitions.collect(violations, "ledger", error); + let rows: ShowcaseRowsItem[] | undefined = undefined as unknown as + | ShowcaseRowsItem[] + | undefined; + if (raw.rows === null) { + violations.push({ path: "rows", reason: "explicit null not allowed" }); + } else if (raw.rows !== undefined) { + if (!Array.isArray(raw.rows)) { + violations.push({ path: "rows", reason: "expected array" }); + } else { + rows = []; + raw.rows.forEach((element: unknown, index: number) => { + let item: ShowcaseRowsItem = undefined as unknown as ShowcaseRowsItem; + try { + item = showcaseRowsItemTransferTypeConverter.fromTransferType(element); + } catch (error) { + __nexgenDefinitions.collect(violations, `rows[${index}]`, error); + } + if (item !== undefined) { + rows!.push(item); + } + }); + } } - } - let metadata: ShowcaseMetadata | undefined = undefined as unknown as - | ShowcaseMetadata - | undefined; - if (raw.metadata === null) { - violations.push({ path: "metadata", reason: "explicit null not allowed" }); - } else if (raw.metadata !== undefined) { - try { - metadata = new ShowcaseMetadataMapper().fromIntermediate(raw.metadata); - } catch (error) { - __nexgenDefinitions.collect(violations, "metadata", error); + let ledgerTs: ShowcaseLedger | undefined = undefined as unknown as + | ShowcaseLedger + | undefined; + if (raw.ledger === null) { + violations.push({ path: "ledger", reason: "explicit null not allowed" }); + } else if (raw.ledger !== undefined) { + try { + ledgerTs = showcaseLedgerTransferTypeConverter.fromTransferType(raw.ledger); + } catch (error) { + __nexgenDefinitions.collect(violations, "ledger", error); + } } - } - let quotas: Quotas | undefined = undefined as unknown as Quotas | undefined; - if (raw.quotas === null) { - violations.push({ path: "quotas", reason: "explicit null not allowed" }); - } else if (raw.quotas !== undefined) { - try { - quotas = new QuotasMapper().fromIntermediate(raw.quotas); - } catch (error) { - __nexgenDefinitions.collect(violations, "quotas", error); + let metadata: ShowcaseMetadata | undefined = undefined as unknown as + | ShowcaseMetadata + | undefined; + if (raw.metadata === null) { + violations.push({ path: "metadata", reason: "explicit null not allowed" }); + } else if (raw.metadata !== undefined) { + try { + metadata = showcaseMetadataTransferTypeConverter.fromTransferType( + raw.metadata, + ); + } catch (error) { + __nexgenDefinitions.collect(violations, "metadata", error); + } } - } - let tokens: Tokens | undefined = undefined as unknown as Tokens | undefined; - if (raw.tokens === null) { - violations.push({ path: "tokens", reason: "explicit null not allowed" }); - } else if (raw.tokens !== undefined) { - try { - tokens = new TokensMapper().fromIntermediate(raw.tokens); - } catch (error) { - __nexgenDefinitions.collect(violations, "tokens", error); + let quotas: Quotas | undefined = undefined as unknown as Quotas | undefined; + if (raw.quotas === null) { + violations.push({ path: "quotas", reason: "explicit null not allowed" }); + } else if (raw.quotas !== undefined) { + try { + quotas = quotasTransferTypeConverter.fromTransferType(raw.quotas); + } catch (error) { + __nexgenDefinitions.collect(violations, "quotas", error); + } } - } - let nicknames: Nicknames | undefined = undefined as unknown as - | Nicknames - | undefined; - if (raw.nicknames === null) { - violations.push({ path: "nicknames", reason: "explicit null not allowed" }); - } else if (raw.nicknames !== undefined) { - try { - nicknames = new NicknamesMapper().fromIntermediate(raw.nicknames); - } catch (error) { - __nexgenDefinitions.collect(violations, "nicknames", error); + let tokens: Tokens | undefined = undefined as unknown as Tokens | undefined; + if (raw.tokens === null) { + violations.push({ path: "tokens", reason: "explicit null not allowed" }); + } else if (raw.tokens !== undefined) { + try { + tokens = tokensTransferTypeConverter.fromTransferType(raw.tokens); + } catch (error) { + __nexgenDefinitions.collect(violations, "tokens", error); + } } - } - let choices: Choices | undefined = undefined as unknown as Choices | undefined; - if (raw.choices === null) { - violations.push({ path: "choices", reason: "explicit null not allowed" }); - } else if (raw.choices !== undefined) { - try { - choices = new ChoicesMapper().fromIntermediate(raw.choices); - } catch (error) { - __nexgenDefinitions.collect(violations, "choices", error); + let nicknames: Nicknames | undefined = undefined as unknown as + | Nicknames + | undefined; + if (raw.nicknames === null) { + violations.push({ path: "nicknames", reason: "explicit null not allowed" }); + } else if (raw.nicknames !== undefined) { + try { + nicknames = nicknamesTransferTypeConverter.fromTransferType(raw.nicknames); + } catch (error) { + __nexgenDefinitions.collect(violations, "nicknames", error); + } } - } - let extras: Extras | undefined = undefined as unknown as Extras | undefined; - if (raw.extras === null) { - violations.push({ path: "extras", reason: "explicit null not allowed" }); - } else if (raw.extras !== undefined) { - try { - extras = new ExtrasMapper().fromIntermediate(raw.extras); - } catch (error) { - __nexgenDefinitions.collect(violations, "extras", error); + let choices: Choices | undefined = undefined as unknown as Choices | undefined; + if (raw.choices === null) { + violations.push({ path: "choices", reason: "explicit null not allowed" }); + } else if (raw.choices !== undefined) { + try { + choices = choicesTransferTypeConverter.fromTransferType(raw.choices); + } catch (error) { + __nexgenDefinitions.collect(violations, "choices", error); + } } - } - let shape: Shape | undefined = undefined as unknown as Shape | undefined; - if (raw.shape === null) { - violations.push({ path: "shape", reason: "explicit null not allowed" }); - } else if (raw.shape !== undefined) { - try { - shape = new ShapeMapper().fromIntermediate(raw.shape); - } catch (error) { - __nexgenDefinitions.collect(violations, "shape", error); + let extras: Extras | undefined = undefined as unknown as Extras | undefined; + if (raw.extras === null) { + violations.push({ path: "extras", reason: "explicit null not allowed" }); + } else if (raw.extras !== undefined) { + try { + extras = extrasTransferTypeConverter.fromTransferType(raw.extras); + } catch (error) { + __nexgenDefinitions.collect(violations, "extras", error); + } } - } - let note: Note | undefined = undefined as unknown as Note | undefined; - if (raw.note === null) { - violations.push({ path: "note", reason: "explicit null not allowed" }); - } else if (raw.note !== undefined) { - try { - note = new NoteMapper().fromIntermediate(raw.note); - } catch (error) { - __nexgenDefinitions.collect(violations, "note", error); + let shape: Shape | undefined = undefined as unknown as Shape | undefined; + if (raw.shape === null) { + violations.push({ path: "shape", reason: "explicit null not allowed" }); + } else if (raw.shape !== undefined) { + try { + shape = shapeTransferTypeConverter.fromTransferType(raw.shape); + } catch (error) { + __nexgenDefinitions.collect(violations, "shape", error); + } } - } - let address: Address | undefined = undefined as unknown as Address | undefined; - if (raw.address === null) { - violations.push({ path: "address", reason: "explicit null not allowed" }); - } else if (raw.address !== undefined) { - try { - address = new AddressMapper().fromIntermediate(raw.address); - } catch (error) { - __nexgenDefinitions.collect(violations, "address", error); + let note: Note | undefined = undefined as unknown as Note | undefined; + if (raw.note === null) { + violations.push({ path: "note", reason: "explicit null not allowed" }); + } else if (raw.note !== undefined) { + try { + note = noteTransferTypeConverter.fromTransferType(raw.note); + } catch (error) { + __nexgenDefinitions.collect(violations, "note", error); + } } - } - let labels: Labels | undefined = undefined as unknown as Labels | undefined; - if (raw.labels === null) { - violations.push({ path: "labels", reason: "explicit null not allowed" }); - } else if (raw.labels !== undefined) { - try { - labels = new LabelsMapper().fromIntermediate(raw.labels); - } catch (error) { - __nexgenDefinitions.collect(violations, "labels", error); + let address: Address | undefined = undefined as unknown as Address | undefined; + if (raw.address === null) { + violations.push({ path: "address", reason: "explicit null not allowed" }); + } else if (raw.address !== undefined) { + try { + address = addressTransferTypeConverter.fromTransferType(raw.address); + } catch (error) { + __nexgenDefinitions.collect(violations, "address", error); + } } - } - let settings: Settings | undefined = undefined as unknown as Settings | undefined; - if (raw.settings === null) { - violations.push({ path: "settings", reason: "explicit null not allowed" }); - } else if (raw.settings !== undefined) { - try { - settings = new SettingsMapper().fromIntermediate(raw.settings); - } catch (error) { - __nexgenDefinitions.collect(violations, "settings", error); + let labels: Labels | undefined = undefined as unknown as Labels | undefined; + if (raw.labels === null) { + violations.push({ path: "labels", reason: "explicit null not allowed" }); + } else if (raw.labels !== undefined) { + try { + labels = labelsTransferTypeConverter.fromTransferType(raw.labels); + } catch (error) { + __nexgenDefinitions.collect(violations, "labels", error); + } } - } - let attributes: Attributes | undefined = undefined as unknown as - | Attributes - | undefined; - if (raw.attributes === null) { - violations.push({ path: "attributes", reason: "explicit null not allowed" }); - } else if (raw.attributes !== undefined) { - try { - attributes = new AttributesMapper().fromIntermediate(raw.attributes); - } catch (error) { - __nexgenDefinitions.collect(violations, "attributes", error); + let settings: Settings | undefined = undefined as unknown as Settings | undefined; + if (raw.settings === null) { + violations.push({ path: "settings", reason: "explicit null not allowed" }); + } else if (raw.settings !== undefined) { + try { + settings = settingsTransferTypeConverter.fromTransferType(raw.settings); + } catch (error) { + __nexgenDefinitions.collect(violations, "settings", error); + } } - } - let contact: ContactTs | undefined = undefined as unknown as ContactTs | undefined; - if (raw.contact === null) { - violations.push({ path: "contact", reason: "explicit null not allowed" }); - } else if (raw.contact !== undefined) { - try { - contact = new ContactTsMapper().fromIntermediate(raw.contact); - } catch (error) { - __nexgenDefinitions.collect(violations, "contact", error); + let attributes: Attributes | undefined = undefined as unknown as + | Attributes + | undefined; + if (raw.attributes === null) { + violations.push({ path: "attributes", reason: "explicit null not allowed" }); + } else if (raw.attributes !== undefined) { + try { + attributes = attributesTransferTypeConverter.fromTransferType(raw.attributes); + } catch (error) { + __nexgenDefinitions.collect(violations, "attributes", error); + } } - } - for (const key of Object.keys(raw)) { - if ( - key !== "kind" && - key !== "revision" && - key !== "enabled" && - key !== "status" && - key !== "tier" && - key !== "scale" && - key !== "name" && - key !== "count" && - key !== "active" && - key !== "nickname" && - key !== "code" && - key !== "sku" && - key !== "phrase" && - key !== "requestId" && - key !== "contactEmail" && - key !== "host" && - key !== "homepage" && - key !== "gateway" && - key !== "blob" && - key !== "urlBlob" && - key !== "retries" && - key !== "verbose" && - key !== "greeting" && - key !== "debug" && - key !== "legacyId" && - key !== "middleName" && - key !== "category" && - key !== "priority" && - key !== "level" && - key !== "ratio" && - key !== "step" && - key !== "tags" && - key !== "aliases" && - key !== "roles" && - key !== "idOrName" && - key !== "mode" && - key !== "payload" && - key !== "detail" && - key !== "shapeOrName" && - key !== "measurements" && - key !== "shapes" && - key !== "segments" && - key !== "slots" && - key !== "grid" && - key !== "location" && - key !== "audit" && - key !== "rows" && - key !== "ledger" && - key !== "metadata" && - key !== "quotas" && - key !== "tokens" && - key !== "nicknames" && - key !== "choices" && - key !== "extras" && - key !== "shape" && - key !== "note" && - key !== "address" && - key !== "labels" && - key !== "settings" && - key !== "attributes" && - key !== "contact" - ) { - violations.push({ path: key, reason: "unknown field" }); + let contact: ContactTs | undefined = undefined as unknown as + | ContactTs + | undefined; + if (raw.contact === null) { + violations.push({ path: "contact", reason: "explicit null not allowed" }); + } else if (raw.contact !== undefined) { + try { + contact = contactTsTransferTypeConverter.fromTransferType(raw.contact); + } catch (error) { + __nexgenDefinitions.collect(violations, "contact", error); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Showcase = { - kind, - revision, - enabled, - status, - tier, - scale, - name, - count, - active, - category, - }; - if (nickname !== undefined) { - out.nickname = nickname; - } - if (code !== undefined) { - out.code = code; - } - if (sku !== undefined) { - out.sku = sku; - } - if (phrase !== undefined) { - out.phrase = phrase; - } - if (requestId !== undefined) { - out.requestId = requestId; - } - if (contactEmail !== undefined) { - out.contactEmail = contactEmail; - } - if (host !== undefined) { - out.host = host; - } - if (homepage !== undefined) { - out.homepage = homepage; - } - if (gateway !== undefined) { - out.gateway = gateway; - } - if (blob !== undefined) { - out.blob = blob; - } - if (urlBlob !== undefined) { - out.urlBlob = urlBlob; - } - if (retries !== undefined) { - out.retries = retries; - } - if (verbose !== undefined) { - out.verbose = verbose; - } - if (greeting !== undefined) { - out.greeting = greeting; - } - if (debug !== undefined) { - out.debug = debug; - } - if (legacyIdTs !== undefined) { - out.legacyIdTs = legacyIdTs; - } - if (middleName !== undefined) { - out.middleName = middleName; - } - if (priority !== undefined) { - out.priority = priority; - } - if (level !== undefined) { - out.level = level; - } - if (ratio !== undefined) { - out.ratio = ratio; - } - if (step !== undefined) { - out.step = step; - } - if (tags !== undefined) { - out.tags = tags; - } - if (aliases !== undefined) { - out.aliases = aliases; - } - if (roles !== undefined) { - out.roles = roles; - } - if (idOrName !== undefined) { - out.idOrName = idOrName; - } - if (mode !== undefined) { - out.mode = mode; - } - if (payload !== undefined) { - out.payload = payload; - } - if (detail !== undefined) { - out.detail = detail; - } - if (shapeOrName !== undefined) { - out.shapeOrName = shapeOrName; - } - if (measurements !== undefined) { - out.measurements = measurements; - } - if (shapes !== undefined) { - out.shapes = shapes; - } - if (segments !== undefined) { - out.segments = segments; - } - if (slots !== undefined) { - out.slots = slots; - } - if (grid !== undefined) { - out.grid = grid; - } - if (location !== undefined) { - out.location = location; - } - if (audit !== undefined) { - out.audit = audit; - } - if (rows !== undefined) { - out.rows = rows; - } - if (ledgerTs !== undefined) { - out.ledgerTs = ledgerTs; - } - if (metadata !== undefined) { - out.metadata = metadata; - } - if (quotas !== undefined) { - out.quotas = quotas; - } - if (tokens !== undefined) { - out.tokens = tokens; - } - if (nicknames !== undefined) { - out.nicknames = nicknames; - } - if (choices !== undefined) { - out.choices = choices; - } - if (extras !== undefined) { - out.extras = extras; - } - if (shape !== undefined) { - out.shape = shape; - } - if (note !== undefined) { - out.note = note; - } - if (address !== undefined) { - out.address = address; - } - if (labels !== undefined) { - out.labels = labels; - } - if (settings !== undefined) { - out.settings = settings; - } - if (attributes !== undefined) { - out.attributes = attributes; - } - if (contact !== undefined) { - out.contact = contact; - } - return out; - } + for (const key of Object.keys(raw)) { + if ( + key !== "kind" && + key !== "revision" && + key !== "enabled" && + key !== "status" && + key !== "tier" && + key !== "scale" && + key !== "name" && + key !== "count" && + key !== "active" && + key !== "nickname" && + key !== "code" && + key !== "sku" && + key !== "phrase" && + key !== "requestId" && + key !== "contactEmail" && + key !== "host" && + key !== "homepage" && + key !== "gateway" && + key !== "blob" && + key !== "urlBlob" && + key !== "retries" && + key !== "verbose" && + key !== "greeting" && + key !== "debug" && + key !== "legacyId" && + key !== "middleName" && + key !== "category" && + key !== "priority" && + key !== "level" && + key !== "ratio" && + key !== "step" && + key !== "tags" && + key !== "aliases" && + key !== "roles" && + key !== "idOrName" && + key !== "mode" && + key !== "payload" && + key !== "detail" && + key !== "shapeOrName" && + key !== "measurements" && + key !== "shapes" && + key !== "segments" && + key !== "slots" && + key !== "grid" && + key !== "location" && + key !== "audit" && + key !== "rows" && + key !== "ledger" && + key !== "metadata" && + key !== "quotas" && + key !== "tokens" && + key !== "nicknames" && + key !== "choices" && + key !== "extras" && + key !== "shape" && + key !== "note" && + key !== "address" && + key !== "labels" && + key !== "settings" && + key !== "attributes" && + key !== "contact" + ) { + violations.push({ path: key, reason: "unknown field" }); + } + } - public toIntermediate(value: Showcase): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "showcase") { - violations.push({ path: "kind", reason: `must equal "showcase"` }); - } - out.kind = value.kind; - if (value.revision !== 1) { - violations.push({ path: "revision", reason: `must equal 1` }); - } - out.revision = value.revision; - if (value.enabled !== true) { - violations.push({ path: "enabled", reason: `must equal true` }); - } - out.enabled = value.enabled; - if ( - value.status !== "active" && - value.status !== "inactive" && - value.status !== "pending" - ) { - violations.push({ - path: "status", - reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(value.status)}`, - }); - } - out.status = value.status; - if (value.tier !== 1 && value.tier !== 2 && value.tier !== 3) { - violations.push({ - path: "tier", - reason: `must be one of [1, 2, 3], got ${JSON.stringify(value.tier)}`, - }); - } - out.tier = value.tier; - if (value.scale !== 1.5 && value.scale !== 2.5) { - violations.push({ - path: "scale", - reason: `must be one of [1.5, 2.5], got ${JSON.stringify(value.scale)}`, - }); - } - out.scale = value.scale; - if ([...value.name].length < 1) { - violations.push({ - path: "name", - reason: `must have length >= 1, got ${[...value.name].length}`, - }); - } - if ([...value.name].length > 64) { - violations.push({ - path: "name", - reason: `must have length <= 64, got ${[...value.name].length}`, - }); - } - out.name = value.name; - out.count = value.count; - out.active = value.active; - if (value.nickname !== undefined) { - if ([...value.nickname].length > 12) { - violations.push({ - path: "nickname", - reason: `must have length <= 12, got ${[...value.nickname].length}`, - }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - out.nickname = value.nickname; - } - if (value.code !== undefined) { - if ([...value.code].length < 2) { - violations.push({ - path: "code", - reason: `must have length >= 2, got ${[...value.code].length}`, - }); + const out: Showcase = { + kind, + revision, + enabled, + status, + tier, + scale, + name, + count, + active, + category, + }; + if (nickname !== undefined) { + out.nickname = nickname; } - if ([...value.code].length > 5) { - violations.push({ - path: "code", - reason: `must have length <= 5, got ${[...value.code].length}`, - }); + if (code !== undefined) { + out.code = code; } - out.code = value.code; - } - if (value.sku !== undefined) { - if (!PATTERN_821EF753B4B37A85.test(value.sku)) { - violations.push({ - path: "sku", - reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(value.sku)}`, - }); + if (sku !== undefined) { + out.sku = sku; } - out.sku = value.sku; - } - if (value.phrase !== undefined) { - if (!PATTERN_AF8AB992526D6283.test(value.phrase)) { - violations.push({ - path: "phrase", - reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(value.phrase)}`, - }); + if (phrase !== undefined) { + out.phrase = phrase; } - out.phrase = value.phrase; - } - if (value.requestId !== undefined) { - if (!PATTERN_52CD3CCF2038430A.test(value.requestId)) { - violations.push({ - path: "requestId", - reason: `must be a valid uuid, got ${JSON.stringify(value.requestId)}`, - }); + if (requestId !== undefined) { + out.requestId = requestId; + } + if (contactEmail !== undefined) { + out.contactEmail = contactEmail; + } + if (host !== undefined) { + out.host = host; + } + if (homepage !== undefined) { + out.homepage = homepage; + } + if (gateway !== undefined) { + out.gateway = gateway; + } + if (blob !== undefined) { + out.blob = blob; + } + if (urlBlob !== undefined) { + out.urlBlob = urlBlob; + } + if (retries !== undefined) { + out.retries = retries; + } + if (verbose !== undefined) { + out.verbose = verbose; + } + if (greeting !== undefined) { + out.greeting = greeting; + } + if (debug !== undefined) { + out.debug = debug; + } + if (legacyIdTs !== undefined) { + out.legacyIdTs = legacyIdTs; + } + if (middleName !== undefined) { + out.middleName = middleName; + } + if (priority !== undefined) { + out.priority = priority; + } + if (level !== undefined) { + out.level = level; + } + if (ratio !== undefined) { + out.ratio = ratio; + } + if (step !== undefined) { + out.step = step; + } + if (tags !== undefined) { + out.tags = tags; + } + if (aliases !== undefined) { + out.aliases = aliases; + } + if (roles !== undefined) { + out.roles = roles; + } + if (idOrName !== undefined) { + out.idOrName = idOrName; + } + if (mode !== undefined) { + out.mode = mode; + } + if (payload !== undefined) { + out.payload = payload; + } + if (detail !== undefined) { + out.detail = detail; + } + if (shapeOrName !== undefined) { + out.shapeOrName = shapeOrName; + } + if (measurements !== undefined) { + out.measurements = measurements; + } + if (shapes !== undefined) { + out.shapes = shapes; + } + if (segments !== undefined) { + out.segments = segments; + } + if (slots !== undefined) { + out.slots = slots; + } + if (grid !== undefined) { + out.grid = grid; + } + if (location !== undefined) { + out.location = location; + } + if (audit !== undefined) { + out.audit = audit; + } + if (rows !== undefined) { + out.rows = rows; + } + if (ledgerTs !== undefined) { + out.ledgerTs = ledgerTs; + } + if (metadata !== undefined) { + out.metadata = metadata; + } + if (quotas !== undefined) { + out.quotas = quotas; + } + if (tokens !== undefined) { + out.tokens = tokens; + } + if (nicknames !== undefined) { + out.nicknames = nicknames; + } + if (choices !== undefined) { + out.choices = choices; + } + if (extras !== undefined) { + out.extras = extras; + } + if (shape !== undefined) { + out.shape = shape; } - out.requestId = value.requestId; + if (note !== undefined) { + out.note = note; + } + if (address !== undefined) { + out.address = address; + } + if (labels !== undefined) { + out.labels = labels; + } + if (settings !== undefined) { + out.settings = settings; + } + if (attributes !== undefined) { + out.attributes = attributes; + } + if (contact !== undefined) { + out.contact = contact; + } + return out; } - if (value.contactEmail !== undefined) { + + public toTransferType(value: Showcase): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "showcase") { + violations.push({ path: "kind", reason: `must equal "showcase"` }); + } + out.kind = value.kind; + if (value.revision !== 1) { + violations.push({ path: "revision", reason: `must equal 1` }); + } + out.revision = value.revision; + if (value.enabled !== true) { + violations.push({ path: "enabled", reason: `must equal true` }); + } + out.enabled = value.enabled; if ( - [...value.contactEmail].length > 254 || - !PATTERN_E7C805FB9E8E4DC4.test(value.contactEmail) + value.status !== "active" && + value.status !== "inactive" && + value.status !== "pending" ) { violations.push({ - path: "contactEmail", - reason: `must be a valid email, got ${JSON.stringify(value.contactEmail)}`, + path: "status", + reason: `must be one of ["active", "inactive", "pending"], got ${JSON.stringify(value.status)}`, }); } - out.contactEmail = value.contactEmail; - } - if (value.host !== undefined) { - if ([...value.host].length > 253 || !PATTERN_BB674DB499542D4F.test(value.host)) { + out.status = value.status; + if (value.tier !== 1 && value.tier !== 2 && value.tier !== 3) { violations.push({ - path: "host", - reason: `must be a valid hostname, got ${JSON.stringify(value.host)}`, + path: "tier", + reason: `must be one of [1, 2, 3], got ${JSON.stringify(value.tier)}`, }); } - out.host = value.host; - } - if (value.homepage !== undefined) { - if (!PATTERN_2F0C822905CC055D.test(value.homepage)) { + out.tier = value.tier; + if (value.scale !== 1.5 && value.scale !== 2.5) { violations.push({ - path: "homepage", - reason: `must be a valid uri, got ${JSON.stringify(value.homepage)}`, + path: "scale", + reason: `must be one of [1.5, 2.5], got ${JSON.stringify(value.scale)}`, }); } - out.homepage = value.homepage; - } - if (value.gateway !== undefined) { - if (!PATTERN_F5FB862A44510B9D.test(value.gateway)) { + out.scale = value.scale; + if ([...value.name].length < 1) { violations.push({ - path: "gateway", - reason: `must be a valid ipv4, got ${JSON.stringify(value.gateway)}`, + path: "name", + reason: `must have length >= 1, got ${[...value.name].length}`, }); } - out.gateway = value.gateway; - } - if (value.blob !== undefined) { - out.blob = __nexgenDefinitions.bytesToBase64(value.blob); - } - if (value.urlBlob !== undefined) { - out.urlBlob = __nexgenDefinitions.bytesToBase64Url(value.urlBlob); - } - if (value.retries !== undefined) { - out.retries = value.retries; - } - if (value.verbose !== undefined) { - out.verbose = value.verbose; - } - if (value.greeting !== undefined) { - out.greeting = value.greeting; - } - if (value.debug !== undefined) { - out.debug = value.debug; - } - if (value.legacyIdTs !== undefined) { - out.legacyId = value.legacyIdTs; - } - if (value.middleName !== undefined) { - out.middleName = value.middleName; - } - out.category = value.category; - if (value.priority !== undefined) { - if (value.priority < 1) { + if ([...value.name].length > 64) { violations.push({ - path: "priority", - reason: `must be >= 1, got ${value.priority}`, + path: "name", + reason: `must have length <= 64, got ${[...value.name].length}`, }); } - if (value.priority > 10) { - violations.push({ - path: "priority", - reason: `must be <= 10, got ${value.priority}`, - }); + out.name = value.name; + out.count = value.count; + out.active = value.active; + if (value.nickname !== undefined) { + if ([...value.nickname].length > 12) { + violations.push({ + path: "nickname", + reason: `must have length <= 12, got ${[...value.nickname].length}`, + }); + } + out.nickname = value.nickname; } - out.priority = value.priority; - } - if (value.level !== undefined) { - if (value.level <= 0) { - violations.push({ path: "level", reason: `must be > 0, got ${value.level}` }); + if (value.code !== undefined) { + if ([...value.code].length < 2) { + violations.push({ + path: "code", + reason: `must have length >= 2, got ${[...value.code].length}`, + }); + } + if ([...value.code].length > 5) { + violations.push({ + path: "code", + reason: `must have length <= 5, got ${[...value.code].length}`, + }); + } + out.code = value.code; } - out.level = value.level; - } - if (value.ratio !== undefined) { - if (value.ratio < 5) { - violations.push({ path: "ratio", reason: `must be >= 5, got ${value.ratio}` }); + if (value.sku !== undefined) { + if (!PATTERN_821EF753B4B37A85.test(value.sku)) { + violations.push({ + path: "sku", + reason: `must match pattern ^[A-Z]{2,4}\$, got ${JSON.stringify(value.sku)}`, + }); + } + out.sku = value.sku; } - if (value.ratio % 5 !== 0) { - violations.push({ - path: "ratio", - reason: `must be a multiple of 5, got ${value.ratio}`, - }); + if (value.phrase !== undefined) { + if (!PATTERN_AF8AB992526D6283.test(value.phrase)) { + violations.push({ + path: "phrase", + reason: `must match pattern ^[^\\t\\n\\x0B\\f\\r ]+[\\t\\n\\x0B\\f\\r ][^\\t\\n\\x0B\\f\\r ]+\$, got ${JSON.stringify(value.phrase)}`, + }); + } + out.phrase = value.phrase; } - out.ratio = value.ratio; - } - if (value.step !== undefined) { - if (value.step % 3 !== 0) { - violations.push({ - path: "step", - reason: `must be a multiple of 3, got ${value.step}`, - }); + if (value.requestId !== undefined) { + if (!PATTERN_52CD3CCF2038430A.test(value.requestId)) { + violations.push({ + path: "requestId", + reason: `must be a valid uuid, got ${JSON.stringify(value.requestId)}`, + }); + } + out.requestId = value.requestId; } - out.step = value.step; - } - if (value.tags !== undefined) { - if (value.tags.length < 1) { - violations.push({ - path: "tags", - reason: `must have at least 1 items, got ${value.tags.length}`, - }); + if (value.contactEmail !== undefined) { + if ( + [...value.contactEmail].length > 254 || + !PATTERN_E7C805FB9E8E4DC4.test(value.contactEmail) + ) { + violations.push({ + path: "contactEmail", + reason: `must be a valid email, got ${JSON.stringify(value.contactEmail)}`, + }); + } + out.contactEmail = value.contactEmail; + } + if (value.host !== undefined) { + if ( + [...value.host].length > 253 || + !PATTERN_BB674DB499542D4F.test(value.host) + ) { + violations.push({ + path: "host", + reason: `must be a valid hostname, got ${JSON.stringify(value.host)}`, + }); + } + out.host = value.host; + } + if (value.homepage !== undefined) { + if (!PATTERN_2F0C822905CC055D.test(value.homepage)) { + violations.push({ + path: "homepage", + reason: `must be a valid uri, got ${JSON.stringify(value.homepage)}`, + }); + } + out.homepage = value.homepage; + } + if (value.gateway !== undefined) { + if (!PATTERN_F5FB862A44510B9D.test(value.gateway)) { + violations.push({ + path: "gateway", + reason: `must be a valid ipv4, got ${JSON.stringify(value.gateway)}`, + }); + } + out.gateway = value.gateway; + } + if (value.blob !== undefined) { + out.blob = __nexgenDefinitions.bytesToBase64(value.blob); + } + if (value.urlBlob !== undefined) { + out.urlBlob = __nexgenDefinitions.bytesToBase64Url(value.urlBlob); + } + if (value.retries !== undefined) { + out.retries = value.retries; + } + if (value.verbose !== undefined) { + out.verbose = value.verbose; + } + if (value.greeting !== undefined) { + out.greeting = value.greeting; + } + if (value.debug !== undefined) { + out.debug = value.debug; + } + if (value.legacyIdTs !== undefined) { + out.legacyId = value.legacyIdTs; + } + if (value.middleName !== undefined) { + out.middleName = value.middleName; + } + out.category = value.category; + if (value.priority !== undefined) { + if (value.priority < 1) { + violations.push({ + path: "priority", + reason: `must be >= 1, got ${value.priority}`, + }); + } + if (value.priority > 10) { + violations.push({ + path: "priority", + reason: `must be <= 10, got ${value.priority}`, + }); + } + out.priority = value.priority; + } + if (value.level !== undefined) { + if (value.level <= 0) { + violations.push({ path: "level", reason: `must be > 0, got ${value.level}` }); + } + out.level = value.level; + } + if (value.ratio !== undefined) { + if (value.ratio < 5) { + violations.push({ + path: "ratio", + reason: `must be >= 5, got ${value.ratio}`, + }); + } + if (value.ratio % 5 !== 0) { + violations.push({ + path: "ratio", + reason: `must be a multiple of 5, got ${value.ratio}`, + }); + } + out.ratio = value.ratio; + } + if (value.step !== undefined) { + if (value.step % 3 !== 0) { + violations.push({ + path: "step", + reason: `must be a multiple of 3, got ${value.step}`, + }); + } + out.step = value.step; + } + if (value.tags !== undefined) { + if (value.tags.length < 1) { + violations.push({ + path: "tags", + reason: `must have at least 1 items, got ${value.tags.length}`, + }); + } + if (value.tags.length > 5) { + violations.push({ + path: "tags", + reason: `must have at most 5 items, got ${value.tags.length}`, + }); + } + out.tags = value.tags; + } + if (value.aliases !== undefined) { + { + const seen = new Map(); + value.aliases.forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "aliases", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } + out.aliases = value.aliases; + } + if (value.roles !== undefined) { + { + const matchCount = value.roles.filter( + (element) => element === "admin", + ).length; + if (matchCount < 1) { + violations.push({ + path: "roles", + reason: `too few matching items: at least 1, got ${matchCount}`, + }); + } + if (matchCount > 2) { + violations.push({ + path: "roles", + reason: `too many matching items: at most 2, got ${matchCount}`, + }); + } + } + out.roles = value.roles; + } + if (value.idOrName !== undefined) { + if (typeof value.idOrName === "string") { + if ([...(value.idOrName as string)].length < 3) { + violations.push({ + path: "idOrName", + reason: `must have length >= 3, got ${[...(value.idOrName as string)].length}`, + }); + } + } + if ( + typeof value.idOrName === "number" && + Number.isSafeInteger(value.idOrName) + ) { + if ((value.idOrName as number) < 1) { + violations.push({ + path: "idOrName", + reason: `must be >= 1, got ${value.idOrName as number}`, + }); + } + } + out.idOrName = value.idOrName; + } + if (value.mode !== undefined) { + if (typeof value.mode === "string") { + if ( + (value.mode as "auto" | "manual") !== "auto" && + (value.mode as "auto" | "manual") !== "manual" + ) { + violations.push({ + path: "mode", + reason: `must be one of ["auto", "manual"], got ${JSON.stringify(value.mode as "auto" | "manual")}`, + }); + } + } + if (typeof value.mode === "number" && Number.isSafeInteger(value.mode)) { + if ((value.mode as number) < 0) { + violations.push({ + path: "mode", + reason: `must be >= 0, got ${value.mode as number}`, + }); + } + } + out.mode = value.mode; + } + if (value.payload !== undefined) { + out.payload = value.payload; + } + if (value.detail !== undefined) { + out.detail = serializeShowcaseDetail(value.detail); + } + if (value.shapeOrName !== undefined) { + if (typeof value.shapeOrName === "string") { + if ([...(value.shapeOrName as string)].length > 32) { + violations.push({ + path: "shapeOrName", + reason: `must have length <= 32, got ${[...(value.shapeOrName as string)].length}`, + }); + } + } + out.shapeOrName = serializeShowcaseShapeOrName(value.shapeOrName); + } + if (value.measurements !== undefined) { + if (Array.isArray(value.measurements)) { + if ((value.measurements as number[]).length < 1) { + violations.push({ + path: "measurements", + reason: `must have at least 1 items, got ${(value.measurements as number[]).length}`, + }); + } + { + const seen = new Map(); + (value.measurements as number[]).forEach((element, index) => { + if (seen.has(element)) { + violations.push({ + path: "measurements", + reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, + }); + } else { + seen.set(element, index); + } + }); + } + } + if (typeof value.measurements === "string") { + if (!PATTERN_C182F89FDB221836.test(value.measurements as string)) { + violations.push({ + path: "measurements", + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(value.measurements as string)}`, + }); + } + } + out.measurements = value.measurements; + } + if (value.shapes !== undefined) { + out.shapes = value.shapes.map((element) => + shapeTransferTypeConverter.toTransferType(element), + ); + } + if (value.segments !== undefined) { + out.segments = value.segments.map((element) => + showcaseSegmentsItemTransferTypeConverter.toTransferType(element), + ); + } + if (value.slots !== undefined) { + out.slots = value.slots; + } + if (value.grid !== undefined) { + out.grid = value.grid; + } + if (value.location !== undefined) { + out.location = showcaseLocationTransferTypeConverter.toTransferType( + value.location, + ); + } + if (value.audit !== undefined) { + out.audit = + value.audit === null + ? null + : showcaseAuditTransferTypeConverter.toTransferType(value.audit); + } + if (value.rows !== undefined) { + out.rows = value.rows.map((element) => + showcaseRowsItemTransferTypeConverter.toTransferType(element), + ); + } + if (value.ledgerTs !== undefined) { + out.ledger = showcaseLedgerTransferTypeConverter.toTransferType(value.ledgerTs); + } + if (value.metadata !== undefined) { + out.metadata = showcaseMetadataTransferTypeConverter.toTransferType( + value.metadata, + ); + } + if (value.quotas !== undefined) { + out.quotas = quotasTransferTypeConverter.toTransferType(value.quotas); + } + if (value.tokens !== undefined) { + out.tokens = tokensTransferTypeConverter.toTransferType(value.tokens); } - if (value.tags.length > 5) { - violations.push({ - path: "tags", - reason: `must have at most 5 items, got ${value.tags.length}`, - }); + if (value.nicknames !== undefined) { + out.nicknames = nicknamesTransferTypeConverter.toTransferType(value.nicknames); } - out.tags = value.tags; - } - if (value.aliases !== undefined) { - { - const seen = new Map(); - value.aliases.forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "aliases", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); + if (value.choices !== undefined) { + out.choices = choicesTransferTypeConverter.toTransferType(value.choices); } - out.aliases = value.aliases; - } - if (value.roles !== undefined) { - { - const matchCount = value.roles.filter((element) => element === "admin").length; - if (matchCount < 1) { - violations.push({ - path: "roles", - reason: `too few matching items: at least 1, got ${matchCount}`, - }); - } - if (matchCount > 2) { - violations.push({ - path: "roles", - reason: `too many matching items: at most 2, got ${matchCount}`, - }); - } + if (value.extras !== undefined) { + out.extras = extrasTransferTypeConverter.toTransferType(value.extras); } - out.roles = value.roles; - } - if (value.idOrName !== undefined) { - if (typeof value.idOrName === "string") { - if ([...(value.idOrName as string)].length < 3) { - violations.push({ - path: "idOrName", - reason: `must have length >= 3, got ${[...(value.idOrName as string)].length}`, - }); - } + if (value.shape !== undefined) { + out.shape = shapeTransferTypeConverter.toTransferType(value.shape); } - if (typeof value.idOrName === "number" && Number.isSafeInteger(value.idOrName)) { - if ((value.idOrName as number) < 1) { - violations.push({ - path: "idOrName", - reason: `must be >= 1, got ${value.idOrName as number}`, - }); - } + if (value.note !== undefined) { + out.note = noteTransferTypeConverter.toTransferType(value.note); } - out.idOrName = value.idOrName; - } - if (value.mode !== undefined) { - if (typeof value.mode === "string") { - if ( - (value.mode as "auto" | "manual") !== "auto" && - (value.mode as "auto" | "manual") !== "manual" - ) { - violations.push({ - path: "mode", - reason: `must be one of ["auto", "manual"], got ${JSON.stringify(value.mode as "auto" | "manual")}`, - }); - } + if (value.address !== undefined) { + out.address = addressTransferTypeConverter.toTransferType(value.address); } - if (typeof value.mode === "number" && Number.isSafeInteger(value.mode)) { - if ((value.mode as number) < 0) { - violations.push({ - path: "mode", - reason: `must be >= 0, got ${value.mode as number}`, - }); - } + if (value.labels !== undefined) { + out.labels = labelsTransferTypeConverter.toTransferType(value.labels); } - out.mode = value.mode; - } - if (value.payload !== undefined) { - out.payload = value.payload; - } - if (value.detail !== undefined) { - out.detail = serializeShowcaseDetail(value.detail); - } - if (value.shapeOrName !== undefined) { - if (typeof value.shapeOrName === "string") { - if ([...(value.shapeOrName as string)].length > 32) { - violations.push({ - path: "shapeOrName", - reason: `must have length <= 32, got ${[...(value.shapeOrName as string)].length}`, - }); - } + if (value.settings !== undefined) { + out.settings = settingsTransferTypeConverter.toTransferType(value.settings); } - out.shapeOrName = serializeShowcaseShapeOrName(value.shapeOrName); - } - if (value.measurements !== undefined) { - if (Array.isArray(value.measurements)) { - if ((value.measurements as number[]).length < 1) { - violations.push({ - path: "measurements", - reason: `must have at least 1 items, got ${(value.measurements as number[]).length}`, - }); - } - { - const seen = new Map(); - (value.measurements as number[]).forEach((element, index) => { - if (seen.has(element)) { - violations.push({ - path: "measurements", - reason: `duplicate items: element at index ${index} equals index ${seen.get(element)}`, - }); - } else { - seen.set(element, index); - } - }); - } + if (value.attributes !== undefined) { + out.attributes = attributesTransferTypeConverter.toTransferType( + value.attributes, + ); } - if (typeof value.measurements === "string") { - if (!PATTERN_C182F89FDB221836.test(value.measurements as string)) { - violations.push({ - path: "measurements", - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(value.measurements as string)}`, - }); - } + if (value.contact !== undefined) { + out.contact = contactTsTransferTypeConverter.toTransferType(value.contact); } - out.measurements = value.measurements; - } - if (value.shapes !== undefined) { - out.shapes = value.shapes.map((element) => - new ShapeMapper().toIntermediate(element), - ); - } - if (value.segments !== undefined) { - out.segments = value.segments.map((element) => - new ShowcaseSegmentsItemMapper().toIntermediate(element), - ); - } - if (value.slots !== undefined) { - out.slots = value.slots; - } - if (value.grid !== undefined) { - out.grid = value.grid; - } - if (value.location !== undefined) { - out.location = new ShowcaseLocationMapper().toIntermediate(value.location); - } - if (value.audit !== undefined) { - out.audit = - value.audit === null - ? null - : new ShowcaseAuditMapper().toIntermediate(value.audit); - } - if (value.rows !== undefined) { - out.rows = value.rows.map((element) => - new ShowcaseRowsItemMapper().toIntermediate(element), - ); - } - if (value.ledgerTs !== undefined) { - out.ledger = new ShowcaseLedgerMapper().toIntermediate(value.ledgerTs); - } - if (value.metadata !== undefined) { - out.metadata = new ShowcaseMetadataMapper().toIntermediate(value.metadata); - } - if (value.quotas !== undefined) { - out.quotas = new QuotasMapper().toIntermediate(value.quotas); - } - if (value.tokens !== undefined) { - out.tokens = new TokensMapper().toIntermediate(value.tokens); - } - if (value.nicknames !== undefined) { - out.nicknames = new NicknamesMapper().toIntermediate(value.nicknames); - } - if (value.choices !== undefined) { - out.choices = new ChoicesMapper().toIntermediate(value.choices); - } - if (value.extras !== undefined) { - out.extras = new ExtrasMapper().toIntermediate(value.extras); - } - if (value.shape !== undefined) { - out.shape = new ShapeMapper().toIntermediate(value.shape); - } - if (value.note !== undefined) { - out.note = new NoteMapper().toIntermediate(value.note); - } - if (value.address !== undefined) { - out.address = new AddressMapper().toIntermediate(value.address); - } - if (value.labels !== undefined) { - out.labels = new LabelsMapper().toIntermediate(value.labels); - } - if (value.settings !== undefined) { - out.settings = new SettingsMapper().toIntermediate(value.settings); - } - if (value.attributes !== undefined) { - out.attributes = new AttributesMapper().toIntermediate(value.attributes); - } - if (value.contact !== undefined) { - out.contact = new ContactTsMapper().toIntermediate(value.contact); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_AUDIT_DECLARED = new Set(["by"]); -export class ShowcaseAuditMapper { - public fromIntermediate(raw: unknown): ShowcaseAudit { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseAuditTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseAudit { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let by: string = undefined as unknown as string; - if (raw.by === undefined || raw.by === null) { - violations.push({ path: "by", reason: "required" }); - } else { - if (typeof raw.by !== "string") { - violations.push({ path: "by", reason: "expected string" }); + let by: string = undefined as unknown as string; + if (raw.by === undefined || raw.by === null) { + violations.push({ path: "by", reason: "required" }); } else { - by = raw.by; - if ([...raw.by].length < 1) { - violations.push({ - path: "by", - reason: `must have length >= 1, got ${[...raw.by].length}`, - }); + if (typeof raw.by !== "string") { + violations.push({ path: "by", reason: "expected string" }); + } else { + by = raw.by; + if ([...raw.by].length < 1) { + violations.push({ + path: "by", + reason: `must have length >= 1, got ${[...raw.by].length}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_AUDIT_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_AUDIT_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseAudit = { by, additionalProperties }; + return out; } - const out: ShowcaseAudit = { by, additionalProperties }; - return out; - } - public toIntermediate(value: ShowcaseAudit): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.by].length < 1) { - violations.push({ - path: "by", - reason: `must have length >= 1, got ${[...value.by].length}`, - }); - } - out.by = value.by; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseAudit): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.by].length < 1) { + violations.push({ + path: "by", + reason: `must have length >= 1, got ${[...value.by].length}`, + }); + } + out.by = value.by; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_DETAIL_OBJECT_DECLARED = new Set(["code", "hint"]); -export class ShowcaseDetailObjectMapper { - public fromIntermediate(raw: unknown): ShowcaseDetailObject { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseDetailObjectTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseDetailObject { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let code: string = undefined as unknown as string; - if (raw.code === undefined || raw.code === null) { - violations.push({ path: "code", reason: "required" }); - } else { - if (typeof raw.code !== "string") { - violations.push({ path: "code", reason: "expected string" }); + let code: string = undefined as unknown as string; + if (raw.code === undefined || raw.code === null) { + violations.push({ path: "code", reason: "required" }); } else { - code = raw.code; - if ([...raw.code].length < 1) { - violations.push({ - path: "code", - reason: `must have length >= 1, got ${[...raw.code].length}`, - }); + if (typeof raw.code !== "string") { + violations.push({ path: "code", reason: "expected string" }); + } else { + code = raw.code; + if ([...raw.code].length < 1) { + violations.push({ + path: "code", + reason: `must have length >= 1, got ${[...raw.code].length}`, + }); + } } } - } - let hint: string | undefined = undefined as unknown as string | undefined; - if (raw.hint === null) { - violations.push({ path: "hint", reason: "explicit null not allowed" }); - } else if (raw.hint !== undefined) { - if (typeof raw.hint !== "string") { - violations.push({ path: "hint", reason: "expected string" }); - } else { - hint = raw.hint; + let hint: string | undefined = undefined as unknown as string | undefined; + if (raw.hint === null) { + violations.push({ path: "hint", reason: "explicit null not allowed" }); + } else if (raw.hint !== undefined) { + if (typeof raw.hint !== "string") { + violations.push({ path: "hint", reason: "expected string" }); + } else { + hint = raw.hint; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_DETAIL_OBJECT_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_DETAIL_OBJECT_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseDetailObject = { code, additionalProperties }; - if (hint !== undefined) { - out.hint = hint; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseDetailObject = { code, additionalProperties }; + if (hint !== undefined) { + out.hint = hint; + } + return out; } - return out; - } - public toIntermediate(value: ShowcaseDetailObject): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.code].length < 1) { - violations.push({ - path: "code", - reason: `must have length >= 1, got ${[...value.code].length}`, - }); - } - out.code = value.code; - if (value.hint !== undefined) { - out.hint = value.hint; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseDetailObject): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.code].length < 1) { + violations.push({ + path: "code", + reason: `must have length >= 1, got ${[...value.code].length}`, + }); + } + out.code = value.code; + if (value.hint !== undefined) { + out.hint = value.hint; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class ShowcaseLedgerMapper { - public fromIntermediate(raw: unknown): ShowcaseLedger { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLedgerTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLedger { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: ShowcaseLedgerValue | undefined = undefined; - try { - entry = new ShowcaseLedgerValueMapper().fromIntermediate(raw[key]); - } catch (error) { - __nexgenDefinitions.collect(violations, key, error); + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: ShowcaseLedgerValue | undefined = undefined; + try { + entry = showcaseLedgerValueTransferTypeConverter.fromTransferType(raw[key]); + } catch (error) { + __nexgenDefinitions.collect(violations, key, error); + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } } - if (entry !== undefined) { - additionalProperties[key] = entry; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return { additionalProperties }; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - public toIntermediate(value: ShowcaseLedger): unknown { - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = new ShowcaseLedgerValueMapper().toIntermediate(entry); + public toTransferType(value: ShowcaseLedger): unknown { + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = showcaseLedgerValueTransferTypeConverter.toTransferType(entry); + } + return out; } - return out; - } -} + })(); const SHOWCASE_LEDGER_VALUE_DECLARED = new Set(["amount"]); -export class ShowcaseLedgerValueMapper { - public fromIntermediate(raw: unknown): ShowcaseLedgerValue { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLedgerValueTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLedgerValue { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let amount: number = undefined as unknown as number; - if (raw.amount === undefined || raw.amount === null) { - violations.push({ path: "amount", reason: "required" }); - } else { - if (typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount)) { - violations.push({ path: "amount", reason: "expected integer" }); + let amount: number = undefined as unknown as number; + if (raw.amount === undefined || raw.amount === null) { + violations.push({ path: "amount", reason: "required" }); } else { - amount = raw.amount; - if (raw.amount < 0) { - violations.push({ - path: "amount", - reason: `must be >= 0, got ${raw.amount}`, - }); + if (typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount)) { + violations.push({ path: "amount", reason: "expected integer" }); + } else { + amount = raw.amount; + if (raw.amount < 0) { + violations.push({ + path: "amount", + reason: `must be >= 0, got ${raw.amount}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LEDGER_VALUE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LEDGER_VALUE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseLedgerValue = { amount, additionalProperties }; + return out; } - const out: ShowcaseLedgerValue = { amount, additionalProperties }; - return out; - } - public toIntermediate(value: ShowcaseLedgerValue): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.amount < 0) { - violations.push({ path: "amount", reason: `must be >= 0, got ${value.amount}` }); - } - out.amount = value.amount; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseLedgerValue): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.amount < 0) { + violations.push({ + path: "amount", + reason: `must be >= 0, got ${value.amount}`, + }); + } + out.amount = value.amount; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const SHOWCASE_LOCATION_DECLARED = new Set(["city", "geo"]); -export class ShowcaseLocationMapper { - public fromIntermediate(raw: unknown): ShowcaseLocation { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const showcaseLocationTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLocation { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let city: string = undefined as unknown as string; - if (raw.city === undefined || raw.city === null) { - violations.push({ path: "city", reason: "required" }); - } else { - if (typeof raw.city !== "string") { - violations.push({ path: "city", reason: "expected string" }); + let city: string = undefined as unknown as string; + if (raw.city === undefined || raw.city === null) { + violations.push({ path: "city", reason: "required" }); } else { - city = raw.city; - if ([...raw.city].length < 1) { - violations.push({ - path: "city", - reason: `must have length >= 1, got ${[...raw.city].length}`, - }); + if (typeof raw.city !== "string") { + violations.push({ path: "city", reason: "expected string" }); + } else { + city = raw.city; + if ([...raw.city].length < 1) { + violations.push({ + path: "city", + reason: `must have length >= 1, got ${[...raw.city].length}`, + }); + } } } - } - let geo: ShowcaseLocationGeo | undefined = undefined as unknown as - | ShowcaseLocationGeo - | undefined; - if (raw.geo === null) { - violations.push({ path: "geo", reason: "explicit null not allowed" }); - } else if (raw.geo !== undefined) { - try { - geo = new ShowcaseLocationGeoMapper().fromIntermediate(raw.geo); - } catch (error) { - __nexgenDefinitions.collect(violations, "geo", error); + let geo: ShowcaseLocationGeo | undefined = undefined as unknown as + | ShowcaseLocationGeo + | undefined; + if (raw.geo === null) { + violations.push({ path: "geo", reason: "explicit null not allowed" }); + } else if (raw.geo !== undefined) { + try { + geo = showcaseLocationGeoTransferTypeConverter.fromTransferType(raw.geo); + } catch (error) { + __nexgenDefinitions.collect(violations, "geo", error); + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LOCATION_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LOCATION_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseLocation = { city, additionalProperties }; - if (geo !== undefined) { - out.geo = geo; - } - return out; - } - - public toIntermediate(value: ShowcaseLocation): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.city].length < 1) { - violations.push({ - path: "city", - reason: `must have length >= 1, got ${[...value.city].length}`, - }); - } - out.city = value.city; - if (value.geo !== undefined) { - out.geo = new ShowcaseLocationGeoMapper().toIntermediate(value.geo); - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} - -const SHOWCASE_LOCATION_GEO_DECLARED = new Set(["lat", "lon"]); -export class ShowcaseLocationGeoMapper { - public fromIntermediate(raw: unknown): ShowcaseLocationGeo { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let lat: number | undefined = undefined as unknown as number | undefined; - if (raw.lat === null) { - violations.push({ path: "lat", reason: "explicit null not allowed" }); - } else if (raw.lat !== undefined) { - if (typeof raw.lat !== "number") { - violations.push({ path: "lat", reason: "expected number" }); - } else { - lat = raw.lat; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - } - - let lon: number | undefined = undefined as unknown as number | undefined; - if (raw.lon === null) { - violations.push({ path: "lon", reason: "explicit null not allowed" }); - } else if (raw.lon !== undefined) { - if (typeof raw.lon !== "number") { - violations.push({ path: "lon", reason: "expected number" }); - } else { - lon = raw.lon; + const out: ShowcaseLocation = { city, additionalProperties }; + if (geo !== undefined) { + out.geo = geo; } + return out; } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_LOCATION_GEO_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + public toTransferType(value: ShowcaseLocation): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.city].length < 1) { + violations.push({ + path: "city", + reason: `must have length >= 1, got ${[...value.city].length}`, + }); } + out.city = value.city; + if (value.geo !== undefined) { + out.geo = showcaseLocationGeoTransferTypeConverter.toTransferType(value.geo); + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } + })(); - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: ShowcaseLocationGeo = { additionalProperties }; - if (lat !== undefined) { - out.lat = lat; - } - if (lon !== undefined) { - out.lon = lon; - } - return out; - } - - public toIntermediate(value: ShowcaseLocationGeo): unknown { - const out: Record = {}; - if (value.lat !== undefined) { - out.lat = value.lat; - } - if (value.lon !== undefined) { - out.lon = value.lon; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - return out; - } -} - -export class ShowcaseMetadataMapper { - public fromIntermediate(raw: unknown): ShowcaseMetadata { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - const keys = Object.keys(raw); - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - const additionalProperties: Record = {}; - for (const key of keys) { - additionalProperties[key] = raw[key]; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: ShowcaseMetadata): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - const keys = Object.keys(out); - if (keys.length > 3) { - violations.push({ - path: "", - reason: `must have at most 3 properties, got ${keys.length}`, - }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} +const SHOWCASE_LOCATION_GEO_DECLARED = new Set(["lat", "lon"]); -const SHOWCASE_ROWS_ITEM_DECLARED = new Set(["cell"]); +export const showcaseLocationGeoTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseLocationGeo { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let lat: number | undefined = undefined as unknown as number | undefined; + if (raw.lat === null) { + violations.push({ path: "lat", reason: "explicit null not allowed" }); + } else if (raw.lat !== undefined) { + if (typeof raw.lat !== "number") { + violations.push({ path: "lat", reason: "expected number" }); + } else { + lat = raw.lat; + } + } -export class ShowcaseRowsItemMapper { - public fromIntermediate(raw: unknown): ShowcaseRowsItem { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } + let lon: number | undefined = undefined as unknown as number | undefined; + if (raw.lon === null) { + violations.push({ path: "lon", reason: "explicit null not allowed" }); + } else if (raw.lon !== undefined) { + if (typeof raw.lon !== "number") { + violations.push({ path: "lon", reason: "expected number" }); + } else { + lon = raw.lon; + } + } - let cell: string = undefined as unknown as string; - if (raw.cell === undefined || raw.cell === null) { - violations.push({ path: "cell", reason: "required" }); - } else { - if (typeof raw.cell !== "string") { - violations.push({ path: "cell", reason: "expected string" }); - } else { - cell = raw.cell; - if ([...raw.cell].length < 1) { - violations.push({ - path: "cell", - reason: `must have length >= 1, got ${[...raw.cell].length}`, - }); + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_LOCATION_GEO_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SHOWCASE_ROWS_ITEM_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + const out: ShowcaseLocationGeo = { additionalProperties }; + if (lat !== undefined) { + out.lat = lat; + } + if (lon !== undefined) { + out.lon = lon; + } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: ShowcaseLocationGeo): unknown { + const out: Record = {}; + if (value.lat !== undefined) { + out.lat = value.lat; + } + if (value.lon !== undefined) { + out.lon = value.lon; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - const out: ShowcaseRowsItem = { cell, additionalProperties }; - return out; - } + })(); - public toIntermediate(value: ShowcaseRowsItem): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if ([...value.cell].length < 1) { - violations.push({ - path: "cell", - reason: `must have length >= 1, got ${[...value.cell].length}`, - }); - } - out.cell = value.cell; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} +export const showcaseMetadataTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseMetadata { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } -export class ShowcaseSegmentsItemMapper { - public fromIntermediate(raw: unknown): ShowcaseSegmentsItem { - const violations: __nexgenDefinitions.Violation[] = []; - let out: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; - if (typeof raw === "string") { - out = raw as string; - if ([...(out as string)].length < 2) { + const keys = Object.keys(raw); + if (keys.length > 3) { violations.push({ path: "", - reason: `must have length >= 2, got ${[...(out as string)].length}`, + reason: `must have at most 3 properties, got ${keys.length}`, }); } - } else if (typeof raw === "number" && Number.isSafeInteger(raw)) { - out = raw as number; - if ((out as number) < 0) { - violations.push({ path: "", reason: `must be >= 0, got ${out as number}` }); + const additionalProperties: Record = {}; + for (const key of keys) { + additionalProperties[key] = raw[key]; } - } else { - violations.push({ path: "", reason: "expected one of: string, integer" }); - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - return out; - } - public toIntermediate(value: ShowcaseSegmentsItem): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - if (typeof value === "string") { - if ([...(value as string)].length < 2) { + public toTransferType(value: ShowcaseMetadata): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + const keys = Object.keys(out); + if (keys.length > 3) { violations.push({ path: "", - reason: `must have length >= 2, got ${[...(value as string)].length}`, + reason: `must have at most 3 properties, got ${keys.length}`, }); } - } - if (typeof value === "number" && Number.isSafeInteger(value)) { - if ((value as number) < 0) { - violations.push({ path: "", reason: `must be >= 0, got ${value as number}` }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + })(); + +const SHOWCASE_ROWS_ITEM_DECLARED = new Set(["cell"]); + +export const showcaseRowsItemTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseRowsItem { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + let cell: string = undefined as unknown as string; + if (raw.cell === undefined || raw.cell === null) { + violations.push({ path: "cell", reason: "required" }); + } else { + if (typeof raw.cell !== "string") { + violations.push({ path: "cell", reason: "expected string" }); + } else { + cell = raw.cell; + if ([...raw.cell].length < 1) { + violations.push({ + path: "cell", + reason: `must have length >= 1, got ${[...raw.cell].length}`, + }); + } + } + } + + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SHOWCASE_ROWS_ITEM_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } + } + + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: ShowcaseRowsItem = { cell, additionalProperties }; + return out; } - if (typeof value === "string") { - return value; + + public toTransferType(value: ShowcaseRowsItem): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if ([...value.cell].length < 1) { + violations.push({ + path: "cell", + reason: `must have length >= 1, got ${[...value.cell].length}`, + }); + } + out.cell = value.cell; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - if (typeof value === "number" && Number.isSafeInteger(value)) { - return value; + })(); + +export const showcaseSegmentsItemTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): ShowcaseSegmentsItem { + const violations: __nexgenDefinitions.Violation[] = []; + let out: ShowcaseSegmentsItem = undefined as unknown as ShowcaseSegmentsItem; + if (typeof raw === "string") { + out = raw as string; + if ([...(out as string)].length < 2) { + violations.push({ + path: "", + reason: `must have length >= 2, got ${[...(out as string)].length}`, + }); + } + } else if (typeof raw === "number" && Number.isSafeInteger(raw)) { + out = raw as number; + if ((out as number) < 0) { + violations.push({ path: "", reason: `must be >= 0, got ${out as number}` }); + } + } else { + violations.push({ path: "", reason: "expected one of: string, integer" }); + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected one of: string, integer" }, - ]); - } -} -export class GetShowcaseInputMapper { - public fromIntermediate(raw: unknown): GetShowcaseInput { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { + public toTransferType(value: ShowcaseSegmentsItem): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + if (typeof value === "string") { + if ([...(value as string)].length < 2) { + violations.push({ + path: "", + reason: `must have length >= 2, got ${[...(value as string)].length}`, + }); + } + } + if (typeof value === "number" && Number.isSafeInteger(value)) { + if ((value as number) < 0) { + violations.push({ path: "", reason: `must be >= 0, got ${value as number}` }); + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + if (typeof value === "string") { + return value; + } + if (typeof value === "number" && Number.isSafeInteger(value)) { + return value; + } throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, + { path: "", reason: "expected one of: string, integer" }, ]); } + })(); + +export const getShowcaseInputTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): GetShowcaseInput { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - id = raw.id; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - for (const key of Object.keys(raw)) { - if (key !== "id") { - violations.push({ path: key, reason: "unknown field" }); + for (const key of Object.keys(raw)) { + if (key !== "id") { + violations.push({ path: key, reason: "unknown field" }); + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: GetShowcaseInput = { id }; + return out; } - const out: GetShowcaseInput = { id }; - return out; - } - public toIntermediate(value: GetShowcaseInput): unknown { - const out: Record = {}; - out.id = value.id; - return out; - } -} + public toTransferType(value: GetShowcaseInput): unknown { + const out: Record = {}; + out.id = value.id; + return out; + } + })(); const SQUARE_DECLARED = new Set(["kind", "side"]); -export class SquareMapper { - public fromIntermediate(raw: unknown): Square { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const squareTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Square { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "square" = undefined as unknown as "square"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== SQUARE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "square"` }); + let kind: "square" = undefined as unknown as "square"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "square"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== SQUARE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "square"` }); + } else { + kind = raw.kind as "square"; + } } - } - let side: number = undefined as unknown as number; - if (raw.side === undefined || raw.side === null) { - violations.push({ path: "side", reason: "required" }); - } else { - if (typeof raw.side !== "number") { - violations.push({ path: "side", reason: "expected number" }); + let side: number = undefined as unknown as number; + if (raw.side === undefined || raw.side === null) { + violations.push({ path: "side", reason: "required" }); } else { - side = raw.side; + if (typeof raw.side !== "number") { + violations.push({ path: "side", reason: "expected number" }); + } else { + side = raw.side; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!SQUARE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!SQUARE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Square = { kind, side, additionalProperties }; + return out; } - const out: Square = { kind, side, additionalProperties }; - return out; - } - public toIntermediate(value: Square): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "square") { - violations.push({ path: "kind", reason: `must equal "square"` }); - } - out.kind = value.kind; - out.side = value.side; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: Square): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "square") { + violations.push({ path: "kind", reason: `must equal "square"` }); + } + out.kind = value.kind; + out.side = value.side; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const TEXT_NOTE_DECLARED = new Set(["kind", "body"]); -export class TextNoteMapper { - public fromIntermediate(raw: unknown): TextNote { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const textNoteTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): TextNote { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let kind: "text" = undefined as unknown as "text"; - if (raw.kind === undefined || raw.kind === null) { - violations.push({ path: "kind", reason: "required" }); - } else { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else if (raw.kind !== TEXT_NOTE_KIND_CONST) { - violations.push({ path: "kind", reason: `must equal "text"` }); + let kind: "text" = undefined as unknown as "text"; + if (raw.kind === undefined || raw.kind === null) { + violations.push({ path: "kind", reason: "required" }); } else { - kind = raw.kind as "text"; + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else if (raw.kind !== TEXT_NOTE_KIND_CONST) { + violations.push({ path: "kind", reason: `must equal "text"` }); + } else { + kind = raw.kind as "text"; + } } - } - let body: string = undefined as unknown as string; - if (raw.body === undefined || raw.body === null) { - violations.push({ path: "body", reason: "required" }); - } else { - if (typeof raw.body !== "string") { - violations.push({ path: "body", reason: "expected string" }); + let body: string = undefined as unknown as string; + if (raw.body === undefined || raw.body === null) { + violations.push({ path: "body", reason: "required" }); } else { - body = raw.body; - if ([...raw.body].length < 1) { - violations.push({ - path: "body", - reason: `must have length >= 1, got ${[...raw.body].length}`, - }); + if (typeof raw.body !== "string") { + violations.push({ path: "body", reason: "expected string" }); + } else { + body = raw.body; + if ([...raw.body].length < 1) { + violations.push({ + path: "body", + reason: `must have length >= 1, got ${[...raw.body].length}`, + }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!TEXT_NOTE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!TEXT_NOTE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: TextNote = { kind, body, additionalProperties }; + return out; } - const out: TextNote = { kind, body, additionalProperties }; - return out; - } - public toIntermediate(value: TextNote): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - if (value.kind !== "text") { - violations.push({ path: "kind", reason: `must equal "text"` }); - } - out.kind = value.kind; - if ([...value.body].length < 1) { - violations.push({ - path: "body", - reason: `must have length >= 1, got ${[...value.body].length}`, - }); - } - out.body = value.body; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + public toTransferType(value: TextNote): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + if (value.kind !== "text") { + violations.push({ path: "kind", reason: `must equal "text"` }); + } + out.kind = value.kind; + if ([...value.body].length < 1) { + violations.push({ + path: "body", + reason: `must have length >= 1, got ${[...value.body].length}`, + }); + } + out.body = value.body; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); -export class TokensMapper { - public fromIntermediate(raw: unknown): Tokens { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); +export const tokensTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Tokens { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } + + const keys = Object.keys(raw); + const additionalProperties: Record = {}; + for (const key of keys) { + let entry: string | undefined = undefined; + if (typeof raw[key] !== "string") { + violations.push({ path: key, reason: "expected string" }); + } else { + entry = raw[key]; + if ([...raw[key]].length < 2) { + violations.push({ + path: key, + reason: `must have length >= 2, got ${[...raw[key]].length}`, + }); + } + if ([...raw[key]].length > 8) { + violations.push({ + path: key, + reason: `must have length <= 8, got ${[...raw[key]].length}`, + }); + } + if (!PATTERN_C182F89FDB221836.test(raw[key])) { + violations.push({ + path: key, + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(raw[key])}`, + }); + } + } + if (entry !== undefined) { + additionalProperties[key] = entry; + } + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return { additionalProperties }; } - const keys = Object.keys(raw); - const additionalProperties: Record = {}; - for (const key of keys) { - let entry: string | undefined = undefined; - if (typeof raw[key] !== "string") { - violations.push({ path: key, reason: "expected string" }); - } else { - entry = raw[key]; - if ([...raw[key]].length < 2) { + public toTransferType(value: Tokens): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + if ([...entry].length < 2) { violations.push({ path: key, - reason: `must have length >= 2, got ${[...raw[key]].length}`, + reason: `must have length >= 2, got ${[...entry].length}`, }); } - if ([...raw[key]].length > 8) { + if ([...entry].length > 8) { violations.push({ path: key, - reason: `must have length <= 8, got ${[...raw[key]].length}`, + reason: `must have length <= 8, got ${[...entry].length}`, }); } - if (!PATTERN_C182F89FDB221836.test(raw[key])) { + if (!PATTERN_C182F89FDB221836.test(entry)) { violations.push({ path: key, - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(raw[key])}`, + reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(entry)}`, }); } + out[key] = entry; } - if (entry !== undefined) { - additionalProperties[key] = entry; - } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return { additionalProperties }; - } - - public toIntermediate(value: Tokens): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - if ([...entry].length < 2) { - violations.push({ - path: key, - reason: `must have length >= 2, got ${[...entry].length}`, - }); - } - if ([...entry].length > 8) { - violations.push({ - path: key, - reason: `must have length <= 8, got ${[...entry].length}`, - }); - } - if (!PATTERN_C182F89FDB221836.test(entry)) { - violations.push({ - path: key, - reason: `must match pattern ^[a-z]+\$, got ${JSON.stringify(entry)}`, - }); + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); } - out[key] = entry; + return out; } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - return out; - } -} + })(); const WIDGET_DECLARED = new Set(["id", "kind", "name", "size"]); -export class WidgetMapper { - public fromIntermediate(raw: unknown): Widget { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } - - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); - } else { - id = raw.id; +export const widgetTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): Widget { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); } - } - let kind: string | undefined = undefined as unknown as string | undefined; - if (raw.kind === null) { - violations.push({ path: "kind", reason: "explicit null not allowed" }); - } else if (raw.kind !== undefined) { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - kind = raw.kind; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let name: string = undefined as unknown as string; - if (raw.name === undefined || raw.name === null) { - violations.push({ path: "name", reason: "required" }); - } else { - if (typeof raw.name !== "string") { - violations.push({ path: "name", reason: "expected string" }); - } else { - name = raw.name; + let kind: string | undefined = undefined as unknown as string | undefined; + if (raw.kind === null) { + violations.push({ path: "kind", reason: "explicit null not allowed" }); + } else if (raw.kind !== undefined) { + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else { + kind = raw.kind; + } } - } - let size: number | undefined = undefined as unknown as number | undefined; - if (raw.size === null) { - violations.push({ path: "size", reason: "explicit null not allowed" }); - } else if (raw.size !== undefined) { - if (typeof raw.size !== "number" || !Number.isSafeInteger(raw.size)) { - violations.push({ path: "size", reason: "expected integer" }); + let name: string = undefined as unknown as string; + if (raw.name === undefined || raw.name === null) { + violations.push({ path: "name", reason: "required" }); } else { - size = raw.size; - if (raw.size < 10) { - violations.push({ path: "size", reason: `must be >= 10, got ${raw.size}` }); + if (typeof raw.name !== "string") { + violations.push({ path: "name", reason: "expected string" }); + } else { + name = raw.name; } - if (raw.size > 20) { - violations.push({ path: "size", reason: `must be <= 20, got ${raw.size}` }); + } + + let size: number | undefined = undefined as unknown as number | undefined; + if (raw.size === null) { + violations.push({ path: "size", reason: "explicit null not allowed" }); + } else if (raw.size !== undefined) { + if (typeof raw.size !== "number" || !Number.isSafeInteger(raw.size)) { + violations.push({ path: "size", reason: "expected integer" }); + } else { + size = raw.size; + if (raw.size < 10) { + violations.push({ path: "size", reason: `must be >= 10, got ${raw.size}` }); + } + if (raw.size > 20) { + violations.push({ path: "size", reason: `must be <= 20, got ${raw.size}` }); + } } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!WIDGET_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!WIDGET_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: Widget = { id, name, additionalProperties }; - if (kind !== undefined) { - out.kind = kind; - } - if (size !== undefined) { - out.size = size; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: Widget = { id, name, additionalProperties }; + if (kind !== undefined) { + out.kind = kind; + } + if (size !== undefined) { + out.size = size; + } + return out; } - return out; - } - public toIntermediate(value: Widget): unknown { - const violations: __nexgenDefinitions.Violation[] = []; - const out: Record = {}; - out.id = value.id; - if (value.kind !== undefined) { - out.kind = value.kind; - } - out.name = value.name; - if (value.size !== undefined) { - if (value.size < 10) { - violations.push({ path: "size", reason: `must be >= 10, got ${value.size}` }); + public toTransferType(value: Widget): unknown { + const violations: __nexgenDefinitions.Violation[] = []; + const out: Record = {}; + out.id = value.id; + if (value.kind !== undefined) { + out.kind = value.kind; } - if (value.size > 20) { - violations.push({ path: "size", reason: `must be <= 20, got ${value.size}` }); + out.name = value.name; + if (value.size !== undefined) { + if (value.size < 10) { + violations.push({ path: "size", reason: `must be >= 10, got ${value.size}` }); + } + if (value.size > 20) { + violations.push({ path: "size", reason: `must be <= 20, got ${value.size}` }); + } + out.size = value.size; } - out.size = value.size; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + return out; } - return out; - } -} + })(); const WIDGET_BASE_DECLARED = new Set(["id", "kind"]); -export class WidgetBaseMapper { - public fromIntermediate(raw: unknown): WidgetBase { - const violations: __nexgenDefinitions.Violation[] = []; - if (!__nexgenDefinitions.isPlainObject(raw)) { - throw new __nexgenDefinitions.ValidationError([ - { path: "", reason: "expected object" }, - ]); - } +export const widgetBaseTransferTypeConverter = + new (class implements TransferTypeConverter { + public fromTransferType(raw: unknown): WidgetBase { + const violations: __nexgenDefinitions.Violation[] = []; + if (!__nexgenDefinitions.isPlainObject(raw)) { + throw new __nexgenDefinitions.ValidationError([ + { path: "", reason: "expected object" }, + ]); + } - let id: string = undefined as unknown as string; - if (raw.id === undefined || raw.id === null) { - violations.push({ path: "id", reason: "required" }); - } else { - if (typeof raw.id !== "string") { - violations.push({ path: "id", reason: "expected string" }); + let id: string = undefined as unknown as string; + if (raw.id === undefined || raw.id === null) { + violations.push({ path: "id", reason: "required" }); } else { - id = raw.id; + if (typeof raw.id !== "string") { + violations.push({ path: "id", reason: "expected string" }); + } else { + id = raw.id; + } } - } - let kind: string | undefined = undefined as unknown as string | undefined; - if (raw.kind === null) { - violations.push({ path: "kind", reason: "explicit null not allowed" }); - } else if (raw.kind !== undefined) { - if (typeof raw.kind !== "string") { - violations.push({ path: "kind", reason: "expected string" }); - } else { - kind = raw.kind; + let kind: string | undefined = undefined as unknown as string | undefined; + if (raw.kind === null) { + violations.push({ path: "kind", reason: "explicit null not allowed" }); + } else if (raw.kind !== undefined) { + if (typeof raw.kind !== "string") { + violations.push({ path: "kind", reason: "expected string" }); + } else { + kind = raw.kind; + } } - } - const additionalProperties: Record = {}; - for (const key of Object.keys(raw)) { - if (!WIDGET_BASE_DECLARED.has(key)) { - additionalProperties[key] = raw[key]; + const additionalProperties: Record = {}; + for (const key of Object.keys(raw)) { + if (!WIDGET_BASE_DECLARED.has(key)) { + additionalProperties[key] = raw[key]; + } } - } - if (violations.length) { - throw new __nexgenDefinitions.ValidationError(violations); - } - const out: WidgetBase = { id, additionalProperties }; - if (kind !== undefined) { - out.kind = kind; + if (violations.length) { + throw new __nexgenDefinitions.ValidationError(violations); + } + const out: WidgetBase = { id, additionalProperties }; + if (kind !== undefined) { + out.kind = kind; + } + return out; } - return out; - } - public toIntermediate(value: WidgetBase): unknown { - const out: Record = {}; - out.id = value.id; - if (value.kind !== undefined) { - out.kind = value.kind; - } - for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { - out[key] = entry; + public toTransferType(value: WidgetBase): unknown { + const out: Record = {}; + out.id = value.id; + if (value.kind !== undefined) { + out.kind = value.kind; + } + for (const [key, entry] of Object.entries(value.additionalProperties ?? {})) { + out[key] = entry; + } + return out; } - return out; - } -} + })(); diff --git a/samples/typescript/tests/json-schema-showcase.test.ts b/samples/typescript/tests/json-schema-showcase.test.ts index 29d814dd..6bdf66f1 100644 --- a/samples/typescript/tests/json-schema-showcase.test.ts +++ b/samples/typescript/tests/json-schema-showcase.test.ts @@ -540,7 +540,9 @@ describe("json-schema showcase generated definitions", () => { // The string branch's own `minLength` and the integer branch's own // `minimum` — each enforced only for the branch the token selects. - expect(converter.fromTransferType({ ...base, idOrName: "abc" }).idOrName).toBe("abc"); + expect(converter.fromTransferType({ ...base, idOrName: "abc" }).idOrName).toBe( + "abc", + ); expect(converter.fromTransferType({ ...base, idOrName: 1 }).idOrName).toBe(1); expect(() => converter.fromTransferType({ ...base, idOrName: "ab" })).toThrow( /idOrName: must have length >= 3, got 2/, @@ -579,10 +581,9 @@ describe("json-schema showcase generated definitions", () => { // A named element union validates through its own converter, in both // directions, with the element's index on the violation path. - expect(converter.fromTransferType({ ...base, segments: ["ab", 0] }).segments).toEqual([ - "ab", - 0, - ]); + expect( + converter.fromTransferType({ ...base, segments: ["ab", 0] }).segments, + ).toEqual(["ab", 0]); expect(() => converter.fromTransferType({ ...base, segments: ["a"] })).toThrow( /segments\[0\]: must have length >= 2, got 1/, ); @@ -836,7 +837,10 @@ describe("json-schema showcase generated definitions", () => { // names `ShowcaseSegmentsItem`, and a map member at an inline union named // `ChoicesValue`. Each element runs its union's own converter, so a bad value // is reported at its index / key. - const value = expectRoundTrip("showcase-element-unions.json", showcaseTransferTypeConverter); + const value = expectRoundTrip( + "showcase-element-unions.json", + showcaseTransferTypeConverter, + ); expect(value.shapes).toEqual([ { kind: "circle", radius: 2.5, additionalProperties: {} }, @@ -878,7 +882,10 @@ describe("json-schema showcase generated definitions", () => { }), ).toThrow(/triangle/); expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, segments: ["ok", 1.5] }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + segments: ["ok", 1.5], + }), ).toThrow(/segments\[1\]/); expect(() => showcaseTransferTypeConverter.fromTransferType({ @@ -1014,7 +1021,10 @@ describe("json-schema showcase generated definitions", () => { // map and its member (`ledger`), and a free-form bag (`metadata`). The same // fixture covers a typed map's member constraints (`quotas`, `tokens`, // `nicknames`) and a nested array (`grid`). - const value = expectRoundTrip("showcase-inline-shapes.json", showcaseTransferTypeConverter); + const value = expectRoundTrip( + "showcase-inline-shapes.json", + showcaseTransferTypeConverter, + ); expect(value.grid).toEqual([[1, 2], [3]]); expect(value.location).toEqual({ @@ -1053,24 +1063,39 @@ describe("json-schema showcase generated definitions", () => { // A hoisted shape validates like any other model, at the nested path. expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, location: { city: "" } }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + location: { city: "" }, + }), ).toThrow(/location\.city/); expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, rows: [{ cell: "ok" }, {}] }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + rows: [{ cell: "ok" }, {}], + }), ).toThrow(/rows\[1\]\.cell/); // A nested array reports the failing element at its own two-dimensional index. expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, grid: [[1], [2, 1.5]] }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + grid: [[1], [2, 1.5]], + }), ).toThrow(/grid\[1\]\[1\]/); // A typed map's member constraints are enforced, keyed by the member. expect(() => showcaseTransferTypeConverter.fromTransferType({ ...base, quotas: { cpu: 7 } }), ).toThrow(/cpu/); expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, tokens: { primary: "AB" } }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + tokens: { primary: "AB" }, + }), ).toThrow(/primary/); expect(() => - showcaseTransferTypeConverter.fromTransferType({ ...base, nicknames: { tiny: "a" } }), + showcaseTransferTypeConverter.fromTransferType({ + ...base, + nicknames: { tiny: "a" }, + }), ).toThrow(/tiny/); // The free-form bag's member-count bound rides with the hoisted type. expect(() => @@ -1085,10 +1110,14 @@ describe("json-schema showcase generated definitions", () => { quotasTransferTypeConverter.toTransferType({ additionalProperties: { cpu: 7 } }), ).toThrow(/cpu/); expect(() => - tokensTransferTypeConverter.toTransferType({ additionalProperties: { primary: "AB" } }), + tokensTransferTypeConverter.toTransferType({ + additionalProperties: { primary: "AB" }, + }), ).toThrow(/primary/); expect(() => - nicknamesTransferTypeConverter.toTransferType({ additionalProperties: { tiny: "a" } }), + nicknamesTransferTypeConverter.toTransferType({ + additionalProperties: { tiny: "a" }, + }), ).toThrow(/tiny/); }); }); diff --git a/specs/json-schema/features/ref.md b/specs/json-schema/features/ref.md index cf89f005..178b7624 100644 --- a/specs/json-schema/features/ref.md +++ b/specs/json-schema/features/ref.md @@ -148,6 +148,11 @@ derived as: rules ([[const]]/[[enum]]/[[properties]]; nest where the language allows, P15 backstop). +A type's emitted name is resolved once for the **whole input closure**, so +a reference from another input file names exactly the identifier the +declaring file's own module emits — including its `x--name` +override, which the referencing file does not restate. + **Collision.** All type names occupy **one package-wide namespace** ([[generated-file-layout]]). A collision → **load reject, no mangling**; the escape hatch is `x--name` / root `title` (**P15**, scope diff --git a/src/generator/json_schema/go.rs b/src/generator/json_schema/go.rs index 8a02ba48..95b9cca6 100644 --- a/src/generator/json_schema/go.rs +++ b/src/generator/json_schema/go.rs @@ -13,6 +13,7 @@ use crate::generator::go::{ go_string_literal, }; use crate::generator::json_schema::build_json_name_manifest; +use crate::generator::json_schema::register_cross_module_ref_names; use crate::language::Language; use crate::parser::NameManifest; use crate::planning::{PlannedFamily, PlannedJsonType, PlannedSpec}; @@ -756,6 +757,11 @@ impl ExternalModelBackend for ModelBackend { .collect(); self.local_json_models.clear(); self.model_names.clear(); + // Go flattens the whole tree into one package, so a model another input + // file declares is still an unqualified local name here -- but only the + // tree-wide resolution knows the identifier that file's `x-go-name` + // moved it to. + register_cross_module_ref_names(api_plan, &mut self.model_names); for model in &self.json_models { // Go flattens every input file in a generate closure into one flat @@ -772,20 +778,6 @@ impl ExternalModelBackend for ModelBackend { model.model_name.clone(), ); } - if self.include_service_imports && !api_plan.services.is_empty() { - for (module_path, names) in &api_plan.data.module_imports { - for name in names { - self.model_names.insert( - format!("{}#{name}", module_path.as_module_key()), - name.clone(), - ); - self.model_names.insert( - format!("{}#/$defs/{name}", module_path.as_module_key()), - name.clone(), - ); - } - } - } Ok(()) } diff --git a/src/generator/json_schema/mod.rs b/src/generator/json_schema/mod.rs index 37d82da5..4385eefe 100644 --- a/src/generator/json_schema/mod.rs +++ b/src/generator/json_schema/mod.rs @@ -5,3 +5,26 @@ pub(crate) mod python; pub(crate) mod typescript; pub(in crate::generator) use crate::planning::build_json_name_manifest; + +use std::collections::BTreeMap; + +use crate::planning::PlannedSpec; + +/// Registers the emitted identifier of every model declared in *another* module +/// in a backend's `$ref` registry, under both key forms a backend looks a +/// reference up by: the bare model full name and the resolved `$ref` text +/// `#/$defs/`. +/// +/// A leaf's own name manifest covers only the models it declares, so without +/// this a cross-module `$ref` would be recased from the reference text and drop +/// the `x--name` override the other input file declares. The identifiers +/// themselves are resolved once, tree-wide, by `EmittedNameResolutionPass`. +pub(in crate::generator) fn register_cross_module_ref_names( + api_plan: &PlannedSpec, + ref_names: &mut BTreeMap, +) { + for (full_name, model_name) in &api_plan.data.cross_module_model_names { + ref_names.insert(full_name.clone(), model_name.clone()); + ref_names.insert(format!("#/$defs/{full_name}"), model_name.clone()); + } +} diff --git a/src/generator/json_schema/python.rs b/src/generator/json_schema/python.rs index 475432d3..c80c8941 100644 --- a/src/generator/json_schema/python.rs +++ b/src/generator/json_schema/python.rs @@ -10,6 +10,7 @@ use serde_json::Value; use crate::error::{Error, Result}; use crate::generator::ExternalModelBackend; use crate::generator::json_schema::build_json_name_manifest; +use crate::generator::json_schema::register_cross_module_ref_names; use crate::generator::python::{ PythonImports, PythonModelHoists, RenderedModelFragments, WireValueConversion, module_common_prefix_len, python_field_name, python_string_literal, @@ -208,6 +209,7 @@ impl ExternalModelBackend for ModelBackend { self.json_models = std::mem::take(&mut json_models); self.hoisted_json_models = Vec::new(); self.ref_names.clear(); + register_cross_module_ref_names(api_plan, &mut self.ref_names); for model in &self.json_models { // A resolved `$ref` is `#/$defs/`; register that form (plus the // bare `full_name`) so `reference_model_name` resolves through the manifest diff --git a/src/generator/json_schema/typescript.rs b/src/generator/json_schema/typescript.rs index 677b24a3..391dbb1f 100644 --- a/src/generator/json_schema/typescript.rs +++ b/src/generator/json_schema/typescript.rs @@ -10,6 +10,7 @@ use std::cell::{Cell, RefCell}; use crate::error::{Error, Result}; use crate::generator::json_schema::build_json_name_manifest; +use crate::generator::json_schema::register_cross_module_ref_names; use crate::generator::typescript::{ RenderedExternalModelFragments, WireValueConversion, typescript_generated_field_name, }; @@ -1083,6 +1084,7 @@ impl ExternalModelBackend for ModelBackend { }) .collect(); self.ref_names.clear(); + register_cross_module_ref_names(api_plan, &mut self.ref_names); for model in &self.json_models { // A resolved `$ref` is `#/$defs/`; register that form (plus the // bare `full_name`) so `reference_model_name` resolves through the manifest diff --git a/src/lib.rs b/src/lib.rs index 40f6b7f0..a30029b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,8 +147,8 @@ pub(crate) fn compile_tree_to_files( let planned_tree = planning::ReachabilityPass::new().apply(planned_tree)?; // planned IR -> emitted JSON names -> render target-language files - let generator_ready_tree = - planning::EmittedNameResolutionPass::new(language).apply(planned_tree)?; + let name_resolution = planning::EmittedNameResolutionPass::new(language, &planned_tree)?; + let generator_ready_tree = name_resolution.apply(planned_tree)?; generator::generate_files_from_planned_tree( language, &generator_ready_tree, diff --git a/src/planning/emitted_names.rs b/src/planning/emitted_names.rs index d181b23c..b5fe9791 100644 --- a/src/planning/emitted_names.rs +++ b/src/planning/emitted_names.rs @@ -3,22 +3,97 @@ //! It runs after reachability so its manifest covers exactly the declarations a //! backend will render. The pass owns this planning decision; generators only //! consult the already-resolved planned graph. +//! +//! The manifest is built over the **whole tree**, not one leaf. One input file +//! is one module (P14), so a `$ref` that crosses files names a model whose +//! `x--name` override is declared in the *other* file; a leaf-local +//! manifest cannot see that override, and the consuming module would emit the +//! pre-override identifier — a reference and an import that name nothing. +//! Collision scoping is unchanged by the widening: [`build_name_manifest`] +//! groups the models it is handed by module key, so a foreign model never joins +//! a local module's namespace (P15). + +use std::collections::{BTreeMap, BTreeSet}; use crate::error::{Error, Result}; use crate::language::Language; use crate::parser::{ManifestModel, ManifestService, NameManifest, build_name_manifest}; -use crate::spec::{ApiSpecLeaf, CompilerPass}; -use crate::spec::{ExternalTypeSpec, ModulePath, TypeDeclSpec}; +use crate::spec::{ApiSpecLeaf, ApiSpecNode, ApiSpecTransform, ApiSpecTree, CompilerPass}; +use crate::spec::{ExternalTypeSpec, LanguageStringSpec, ModulePath}; -use super::{PlannedFamily, PlannedSpec}; +use super::{ + PlannedAliasType, PlannedEnumType, PlannedFamily, PlannedFieldData, PlannedFlagsType, + PlannedJsonType, PlannedOperationData, PlannedProtoType, PlannedRecordData, PlannedRecordType, + PlannedResource, PlannedResourceType, PlannedSpec, PlannedSpecData, PlannedVariantType, + SupportSpec, +}; pub(crate) struct EmittedNameResolutionPass { - language: Language, + /// Resolved identifiers for every JSON model in the tree, keyed by full name. + manifest: NameManifest, + /// Every JSON model in the tree, carrying what the per-leaf rewrites need + /// beyond the manifest itself. + tree_models: Vec, +} + +/// One tree-wide JSON model, as the per-leaf rewrites see it. +struct TreeModel { + module_key: String, + full_name: String, + /// The planned (pre-override) identifier. This is the language-agnostic name + /// `module_imports` carries, so it is the key a stale import resolves under. + planned_name: String, } impl EmittedNameResolutionPass { - pub(crate) fn new(language: Language) -> Self { - Self { language } + pub(crate) fn new(language: Language, tree: &ApiSpecTree) -> Result { + let mut models = Vec::new(); + let mut services = Vec::new(); + collect_tree_manifest_inputs(language, &tree.root, &mut models, &mut services); + let manifest = build_name_manifest(language, &models, &services)?; + let tree_models = models + .iter() + .map(|model| TreeModel { + module_key: model.module_key.clone(), + full_name: model.full_name.clone(), + planned_name: model.model_name.clone(), + }) + .collect(); + Ok(Self { + manifest, + tree_models, + }) + } + + /// The resolved identifier for a model, keyed by full name. + fn model_name(&self, full_name: &str) -> Option<&str> { + self.manifest.type_name(full_name) + } + + /// The resolved identifiers of every model `module_key` does *not* declare, + /// keyed by full name. Generators seed their `$ref` registries with these so + /// a cross-module `$ref` resolves through the manifest instead of being + /// recased from the reference text (which drops the foreign override). + fn cross_module_model_names(&self, module_key: &str) -> BTreeMap { + self.tree_models + .iter() + .filter(|model| model.module_key != module_key) + .filter_map(|model| { + let resolved = self.model_name(&model.full_name)?; + Some((model.full_name.clone(), resolved.to_string())) + }) + .collect() + } + + /// The resolved identifier of the model `module_path` declares under the + /// planned name `planned_name`. `module_imports` records planned names, so + /// this is how a stale import name becomes the emitted one. + fn imported_model_name(&self, module_path: &ModulePath, planned_name: &str) -> Option<&str> { + let module_key = module_path.as_module_key(); + self.tree_models + .iter() + .find(|model| model.module_key == module_key && model.planned_name == planned_name) + .and_then(|model| self.model_name(&model.full_name)) } } @@ -27,62 +102,173 @@ impl CompilerPass for EmittedNameResolutionPass { fn transform_leaf( &mut self, - mut leaf: ApiSpecLeaf, + leaf: ApiSpecLeaf, ) -> Result> { - resolve_emitted_json_names(&mut leaf.spec, self.language)?; - Ok(leaf) + let module_key = leaf.spec.module_path.as_module_key(); + let spec = leaf.spec.map_names(EmittedNameMapper { + pass: self, + module_key, + }); + Ok(ApiSpecLeaf { spec, ..leaf }) + } +} + +/// Rewrites every emitted JSON model identifier in one leaf from the tree-wide +/// manifest. Resolving the leaf's declarations is not enough: every *reference* +/// to a model — an operation input/output, a record field type — carries its own +/// [`PlannedJsonType`] clone with the pre-override name, and a reference to +/// another module's model is the only place a foreign override can be applied. +/// Routing the rewrite through [`ApiSpec::map_names`] reaches all of them. +struct EmittedNameMapper<'a> { + pass: &'a EmittedNameResolutionPass, + /// The module key of the leaf being rewritten. + module_key: String, +} + +impl ApiSpecTransform for EmittedNameMapper<'_> { + fn map_spec_data(&mut self, data: PlannedSpecData) -> PlannedSpecData { + PlannedSpecData { + module_imports: data + .module_imports + .into_iter() + .map(|(module_path, names)| { + let names: BTreeSet = names + .into_iter() + .map(|name| { + self.pass + .imported_model_name(&module_path, &name) + .unwrap_or(name.as_str()) + .to_string() + }) + .collect(); + (module_path, names) + }) + .collect(), + cross_module_model_names: self.pass.cross_module_model_names(&self.module_key), + } + } + + fn map_json(&mut self, mut value: PlannedJsonType) -> PlannedJsonType { + if let Some(resolved) = self.pass.model_name(&value.full_name) { + value.model_name = resolved.to_string(); + } + value + } + + fn map_record(&mut self, value: PlannedRecordType) -> PlannedRecordType { + value + } + fn map_enum(&mut self, value: PlannedEnumType) -> PlannedEnumType { + value + } + fn map_flags(&mut self, value: PlannedFlagsType) -> PlannedFlagsType { + value + } + fn map_variant(&mut self, value: PlannedVariantType) -> PlannedVariantType { + value + } + fn map_resource(&mut self, value: PlannedResourceType) -> PlannedResourceType { + value + } + fn map_proto(&mut self, value: PlannedProtoType) -> PlannedProtoType { + value + } + fn map_alias(&mut self, value: PlannedAliasType) -> PlannedAliasType { + value + } + fn map_service_data(&mut self, _: &str, _: ()) {} + fn map_record_data(&mut self, _: &str, value: PlannedRecordData) -> PlannedRecordData { + value + } + /// Identity: a resource's field and method types are WIT-authored (a JSON + /// Schema input declares no resources), so they name no JSON model. + fn map_resource_data(&mut self, _: &str, value: PlannedResource) -> PlannedResource { + value + } + fn map_operation_data(&mut self, _: &str, value: PlannedOperationData) -> PlannedOperationData { + value + } + fn map_field_data(&mut self, _: &str, _: &str, value: PlannedFieldData) -> PlannedFieldData { + value + } + fn map_text(&mut self, value: LanguageStringSpec) -> LanguageStringSpec { + value + } + fn map_support(&mut self, value: SupportSpec) -> SupportSpec { + value } } -/// Builds the manifest over the post-reachability API surface. +/// Builds the manifest over one leaf's post-reachability API surface. The +/// generators use this for the models they render; cross-module identifiers +/// reach them through [`PlannedSpecData::cross_module_model_names`], which this +/// pass resolves tree-wide. pub(crate) fn build_json_name_manifest( language: Language, api_plan: &PlannedSpec, ) -> Result { - let mut models = Vec::new(); - for (_full_name, binding) in api_plan.external_types() { - let ExternalTypeSpec::Json(json) = &binding.external_type else { - continue; - }; - let module_key = json - .module_path - .as_ref() - .map(ModulePath::as_module_key) - .unwrap_or_default(); - let local_name = json - .full_name - .rsplit(['#', '/']) - .next() - .unwrap_or(&json.full_name) - .to_string(); - models.push(ManifestModel { - full_name: json.full_name.clone(), - local_name, - model_name: json.model_name.clone(), - module_key, - schema: json.schema.clone(), - }); + let models = manifest_models(api_plan); + let services = manifest_services(language, api_plan); + build_name_manifest(language, &models, &services) +} + +fn collect_tree_manifest_inputs( + language: Language, + node: &ApiSpecNode, + models: &mut Vec, + services: &mut Vec, +) { + match node { + ApiSpecNode::Leaf(leaf) => { + models.extend(manifest_models(&leaf.spec)); + services.extend(manifest_services(language, &leaf.spec)); + } + ApiSpecNode::Branch(branch) => { + for child in branch.children.values() { + collect_tree_manifest_inputs(language, child, models, services); + } + } + } +} + +fn manifest_models(api_plan: &PlannedSpec) -> Vec { + api_plan + .external_types() + .filter_map(|(_full_name, binding)| match &binding.external_type { + ExternalTypeSpec::Json(json) => Some(manifest_model(json)), + _ => None, + }) + .collect() +} + +fn manifest_model(json: &PlannedJsonType) -> ManifestModel { + let module_key = json + .module_path + .as_ref() + .map(ModulePath::as_module_key) + .unwrap_or_default(); + let local_name = json + .full_name + .rsplit(['#', '/']) + .next() + .unwrap_or(&json.full_name) + .to_string(); + ManifestModel { + full_name: json.full_name.clone(), + local_name, + model_name: json.model_name.clone(), + module_key, + schema: json.schema.clone(), } - let services = api_plan +} + +fn manifest_services(language: Language, api_plan: &PlannedSpec) -> Vec { + api_plan .services .iter() .map(|service| ManifestService { name: service.name.clone(), code_name: service.code_name.for_language(language).map(str::to_string), }) - .collect::>(); - build_name_manifest(language, &models, &services) -} - -fn resolve_emitted_json_names(spec: &mut PlannedSpec, language: Language) -> Result<()> { - let manifest = build_json_name_manifest(language, spec)?; - for entry in spec.types.values_mut() { - if let TypeDeclSpec::External(binding) = &mut entry.declaration - && let ExternalTypeSpec::Json(json) = &mut binding.external_type - && let Some(resolved) = manifest.type_name(&json.full_name) - { - json.model_name = resolved.to_string(); - } - } - Ok(()) + .collect() } diff --git a/src/planning/mod.rs b/src/planning/mod.rs index 5d53d55b..5fc0e3fb 100644 --- a/src/planning/mod.rs +++ b/src/planning/mod.rs @@ -269,6 +269,12 @@ pub(crate) struct PlannedJsonType { #[derive(Debug, Clone, Default, PartialEq)] pub(crate) struct PlannedSpecData { pub(crate) module_imports: BTreeMap>, + /// The emitted identifier of every JSON model declared in *another* module, + /// keyed by model full name. A leaf cannot derive these: the `x--name` + /// override that moves an identifier is declared in the other input file, so + /// `EmittedNameResolutionPass` resolves them from the tree-wide manifest and + /// records them here for the generators' `$ref` registries. + pub(crate) cross_module_model_names: BTreeMap, } impl AsRef for PlannedJsonType { diff --git a/src/planning/type_planning.rs b/src/planning/type_planning.rs index fe20691d..4894db05 100644 --- a/src/planning/type_planning.rs +++ b/src/planning/type_planning.rs @@ -1230,6 +1230,9 @@ impl CompilerPass for TypePlanningPass<'_ .get(&leaf.module_path) .cloned() .unwrap_or_default(), + // Resolved later, by `EmittedNameResolutionPass`: naming a model in + // another module needs the tree-wide name manifest. + cross_module_model_names: BTreeMap::new(), }; let mut planner = TypePlanningContext::new( leaf.spec.clone(), diff --git a/tests/generate_go.rs b/tests/generate_go.rs index 6401f7a1..fc5f3519 100644 --- a/tests/generate_go.rs +++ b/tests/generate_go.rs @@ -1844,3 +1844,103 @@ properties: assert!(!rendered.contains("\t\"time\"\n")); fs::remove_dir_all(temp_dir).unwrap(); } + +/// The entry file of a two-file closure. `get`'s output is the model the *other* +/// file declares, and `FindOutput.page` `$ref`s it from a property, so both +/// cross-module reference shapes are covered. +const CROSS_MODULE_ENTRY_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Pages: + fqn: example.pages.v1.Pages + operations: + get: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "content/page.json" } + find: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "#/$defs/FindOutput" } +$defs: + GetInput: + type: object + additionalProperties: false + properties: + id: { type: string } + FindOutput: + type: object + additionalProperties: false + properties: + page: { $ref: "content/page.json" } +"##; + +/// The referenced file. Its model carries the name override the *consuming* +/// module has to resolve through. +const CROSS_MODULE_PAGE_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +x-go-name: RenamedPage +properties: + title: { type: string } +"##; + +/// Writes the two-file cross-module closure into `dir` and returns the input +/// directory to generate from. +fn write_cross_module_closure(dir: &Path) -> PathBuf { + let input_dir = dir.join("input"); + fs::create_dir_all(input_dir.join("content")).unwrap(); + fs::write( + input_dir.join("kb.nexusrpc.yaml"), + CROSS_MODULE_ENTRY_SCHEMA, + ) + .unwrap(); + fs::write( + input_dir.join("content/page.json"), + CROSS_MODULE_PAGE_SCHEMA, + ) + .unwrap(); + input_dir +} + +/// An `x-go-name` override on a model in *another* input file moves every +/// reference the consuming module emits. Go collapses the whole closure into one +/// flat package, so there is no import to fix — but the operation generic and the +/// cross-module `$ref` field still name the type, and the override is declared in +/// the referenced file, so only the tree-wide name manifest can resolve it +/// (P14/P15). +#[test] +fn go_json_cross_module_go_name_override_moves_every_reference() { + let temp_dir = unique_output_path("go-json-cross-module-override"); + let input_dir = write_cross_module_closure(&temp_dir); + let output_path = temp_dir.join("output"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Go, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let declaring = fs::read_to_string(output_path.join("content_page.go")).unwrap(); + assert!(declaring.contains("type RenamedPage struct {")); + + let consuming = fs::read_to_string(output_path.join("kb.go")).unwrap(); + for expected in [ + "Get nexus.OperationReference[GetInput, RenamedPage]", + "Get: nexus.NewOperationReference[GetInput, RenamedPage](\"Get\")", + "Page *RenamedPage `json:\"page,omitempty\"`", + "var tmp RenamedPage", + ] { + assert!(consuming.contains(expected), "{expected}\n{consuming}"); + } + // Nothing names the pre-override identifier. + for stale in ["[GetInput, Page]", "*Page ", "var tmp Page\n"] { + assert!(!consuming.contains(stale), "{stale}\n{consuming}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_java.rs b/tests/generate_java.rs index 1a7176db..05dfdd1a 100644 --- a/tests/generate_java.rs +++ b/tests/generate_java.rs @@ -369,3 +369,121 @@ fn java_json_decodes_element_position_unions() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// The entry file of a two-file closure. `get`'s output is the model the *other* +/// file declares, and `FindOutput.page` `$ref`s it from a property, so both +/// cross-module reference shapes are covered. +const CROSS_MODULE_ENTRY_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Pages: + fqn: example.pages.v1.Pages + operations: + get: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "content/page.json" } + find: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "#/$defs/FindOutput" } +$defs: + GetInput: + type: object + additionalProperties: false + properties: + id: { type: string } + FindOutput: + type: object + additionalProperties: false + properties: + page: { $ref: "content/page.json" } +"##; + +/// The referenced file. Its model carries the name override the *consuming* +/// module has to resolve through. +const CROSS_MODULE_PAGE_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +x-java-name: RenamedPage +properties: + title: { type: string } +"##; + +/// Writes the two-file cross-module closure into `dir` and returns the input +/// directory to generate from. +fn write_cross_module_closure(dir: &Path) -> PathBuf { + let input_dir = dir.join("input"); + fs::create_dir_all(input_dir.join("content")).unwrap(); + fs::write( + input_dir.join("kb.nexusrpc.yaml"), + CROSS_MODULE_ENTRY_SCHEMA, + ) + .unwrap(); + fs::write( + input_dir.join("content/page.json"), + CROSS_MODULE_PAGE_SCHEMA, + ) + .unwrap(); + input_dir +} + +/// An `x-java-name` override on a model in *another* input file moves every +/// reference the consuming package emits: the operation's return type, the +/// cross-package import, and the field/getter of a cross-module `$ref` property. +/// The override is declared in the referenced file, so only the tree-wide name +/// manifest can resolve it (P14/P15). +#[test] +fn java_json_cross_module_java_name_override_moves_every_reference() { + let temp_dir = unique_output_path("java-json-cross-module-override"); + let input_dir = write_cross_module_closure(&temp_dir); + // The output directory's base name must equal the package's last segment. + let output_path = temp_dir.join("pages"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Java, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: Some("example.pages".to_string()), + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let rendered = read_java_files(&output_path); + let declaring = &rendered[&PathBuf::from("content/page/RenamedPage.java")]; + assert!( + declaring.contains("public final class RenamedPage {"), + "{declaring}" + ); + + let service = &rendered[&PathBuf::from("kb/Pages.java")]; + for expected in [ + "import example.pages.content.page.RenamedPage;", + "RenamedPage get(GetInput input);", + ] { + assert!(service.contains(expected), "{expected}\n{service}"); + } + + let consuming = &rendered[&PathBuf::from("kb/FindOutput.java")]; + for expected in [ + "import example.pages.content.page.RenamedPage;", + "private final @Nullable RenamedPage page;", + "public @Nullable RenamedPage getPage() {", + "context.readTreeAsValue(field, RenamedPage.class);", + ] { + assert!(consuming.contains(expected), "{expected}\n{consuming}"); + } + // Nothing names the pre-override identifier. + for stale in [ + ".page.Page;", + "@Nullable Page ", + "(field, Page.class)", + " Page get(", + ] { + assert!(!service.contains(stale), "{stale}\n{service}"); + assert!(!consuming.contains(stale), "{stale}\n{consuming}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_python.rs b/tests/generate_python.rs index 4c206ec8..05c6c2eb 100644 --- a/tests/generate_python.rs +++ b/tests/generate_python.rs @@ -970,3 +970,109 @@ fn python_json_annotates_element_position_unions() { assert!(exports.contains("BagSegmentsItem")); fs::remove_dir_all(temp_dir).unwrap(); } + +/// The entry file of a two-file closure. `get`'s output is the model the *other* +/// file declares, and `FindOutput.page` `$ref`s it from a property, so both +/// cross-module reference shapes are covered. +const CROSS_MODULE_ENTRY_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Pages: + fqn: example.pages.v1.Pages + operations: + get: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "content/page.json" } + find: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "#/$defs/FindOutput" } +$defs: + GetInput: + type: object + additionalProperties: false + properties: + id: { type: string } + FindOutput: + type: object + additionalProperties: false + properties: + page: { $ref: "content/page.json" } +"##; + +/// The referenced file. Its model carries the name override the *consuming* +/// module has to resolve through. +const CROSS_MODULE_PAGE_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +x-py-name: RenamedPage +properties: + title: { type: string } +"##; + +/// Writes the two-file cross-module closure into `dir` and returns the input +/// directory to generate from. +fn write_cross_module_closure(dir: &Path) -> PathBuf { + let input_dir = dir.join("input"); + fs::create_dir_all(input_dir.join("content")).unwrap(); + fs::write( + input_dir.join("kb.nexusrpc.yaml"), + CROSS_MODULE_ENTRY_SCHEMA, + ) + .unwrap(); + fs::write( + input_dir.join("content/page.json"), + CROSS_MODULE_PAGE_SCHEMA, + ) + .unwrap(); + input_dir +} + +/// An `x-py-name` override on a model in *another* input file moves every +/// reference the consuming module emits: the operation's `Operation[...]` +/// parameter, the relative model imports, and the annotation of a cross-module +/// `$ref` property. The override is declared in the referenced file, so only the +/// tree-wide name manifest can resolve it (P14/P15). +#[test] +fn python_json_cross_module_py_name_override_moves_every_reference() { + let temp_dir = unique_output_path("py-json-cross-module-override"); + let input_dir = write_cross_module_closure(&temp_dir); + let output_path = temp_dir.join("output"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Python, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let declaring = fs::read_to_string(output_path.join("content/page/models.py")).unwrap(); + assert!(declaring.contains("class RenamedPage(pydantic.BaseModel):")); + + let services = fs::read_to_string(output_path.join("kb/services.py")).unwrap(); + for expected in [ + "from ..content.page.models import RenamedPage", + " RenamedPage,\n", + ] { + assert!(services.contains(expected), "{expected}\n{services}"); + } + + let models = fs::read_to_string(output_path.join("kb/models.py")).unwrap(); + for expected in [ + "from ..content.page.models import RenamedPage", + " page: RenamedPage | None", + ] { + assert!(models.contains(expected), "{expected}\n{models}"); + } + // Nothing names the pre-override identifier. + for stale in ["import Page", " Page,", ": Page"] { + assert!(!services.contains(stale), "{stale}\n{services}"); + assert!(!models.contains(stale), "{stale}\n{models}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_typescript.rs b/tests/generate_typescript.rs index 29c8a0d5..3396d4f9 100644 --- a/tests/generate_typescript.rs +++ b/tests/generate_typescript.rs @@ -126,6 +126,62 @@ $defs: title: { type: string } "##; +/// The entry file of a two-file closure. `get`'s output is the model the *other* +/// file declares, and `FindOutput.page` `$ref`s it from a property, so both +/// cross-module reference shapes are covered. +const CROSS_MODULE_ENTRY_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Pages: + fqn: example.pages.v1.Pages + operations: + get: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "content/page.json" } + find: + input: { $ref: "#/$defs/GetInput" } + output: { $ref: "#/$defs/FindOutput" } +$defs: + GetInput: + type: object + additionalProperties: false + properties: + id: { type: string } + FindOutput: + type: object + additionalProperties: false + properties: + page: { $ref: "content/page.json" } +"##; + +/// The referenced file. Its model carries the name override the *consuming* +/// module has to resolve through. +const CROSS_MODULE_PAGE_SCHEMA: &str = r##"$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +x-ts-name: RenamedPage +properties: + title: { type: string } +"##; + +/// Writes the two-file cross-module closure into `dir` and returns the input +/// directory to generate from. +fn write_cross_module_closure(dir: &Path) -> PathBuf { + let input_dir = dir.join("input"); + fs::create_dir_all(input_dir.join("content")).unwrap(); + fs::write( + input_dir.join("kb.nexusrpc.yaml"), + CROSS_MODULE_ENTRY_SCHEMA, + ) + .unwrap(); + fs::write( + input_dir.join("content/page.json"), + CROSS_MODULE_PAGE_SCHEMA, + ) + .unwrap(); + input_dir +} + fn project_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) } @@ -927,7 +983,9 @@ fn typescript_json_maps_element_position_unions() { assert!(rendered.contains("choiceTransferTypeConverter.fromTransferType(element)")); // A map member runs the member converter in both directions. assert!(rendered.contains("entriesValueTransferTypeConverter.fromTransferType(raw[key])")); - assert!(rendered.contains("out[key] = entriesValueTransferTypeConverter.toTransferType(entry);")); + assert!( + rendered.contains("out[key] = entriesValueTransferTypeConverter.toTransferType(entry);") + ); // Element nullability is the element's own concern, and parenthesized. assert!(rendered.contains("slots?: (string | null)[];")); fs::remove_dir_all(temp_dir).unwrap(); @@ -1010,3 +1068,58 @@ fn typescript_json_operation_type_info_follows_ts_name_override() { ); fs::remove_dir_all(temp_dir).unwrap(); } + +/// An `x-ts-name` override on a model in *another* input file moves every +/// reference the consuming module emits: the operation generic, the type-only +/// model import, the converter value import, and the property annotation of a +/// cross-module `$ref`. The override is declared in the referenced file, so only +/// the tree-wide name manifest can resolve it (P14/P15). +#[test] +fn typescript_json_cross_module_ts_name_override_moves_every_reference() { + let temp_dir = unique_output_path("ts-json-cross-module-override"); + let input_dir = write_cross_module_closure(&temp_dir); + let output_path = temp_dir.join("output"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let declaring = fs::read_to_string(output_path.join("content/page/models.ts")).unwrap(); + assert!(declaring.contains("export interface RenamedPage {")); + assert!(declaring.contains("export const renamedPageTransferTypeConverter = new class")); + + let services = fs::read_to_string(output_path.join("kb/services.ts")).unwrap(); + for expected in [ + "import { renamedPageTransferTypeConverter } from '../content/page/models';", + "import type { RenamedPage } from '../content/page/models';", + " RenamedPage\n", + "outputType: { transferTypeConverter: renamedPageTransferTypeConverter } }),", + ] { + assert!(services.contains(expected), "{expected}\n{services}"); + } + + let models = fs::read_to_string(output_path.join("kb/models.ts")).unwrap(); + for expected in [ + "import { renamedPageTransferTypeConverter } from '../content/page/models';", + "import type { RenamedPage } from '../content/page/models';", + " page?: RenamedPage;", + "page = renamedPageTransferTypeConverter.fromTransferType(raw.page);", + ] { + assert!(models.contains(expected), "{expected}\n{models}"); + } + // Nothing names the pre-override identifier. + for stale in ["{ Page }", "pageTransferTypeConverter", ": Page", " Page\n"] { + assert!(!services.contains(stale), "{stale}\n{services}"); + assert!(!models.contains(stale), "{stale}\n{models}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} From e67b25a58f14293cb52574da8b7efbdf76115101 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 14:34:40 -0700 Subject: [PATCH 04/10] Move member-derived synthesized names with the member's name override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An `x--name` override on a property did not reach two of the identifiers synthesized from that property, so a collision on either was rejected with a fix-it the author could not act on. The TypeScript `DEFAULT_` constant and the Go closed-value type `` (with its value constants) derived from the JSON key rather than the emitted member identifier. Two default-bearing members that recase alike — `retryCount` and `retry_count` — collide on `DEFAULT_RETRY_COUNT`, and the rejection's own remedy, "disambiguate with an `x-ts-name` override", moved the members apart while leaving both constants on the colliding name. Applying the override to one member, then to both, rejects identically; the only escape left was renaming the JSON property, which changes the wire contract. The Go closed-value type misfired the same way against a declared type name, and disagreed with Java, whose nested value class was already named off the emitted member. Both now derive from the emitted member identifier, in the emitters and in the P15 manifest that has to predict them, so the documented escape hatch resolves the clash and Go and Java agree on the synthesized type's name. The rule this follows — a name synthesized from a member moves with that member; a name synthesized from a position stays with the position — now sits in PRINCIPLES §15, which previously claimed the override moved every name synthesized from the property. `properties.md` carried the same overstatement while separately (and correctly) documenting the position exception. An inline object hoisted to `` is position-derived and is still renamed by authoring it in `$defs`. No generated sample moves: no checked-in schema puts a name override on a `default`- or `const`-bearing property. --- CHANGELOG.md | 20 ++++ specs/json-schema/PRINCIPLES.md | 2 +- specs/json-schema/features/const.md | 2 +- specs/json-schema/features/default.md | 10 +- specs/json-schema/features/properties.md | 16 +++- src/generator/json_schema/go.rs | 20 ++-- src/generator/json_schema/typescript.rs | 37 ++++--- src/parser/json_schema.rs | 117 +++++++++++++++++++---- tests/generate_go.rs | 62 ++++++++++++ tests/generate_typescript.rs | 64 +++++++++++++ 10 files changed, 308 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f98fa2..ba0e22cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- JSON Schema: An `x--name` override on a property did not move two of the + identifiers **synthesized from that property**, so a collision on either was + rejected with a fix-it the author could not act on. The TypeScript + `DEFAULT_` constant and the Go closed-value type `` (with + its value constants) were derived from the JSON key rather than the emitted + member identifier: two default-bearing members that recase alike + (`retryCount` + `retry_count`) collided on `DEFAULT_RETRY_COUNT`, and the + printed `disambiguate with an x-ts-name override` moved the *members* apart + while leaving both constants on the colliding name — with no remaining escape + short of renaming the JSON property, i.e. changing the wire contract. The Go + closed-value type had the same misfire against a declared type name, and + disagreed with Java, whose nested value class already followed the override. + Both are now named off the emitted member identifier, so the documented escape + hatch resolves the clash and Go and Java agree on the synthesized type's name. + The governing rule — **a name synthesized from a member moves with that + member; a name synthesized from a position stays with the position** — is now + stated in PRINCIPLES §15; an inline object hoisted to `` is + position-derived and is still renamed by authoring it in `$defs` instead. + Emitted output is unchanged for any schema that does not put a name override + on a `default`- or `const`-bearing property. - JSON Schema: A file's **root type and a same-file `$defs` entry of the same name** silently collapsed into one type. `thing.yaml` declaring a root object plus `$defs.Thing` — `Thing` being the name the root derives from the file name diff --git a/specs/json-schema/PRINCIPLES.md b/specs/json-schema/PRINCIPLES.md index 4cec6b1f..ff836040 100644 --- a/specs/json-schema/PRINCIPLES.md +++ b/specs/json-schema/PRINCIPLES.md @@ -50,7 +50,7 @@ renumber them. ### Output organization 14. **One module per input file; merge recursion.** Each input schema file maps to one generated module. Python, TypeScript, and Java mirror the input directory tree (Python subpackages, TS `index.ts` barrels, Java packages); **Go is the exception** — it collapses every input into a single flat package, because a Go package cannot participate in an import cycle and a cross-directory mutual reference would otherwise be an illegal cyclic import (with no cross-package hoist available). Cross-file reference *cycles* hoist only the cycle's strongly-connected types into one shared module (Python `_recursive.py`; Go resolves them within its flat package; TS/Java handle cycles natively). See [[ref]], [[generated-file-layout]]. -15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy) on the *declaring* property, which moves every name synthesized from it. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. +15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy), and which name it moves follows from where the synthesized name came from: **a name synthesized from a member moves with that member; a name synthesized from a position stays with the position.** So the override on the *declaring* property moves the member identifier and everything derived from it — the Go `OrDefault()` accessor and TS `DEFAULT_` constant ([[default]]), the closed-value type ([[const]]) — while a shape named after the position it was written in (an inline object hoisted to ``, see [[properties]]) keeps that name, and is renamed instead by authoring the shape in `$defs` and `$ref`ing it. A value constant is synthesized from the *value*, so it has its own `x--const-name` ([[const]]). Every escape hatch has to reach the name it is offered for: an override that moved the member but not a name derived from it would leave the collision unresolvable, which is the fix-it lying about the remedy. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. ### Surface diff --git a/specs/json-schema/features/const.md b/specs/json-schema/features/const.md index 72a43a50..fc3e692e 100644 --- a/specs/json-schema/features/const.md +++ b/specs/json-schema/features/const.md @@ -384,7 +384,7 @@ so the check is effectively a deserialize-direction guard there. | Composite const (deferred) | `{type:"object", const:{a:1}}`, `{type:"array", const:[1]}` | | Non-ASCII / whitespace string value | `{type:"string", const:"user admin"}`, `{type:"string", const:"café"}` | | Value un-encodable as an identifier | `{type:"string", const:"-"}` (empty token → Stage 3 reject; override: `x--const-name`) | -| Synthesized-name collision (P15) | Go flat `UserEventKind`/`UserEventKindUser` ⨯ a declared top-level name; a `$defs`-named const reusing an existing type name; two values whose encodings collide (`"user-admin"` ⨯ `"user_admin"` → both `UserAdmin`). Type-name clash → `x--name`; value-constant clash → `x--const-name`. (Nesting removes the Java anonymous case; Go stays flat → still caught. TS/Python close the type inline and synthesize nothing.) | +| Synthesized-name collision (P15) | Go flat `UserEventKind`/`UserEventKindUser` ⨯ a declared top-level name; a `$defs`-named const reusing an existing type name; two values whose encodings collide (`"user-admin"` ⨯ `"user_admin"` → both `UserAdmin`). Type-name clash → `x--name` on the declaring member, which moves the synthesized type because it is named `` off the *emitted* member identifier (`kind` + `x-go-name: Category` → `ProbeCategory`); value-constant clash → `x--const-name`. (Nesting removes the Java anonymous case; Go stays flat → still caught. TS/Python close the type inline and synthesize nothing.) | ### Runtime fixtures (validator) diff --git a/specs/json-schema/features/default.md b/specs/json-schema/features/default.md index d1657856..219e6937 100644 --- a/specs/json-schema/features/default.md +++ b/specs/json-schema/features/default.md @@ -143,7 +143,15 @@ Per **P15** these participate in the single per-scope collision pass and Python and Java add no name, so they carry no default-specific collision. The rename **escape hatch** is the [[properties]] case-mapping override (`x-go-name`, …) on the *declaring* field — re-mapping it moves the -synthesized `OrDefault` / `DEFAULT_` names with it. +synthesized `OrDefault` / `DEFAULT_` names with it, because +both are named off the **emitted** member identifier rather than the JSON +key (`retryCount` + `x-ts-name: attempts` → `DEFAULT_ATTEMPTS`). The +derivation has to work that way for the hatch to open at all: two members +that recase alike collide on `DEFAULT_`, and an override that moved +the members apart while leaving both constants on the JSON-derived name +would reject with a fix-it the author cannot act on — the only remaining +escape being a rename of the JSON property, i.e. a change to the wire +contract (P15, P7.1). Python and Java materialize-on-read for free (attribute default / getter); Go does so via the generated `OrDefault()` accessor. TypeScript has diff --git a/specs/json-schema/features/properties.md b/specs/json-schema/features/properties.md index b07c793d..7fbc384c 100644 --- a/specs/json-schema/features/properties.md +++ b/specs/json-schema/features/properties.md @@ -169,7 +169,14 @@ types/consts; the struct method-set for the Go accessor, where Go forbids a field/method clash outright). The single collision pass runs over that full union and rejects on any coincidence; the `x-*-name` override (Stage 4) on the declaring member is the escape hatch for these, -and re-mapping the member moves every name synthesized *from the member*. +and re-mapping the member moves every name synthesized *from the member* +with it — the Go `OrDefault()` accessor and TS `DEFAULT_` +constant ([[default]]), the Go closed-value type and Java value class +([[const]]) are all named off the **emitted** member identifier, not the +JSON key, so the override reaches them. A name synthesized from a +**position** rather than a member does not move: an inline object hoisted +to `` keeps the position's name (see +[Naming an inline object shape](#naming-an-inline-object-shape) below). A [[const]]/[[enum]] **value constant** is synthesized from the *value*, not the member — it shares the same namespace and collision pass but is re-mapped by its own `x--const-name` override, not `x-*-name`. The @@ -179,7 +186,12 @@ unstable under schema evolution (P13). ### Synthesized type names The Stage 1–4 algorithm maps *member* names; a synthesized **named type** -(the [[const]]/[[enum]] value class / defined type) is named separately. +(the [[const]]/[[enum]] value class / defined type) is named separately — +but off the **emitted** member identifier, so a Stage 4 override moves it +along with the member (`kind` + `x-go-name: Category` → `ProbeCategory`, +Java `Probe.Category`). That is what makes the override a working escape +hatch for a collision on the synthesized type, and it keeps the two +languages that synthesize one from disagreeing about its name. A const or an enum synthesizes a named type where the language lacks literal types (Go defined type, Java value class), for every scalar kind; TS and Python close the type inline (a literal / union of literals) and diff --git a/src/generator/json_schema/go.rs b/src/generator/json_schema/go.rs index 95b9cca6..31363e5b 100644 --- a/src/generator/json_schema/go.rs +++ b/src/generator/json_schema/go.rs @@ -1271,7 +1271,8 @@ fn render_const_discriminators(output: &mut String, models: &[&PlannedJsonType]) if !is_closed_value_schema(property) { continue; } - let type_name = const_type_name(&model.model_name, field_name); + let type_name = + const_type_name(&model.model_name, &property.go_member_name(field_name)); let underlying = go_closed_underlying(property); let consts = closed_values(property) .iter() @@ -3850,7 +3851,7 @@ fn go_property_type( model_names: &BTreeMap, ) -> Result { if is_closed_value_schema(schema) { - let type_name = const_type_name(model_name, json_name); + let type_name = const_type_name(model_name, &schema.go_member_name(json_name)); return Ok(if required { type_name } else { @@ -4205,8 +4206,15 @@ fn is_open_object(schema: &Schema) -> bool { && schema.additional_properties.as_ref() != Some(&Value::Bool(false)) } -fn const_type_name(model_name: &str, field_name: &str) -> String { - format!("{model_name}{}", go_field_name(field_name)) +/// The Go closed-value defined type, ``, built from the **emitted +/// member identifier** so an `x-go-name` override on the declaring property moves +/// it: a name synthesized *from the member* follows the member (P15). This matches +/// Java, whose nested value class is already named off the emitted member, and is +/// what makes the collision fix-it in [[const]] actually resolve a clash — while +/// the name derived from the JSON key, the override moved the field and left this +/// type behind. +fn const_type_name(model_name: &str, member_ident: &str) -> String { + format!("{model_name}{member_ident}") } /// True when the schema is a scalar closed value set (`const` or `enum`) that @@ -4277,7 +4285,7 @@ fn go_closed_value_name( } format!( "{}{}", - const_type_name(model_name, field_name), + const_type_name(model_name, &schema.go_member_name(field_name)), go_value_suffix(value) ) } @@ -4412,7 +4420,7 @@ fn render_closed_value_unmarshal( required: bool, ) { let field = property.go_member_name(json_name); - let type_name = const_type_name(model_name, json_name); + let type_name = const_type_name(model_name, &field); let underlying = go_closed_underlying(property); let parser = match underlying { "int64" => "parseIntegerField", diff --git a/src/generator/json_schema/typescript.rs b/src/generator/json_schema/typescript.rs index 391dbb1f..ca67e2dd 100644 --- a/src/generator/json_schema/typescript.rs +++ b/src/generator/json_schema/typescript.rs @@ -1616,17 +1616,20 @@ fn render_default_constants(output: &mut String, models: &[&PlannedJsonType]) -> continue; }; for (field_name, property) in properties { + // The constant is named after the emitted member, not the JSON key, + // so an `x-ts-name` override moves it too (P15). + let member_ident = property.ts_member_name(field_name); if let Some(default) = &property.default { default_fields.push(( model.model_name.clone(), - field_name.clone(), + member_ident.clone(), typescript_value_literal(default)?, )); } if let Some(const_value) = &property.const_value { const_fields.push(( model.model_name.clone(), - field_name.clone(), + member_ident, typescript_value_literal(const_value)?, )); } @@ -2719,7 +2722,7 @@ fn render_property_value_parser( if let Some(const_value) = &property.const_value { let const_name = const_const_name( &model.model_name, - json_name, + field_name, models, ConstNameCollisionKind::Const, )?; @@ -3767,9 +3770,18 @@ fn default_const_name( const_name(model_name, field_name, models, kind, "DEFAULT_", "") } +/// Names a synthesized module-scope constant after the **emitted member +/// identifier**, so an `x-ts-name` override on the declaring property moves the +/// constant with it: a name synthesized *from the member* follows the member +/// (P15, see specs/json-schema/features/default.md). The JSON name still selects +/// the property; only the identifier is derived from the override. +/// +/// Uniqueness is counted over emitted identifiers too. Two members that recase +/// alike collide here — and the override is what separates them, which it cannot +/// do if the constant keeps deriving from the JSON name. fn const_name( model_name: &str, - field_name: &str, + member_ident: &str, models: &[&PlannedJsonType], kind: ConstNameCollisionKind, prefix: &str, @@ -3782,23 +3794,24 @@ fn const_name( .into_iter() .filter(|schema| { schema.properties.as_ref().is_some_and(|properties| { - properties - .get(field_name) - .is_some_and(|property| match kind { - ConstNameCollisionKind::Const => property.const_value.is_some(), - ConstNameCollisionKind::Default => property.default.is_some(), - }) + properties.iter().any(|(json_name, property)| { + property.ts_member_name(json_name) == member_ident + && match kind { + ConstNameCollisionKind::Const => property.const_value.is_some(), + ConstNameCollisionKind::Default => property.default.is_some(), + } + }) }) }) .count(); let mut name = if field_count == 1 { - field_name.to_shouty_snake_case() + member_ident.to_shouty_snake_case() } else { format!( "{}_{}", model_name.to_shouty_snake_case(), - field_name.to_shouty_snake_case() + member_ident.to_shouty_snake_case() ) }; name.insert_str(0, prefix); diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index f28856ae..c213026e 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -5937,11 +5937,14 @@ fn collect_synthesized_top_level( if values.is_empty() { continue; } - // The Go closed-value defined type is `` and each value - // constant is `` — derived from the recased - // field name (the generator names these off `go_field_name`, so the - // collision pass matches the emitted identifiers). - let defined_type = format!("{type_ident}{}", recase_member(Language::Go, json_name)); + // The Go closed-value defined type is `` and each value + // constant is ``. Both derive from the *emitted* + // member identifier, so an `x-go-name` override moves them with the field + // (P15) — and so this pass matches what the generator emits. + let defined_type = format!( + "{type_ident}{}", + member_identifier(Language::Go, json_name, property) + ); top.insert( language, defined_type.clone(), @@ -6039,10 +6042,17 @@ fn validate_member_scope(language: Language, model_full_name: &str, schema: &Sch } /// TypeScript `DEFAULT_` constants (module scope). The generator names a -/// default constant `DEFAULT_` when the field name is unique across the -/// module's models, else `DEFAULT__`. Replicate that name and +/// default constant `DEFAULT_` when the member identifier is unique across +/// the module's models, else `DEFAULT__`. Replicate that name and /// enter it into the shared module namespace so a genuine clash rejects (P15) /// rather than silently coexisting behind the model-name prefix. +/// +/// The identifier is built from the **emitted member identifier**, so an +/// `x-ts-name` override on the declaring property moves this constant with it — +/// a name synthesized *from the member* follows the member (P15). Were it built +/// from the JSON name, two members that recase alike would collide here with no +/// way to author around it: the override would move the members apart while +/// leaving both constants on the colliding name. fn collect_ts_default_constants( module_key: &str, models: &[NsModel], @@ -6052,20 +6062,19 @@ fn collect_ts_default_constants( .iter() .filter(|model| model.module_key == module_key) .collect(); - // How many models declare a scalar-default field with this JSON name. - let field_count = |json_name: &str| -> usize { + // How many models declare a scalar-default field emitting this identifier. + let field_count = |member_ident: &str| -> usize { group .iter() .filter(|model| { - model - .schema - .properties - .as_ref() - .and_then(|properties| properties.get(json_name)) - .and_then(|property| property.extra.get("default")) - .is_some_and(|default| { - !default.is_null() && !default.is_object() && !default.is_array() + model.schema.properties.as_ref().is_some_and(|properties| { + properties.iter().any(|(json_name, property)| { + member_identifier(Language::TypeScript, json_name, property) == member_ident + && property.extra.get("default").is_some_and(|default| { + !default.is_null() && !default.is_object() && !default.is_array() + }) }) + }) }) .count() }; @@ -6080,8 +6089,9 @@ fn collect_ts_default_constants( if default.is_null() || default.is_object() || default.is_array() { continue; } - let field_shouty = json_name.to_shouty_snake_case(); - let ident = if field_count(json_name) == 1 { + let member_ident = member_identifier(Language::TypeScript, json_name, property); + let field_shouty = member_ident.to_shouty_snake_case(); + let ident = if field_count(&member_ident) == 1 { format!("DEFAULT_{field_shouty}") } else { format!( @@ -9038,6 +9048,75 @@ properties: parse_for(Language::Go, input).expect("override resolves the Go collision"); } + /// A name synthesized *from a member* follows that member's override (P15). + /// Two default-bearing members that recase alike collide on the TS + /// `DEFAULT_` constant; the override has to reach the constant, or the + /// rejection's own fix-it cannot resolve it and the only escape left is + /// renaming the JSON property — a change to the wire contract. + #[test] + fn default_constant_collision_resolved_by_override() { + let colliding = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + retryCount: { type: string, default: "a" } + retry_count: { type: string, default: "b" } +"#; + let error = reject_for(Language::TypeScript, colliding); + assert!(error.contains("collision"), "{error}"); + + let resolved = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + retryCount: { type: string, default: "a" } + retry_count: { type: string, default: "b", x-ts-name: retriesTwo } +"#; + parse_for(Language::TypeScript, resolved) + .expect("the override moves the DEFAULT_ constant with the member"); + } + + /// The Go closed-value defined type is `` off the *emitted* + /// member identifier, so an `x-go-name` override moves it out of a clash with + /// a declared type — matching Java's nested value class, which already + /// followed the override. + #[test] + fn closed_value_type_collision_resolved_by_override() { + // The harness names the file-root model `Api`, so the synthesized + // closed-value type is `ApiKind` — which the `$defs` entry then clashes + // with. + let colliding = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + kind: { type: string, const: widget } +$defs: + ApiKind: + type: object + properties: + x: { type: string } +"#; + let error = reject_for(Language::Go, colliding); + assert!( + error.contains("collision") && error.contains("ApiKind"), + "{error}" + ); + + let resolved = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + kind: { type: string, const: widget, x-go-name: Category } +$defs: + ApiKind: + type: object + properties: + x: { type: string } +"#; + parse_for(Language::Go, resolved) + .expect("the override moves the closed-value type with the member"); + } + #[test] fn value_constant_collision_resolved_by_enum_names_override() { // `"user-admin"` and `"user_admin"` both encode to the Go value constant diff --git a/tests/generate_go.rs b/tests/generate_go.rs index fc5f3519..c961bce5 100644 --- a/tests/generate_go.rs +++ b/tests/generate_go.rs @@ -1944,3 +1944,65 @@ fn go_json_cross_module_go_name_override_moves_every_reference() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// A property carrying a per-language name override alongside a `const` and an +/// inline object: the closed-value type is member-derived and moves with the +/// override, while the hoisted shape is position-derived and does not. +const MEMBER_DERIVED_NAME_SCHEMA: &str = r#"$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + retryCount: + type: integer + default: 3 + x-go-name: Attempts + kind: + type: string + const: widget + x-go-name: Category + address: + type: object + x-go-name: Location + properties: + street: { type: string } +"#; + +/// A name synthesized from a member follows that member's `x-go-name` — the +/// `OrDefault()` accessor and the `` closed-value type (plus +/// its value constants). A shape named after its *position* does not move. +/// See `specs/json-schema/PRINCIPLES.md` §15, `specs/json-schema/features/const.md`. +#[test] +fn go_json_override_moves_member_derived_names_only() { + let temp_dir = unique_output_path("go-json-member-derived-names"); + fs::create_dir_all(&temp_dir).unwrap(); + let input_path = temp_dir.join("probe.yaml"); + fs::write(&input_path, MEMBER_DERIVED_NAME_SCHEMA).unwrap(); + let output_path = temp_dir.join("probe"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Go, + input_paths: vec![input_path], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + let rendered = read_go_output_files(&output_path) + .into_values() + .collect::>() + .join("\n"); + + // Member-derived: accessor, closed-value type, and its value constant. + assert!(rendered.contains("func (m Probe) AttemptsOrDefault() int64 {")); + assert!(rendered.contains("type ProbeCategory string")); + assert!(rendered.contains("const ProbeCategoryWidget ProbeCategory = \"widget\"")); + assert!(!rendered.contains("ProbeKind")); + // Position-derived: the hoisted shape keeps the position's name. + assert!(rendered.contains("Location *ProbeAddress `json:\"address,omitempty\"`")); + assert!(rendered.contains("type ProbeAddress struct {")); + assert!(!rendered.contains("ProbeLocation")); + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_typescript.rs b/tests/generate_typescript.rs index 3396d4f9..50527d21 100644 --- a/tests/generate_typescript.rs +++ b/tests/generate_typescript.rs @@ -182,6 +182,30 @@ fn write_cross_module_closure(dir: &Path) -> PathBuf { input_dir } +/// A property carrying a per-language name override alongside a `default`, a +/// `const`, and an inline object — one schema covering all three synthesized-name +/// families at once. +const MEMBER_DERIVED_NAME_SCHEMA: &str = r#"$schema: https://json-schema.org/draft/2020-12/schema +type: object +properties: + retryCount: + type: integer + default: 3 + x-ts-name: attempts + x-go-name: Attempts + kind: + type: string + const: widget + x-ts-name: category + x-go-name: Category + address: + type: object + x-ts-name: location + x-go-name: Location + properties: + street: { type: string } +"#; + fn project_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) } @@ -1123,3 +1147,43 @@ fn typescript_json_cross_module_ts_name_override_moves_every_reference() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// A name synthesized from a member follows that member's `x-ts-name`: the +/// `DEFAULT_` constant is built from the emitted identifier, not the JSON +/// key. A shape named after its *position* does not move — the hoisted inline +/// object keeps ``. +/// See `specs/json-schema/PRINCIPLES.md` §15 and +/// `specs/json-schema/features/default.md`. +#[test] +fn typescript_json_override_moves_member_derived_names_only() { + let temp_dir = unique_output_path("ts-json-member-derived-names"); + fs::create_dir_all(&temp_dir).unwrap(); + let input_path = temp_dir.join("probe.yaml"); + fs::write(&input_path, MEMBER_DERIVED_NAME_SCHEMA).unwrap(); + let output_path = temp_dir.join("probe"); + + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_path], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + let rendered = fs::read_to_string(output_path.join("models.ts")).unwrap(); + + // Member-derived: the override moves the constant with the field. + assert!(rendered.contains("export const DEFAULT_ATTEMPTS = 3;")); + assert!(!rendered.contains("DEFAULT_RETRY_COUNT")); + assert!(rendered.contains("attempts?: number;")); + // Position-derived: the hoisted shape keeps the position's name even though + // the declaring member is renamed. + assert!(rendered.contains("location?: ProbeAddress;")); + assert!(rendered.contains("export interface ProbeAddress {")); + assert!(!rendered.contains("ProbeLocation")); + fs::remove_dir_all(temp_dir).unwrap(); +} From 777d5698b119f0231ababba8d7e6a4b85d72f035 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 14:46:46 -0700 Subject: [PATCH 05/10] Register TypeScript `_CONST` bindings in the collision pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `const`-bearing member emits a module-level `_CONST` holding the fixed wire value, named `__CONST` when the member identifier is not unique across the module's models. That binding never entered the P15 namespace, so two of them could coincide and both be emitted. They coincide through the model-name disambiguator: `A.kind` is prefixed because `kind` is not unique, giving `A_KIND_CONST`, which is exactly what a unique `C.aKind` produces unprefixed. Before this change that schema generated `const A_KIND_CONST` twice in one `models.ts` — a TypeScript SyntaxError, emitted without a diagnostic. It now rejects with a fix-it naming both origins, and an `x-ts-name` on either member resolves it. Unexported is not out of scope: a module-level `const` is a module-scope binding, and a redeclaration is an error whether or not it is exported. `default.md` and `const.md` both claimed TypeScript synthesizes no identifier for a `const`. It synthesizes no named *type* — the type closes to an inline literal — but it does emit this value binding, and both now say so. --- specs/json-schema/features/const.md | 2 +- specs/json-schema/features/default.md | 2 +- src/parser/json_schema.rs | 122 +++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 5 deletions(-) diff --git a/specs/json-schema/features/const.md b/specs/json-schema/features/const.md index fc3e692e..cbe85d72 100644 --- a/specs/json-schema/features/const.md +++ b/specs/json-schema/features/const.md @@ -384,7 +384,7 @@ so the check is effectively a deserialize-direction guard there. | Composite const (deferred) | `{type:"object", const:{a:1}}`, `{type:"array", const:[1]}` | | Non-ASCII / whitespace string value | `{type:"string", const:"user admin"}`, `{type:"string", const:"café"}` | | Value un-encodable as an identifier | `{type:"string", const:"-"}` (empty token → Stage 3 reject; override: `x--const-name`) | -| Synthesized-name collision (P15) | Go flat `UserEventKind`/`UserEventKindUser` ⨯ a declared top-level name; a `$defs`-named const reusing an existing type name; two values whose encodings collide (`"user-admin"` ⨯ `"user_admin"` → both `UserAdmin`). Type-name clash → `x--name` on the declaring member, which moves the synthesized type because it is named `` off the *emitted* member identifier (`kind` + `x-go-name: Category` → `ProbeCategory`); value-constant clash → `x--const-name`. (Nesting removes the Java anonymous case; Go stays flat → still caught. TS/Python close the type inline and synthesize nothing.) | +| Synthesized-name collision (P15) | Go flat `UserEventKind`/`UserEventKindUser` ⨯ a declared top-level name; a `$defs`-named const reusing an existing type name; two values whose encodings collide (`"user-admin"` ⨯ `"user_admin"` → both `UserAdmin`). Type-name clash → `x--name` on the declaring member, which moves the synthesized type because it is named `` off the *emitted* member identifier (`kind` + `x-go-name: Category` → `ProbeCategory`); value-constant clash → `x--const-name`. (Nesting removes the Java anonymous case; Go stays flat → still caught. TS and Python close the type inline and synthesize no named type; TS still emits a module-scope `_CONST` binding for the value, which joins the same collision pass — two of them can coincide through the model-name disambiguator, e.g. a prefixed `A.kind` → `A_KIND_CONST` against an unprefixed `C.aKind`, and emitting both would be a duplicate `const` in one module.) | ### Runtime fixtures (validator) diff --git a/specs/json-schema/features/default.md b/specs/json-schema/features/default.md index 219e6937..acd45131 100644 --- a/specs/json-schema/features/default.md +++ b/specs/json-schema/features/default.md @@ -133,7 +133,7 @@ The read-side surfacing synthesizes **one new identifier in two targets** | Target | Synthesized identifier | Scope | Collision risk | |---|---|---|---| | Go | `OrDefault()` method | struct method-set | a **declared** member whose name maps to `OrDefault` (Go forbids a field and method of the same name — a **hard compile error**); another `OrDefault` from a sibling field | -| TypeScript | `DEFAULT_` const | module | another `DEFAULT_` from a field that case-maps the same ([[const]] synthesizes no TS identifier — the value is an inline literal) | +| TypeScript | `DEFAULT_` const | module | another `DEFAULT_` from a field that case-maps the same. [[const]] synthesizes no named *type* in TS (the type closes to an inline literal) but does emit a module-scope `_CONST` binding holding the wire value, which shares this scope — unexported, yet still a redeclaration error if it coincides | | Python | none (native Pydantic field `default=`) | — | — | | Java | none (default folds into the existing getter) | — | — | diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index c213026e..d1acb77b 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -5807,11 +5807,12 @@ pub(crate) fn build_name_manifest( )?; } } - // TypeScript `DEFAULT_` constants and per-model transfer type - // converters share the module scope; make them participate rather than - // silently coexist (P15). + // TypeScript `DEFAULT_` / `_CONST` constants and per-model + // transfer type converters share the module scope; make them participate + // rather than silently coexist (P15). if language == Language::TypeScript { collect_ts_default_constants(module_key, &ns_models, &mut top)?; + collect_ts_const_constants(module_key, &ns_models, &mut top)?; collect_ts_transfer_type_converters(module_key, &ns_models, &mut top)?; } } @@ -6109,6 +6110,67 @@ fn collect_ts_default_constants( Ok(()) } +/// TypeScript `_CONST` constants (module scope). A `const`-bearing member +/// emits a module-level constant holding the fixed wire value, named +/// `_CONST` when the member identifier is unique across the module's +/// models, else `__CONST`. The constant is not exported, but it is +/// still a module-scope binding: a clash with any other module-scope identifier +/// is a TypeScript redeclaration error, so it belongs in the collision pass +/// (P15) rather than being emitted twice. +/// +/// Like the `DEFAULT_` constant, the identifier is built from the **emitted +/// member identifier**, so an `x-ts-name` override moves it with the member. +fn collect_ts_const_constants( + module_key: &str, + models: &[NsModel], + top: &mut Namespace, +) -> Result<()> { + let group: Vec<&NsModel> = models + .iter() + .filter(|model| model.module_key == module_key) + .collect(); + // How many models declare a `const` member emitting this identifier. + let field_count = |member_ident: &str| -> usize { + group + .iter() + .filter(|model| { + model.schema.properties.as_ref().is_some_and(|properties| { + properties.iter().any(|(json_name, property)| { + member_identifier(Language::TypeScript, json_name, property) == member_ident + && property.extra.contains_key("const") + }) + }) + }) + .count() + }; + for model in &group { + let Some(properties) = &model.schema.properties else { + continue; + }; + for (json_name, property) in properties { + if !property.extra.contains_key("const") { + continue; + } + let member_ident = member_identifier(Language::TypeScript, json_name, property); + let field_shouty = member_ident.to_shouty_snake_case(); + let ident = if field_count(&member_ident) == 1 { + format!("{field_shouty}_CONST") + } else { + format!( + "{}_{field_shouty}_CONST", + model.type_ident.to_shouty_snake_case() + ) + }; + top.insert( + Language::TypeScript, + ident, + format!("`{}.{json_name}` _CONST constant", model.full_name), + )?; + } + } + Ok(()) +} + /// TypeScript per-model `TransferTypeConverter` instances (module scope). The /// identifier is derived from the model's type identifier /// ([`ts_transfer_type_converter_name`]), and lower-camel-casing is not @@ -9117,6 +9179,60 @@ $defs: .expect("the override moves the closed-value type with the member"); } + /// A `const` member's `_CONST` binding is module-scope, so it takes + /// part in the collision pass even though it is not exported. Two of them can + /// coincide through the model-name disambiguator — `A.kind` is prefixed + /// (`kind` is not unique) to `A_KIND_CONST`, which is exactly what the unique + /// `C.aKind` produces unprefixed. Emitting both is a duplicate `const` in one + /// module, a TypeScript `SyntaxError`. + #[test] + fn const_constant_collision_rejects_and_is_resolved_by_override() { + let colliding = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +$defs: + A: + type: object + properties: + kind: { type: string, const: one } + B: + type: object + properties: + kind: { type: string, const: two } + C: + type: object + properties: + aKind: { type: string, const: three } +"#; + let error = reject_for(Language::TypeScript, colliding); + assert!( + error.contains("collision") && error.contains("A_KIND_CONST"), + "{error}" + ); + + let resolved = r#" +$schema: https://json-schema.org/draft/2020-12/schema +type: object +additionalProperties: false +$defs: + A: + type: object + properties: + kind: { type: string, const: one } + B: + type: object + properties: + kind: { type: string, const: two } + C: + type: object + properties: + aKind: { type: string, const: three, x-ts-name: cKind } +"#; + parse_for(Language::TypeScript, resolved) + .expect("the override moves the _CONST binding with the member"); + } + #[test] fn value_constant_collision_resolved_by_enum_names_override() { // `"user-admin"` and `"user_admin"` both encode to the Go value constant From e35089f18d81ea853601e445dd9ffbc87d7bd9b6 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 14:46:46 -0700 Subject: [PATCH 06/10] Make the add_message temp directory unique per call `add_rpc_uses_the_same_oneof_variant_rendering` failed intermittently under the full suite and passed when run alone. `unique_temp_dir` derived the directory from the wall clock only, and `write_oneof_descriptor` passes one fixed name while six tests call it. Two threads reading the same timestamp share a directory and `fs::write` the same `api.bin` concurrently, so a third can read the file truncated mid-write. The clock's granularity, not the test, decided whether the run was green. Add a process-wide counter to the path. Five consecutive runs of the suite pass, and `scripts/validate.sh` is green. --- tests/add_message.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/add_message.rs b/tests/add_message.rs index 11ee6d0f..e86e407f 100644 --- a/tests/add_message.rs +++ b/tests/add_message.rs @@ -4,6 +4,7 @@ use std::fs; use std::path::PathBuf; use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; use nexgen::{add_message_to_string, add_rpc_to_string}; @@ -26,12 +27,19 @@ fn linked_inputs_path(root: &std::path::Path) -> PathBuf { root.join("advanced/samples/inputs/deps") } +/// A per-call temp directory. The clock alone is not enough: `write_oneof_descriptor` +/// passes one fixed `name` and several tests call it, so two threads that read the +/// same timestamp would land on one directory and `fs::write` the same `api.bin` +/// concurrently — a reader then sees the truncated file mid-write. The counter makes +/// the path unique per call regardless of clock granularity. fn unique_temp_dir(name: &str) -> PathBuf { + static COUNTER: AtomicU64 = AtomicU64::new(0); let unique = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); - std::env::temp_dir().join(format!("nexgen-{name}-{unique}")) + let seq = COUNTER.fetch_add(1, Ordering::Relaxed); + std::env::temp_dir().join(format!("nexgen-{name}-{unique}-{seq}")) } fn write_temp_wit(name: &str, contents: &str) -> PathBuf { From fde058a1a1592c133a7e935b253f50ef2900006e Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 15:15:29 -0700 Subject: [PATCH 07/10] Scope the P15 collision pass to what each target actually resolves in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two scope errors let multi-input runs accept schemas that generate uncompilable code. Services were entered into the namespace only when the module key was empty — the root module, which in multi-input mode no file occupies. A service clashing with a model in its own file therefore rejected when that file was the sole input and was silently accepted when the same file sat in a directory. Services now enter the namespace of the module that declares them. Every target was also scoped per module, but Go flattens every module into one package. Two input files each declaring a `Page` emitted the type twice into that package — `Page redeclared in this block`, confirmed with the Go type checker — and the pass said nothing. Go's scope is now the whole input closure; the other three targets keep per-module scoping, where separate modules genuinely keep the names apart. Making Go's scope package-wide was not enough on its own: a collision is detected by identifier, and two models named `Page` in different modules produced the identical origin string `type `Page``, which the namespace read as one declaration re-inserted rather than a clash. Origins now carry the module-qualified name, so the diagnostic names both sides (`a/page#Page` and `b/page#Page`) and the dedupe only fires for what is genuinely the same declaration seen twice. A third defect surfaced while fixing these: a TypeScript service identifier was derived as a type name, though the generator emits a lower-camel `const`. The pass rejected a service and a model of the same name, which generate cleanly as `chatService` and `ChatService`, and missed the clash TypeScript does have — a service whose lower-camel form lands on a model's `TransferTypeConverter`. Widening service insertion while that derivation was wrong would have spread the false positive across every module, so it is fixed here. The manifest feeds only the collision pass, so no emitted name changes. `generated-file-layout.md` already claimed cross-file type collisions were validated in Go, and `services.md` already put service bindings in the model namespace; both were ahead of the implementation. Their text now distinguishes the module scope from Go's package scope, and PRINCIPLES §15 defines what a scope is per target. No generated sample moves. --- CHANGELOG.md | 22 ++++ specs/json-schema/PRINCIPLES.md | 2 +- specs/json-schema/generated-file-layout.md | 5 +- specs/json-schema/services.md | 30 +++-- src/parser/json_schema.rs | 131 +++++++++++++++++---- src/planning/emitted_names.rs | 1 + tests/generate_go.rs | 126 ++++++++++++++++++++ 7 files changed, 284 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba0e22cc..443fc589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- JSON Schema: The P15 collision pass used the wrong scope in two ways, so + **multi-input** runs accepted schemas that generate uncompilable code. Services + were only entered into the namespace of the root module, which in multi-input + mode is no module at all — a service clashing with a model in its own file + rejected when that file was the sole input and was silently accepted when it sat + in a directory. And every target was scoped per module, but **Go flattens every + module into one package**: two input files each declaring a `Page` emitted the + type twice into one package (`Page redeclared in this block`) with no + diagnostic. Services now enter the namespace of their declaring module, and Go's + scope is the whole input closure; its diagnostic names the module each + declaration came from (`a/page#Page` and `b/page#Page`), which it could not do + while two same-named models in different modules produced identical origin text + and were mistaken for one declaration seen twice. The other three targets are + unchanged — separate modules keep the names apart, as + `generated-file-layout.md` already specified. +- JSON Schema: A TypeScript service identifier was derived as a *type* name in the + collision pass while the generator emits a lower-camel `const`, so the pass + rejected a service and a model of the same name — `chatService` and + `ChatService` are distinct TypeScript identifiers and generate cleanly — and + missed the clash TypeScript really has, a service whose lower-camel form lands + on a model's `TransferTypeConverter`. The identifier is now derived the + way it is emitted. Only the collision pass is affected; no emitted name changes. - JSON Schema: An `x--name` override on a property did not move two of the identifiers **synthesized from that property**, so a collision on either was rejected with a fix-it the author could not act on. The TypeScript diff --git a/specs/json-schema/PRINCIPLES.md b/specs/json-schema/PRINCIPLES.md index ff836040..1dc391c1 100644 --- a/specs/json-schema/PRINCIPLES.md +++ b/specs/json-schema/PRINCIPLES.md @@ -50,7 +50,7 @@ renumber them. ### Output organization 14. **One module per input file; merge recursion.** Each input schema file maps to one generated module. Python, TypeScript, and Java mirror the input directory tree (Python subpackages, TS `index.ts` barrels, Java packages); **Go is the exception** — it collapses every input into a single flat package, because a Go package cannot participate in an import cycle and a cross-directory mutual reference would otherwise be an illegal cyclic import (with no cross-package hoist available). Cross-file reference *cycles* hoist only the cycle's strongly-connected types into one shared module (Python `_recursive.py`; Go resolves them within its flat package; TS/Java handle cycles natively). See [[ref]], [[generated-file-layout]]. -15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy), and which name it moves follows from where the synthesized name came from: **a name synthesized from a member moves with that member; a name synthesized from a position stays with the position.** So the override on the *declaring* property moves the member identifier and everything derived from it — the Go `OrDefault()` accessor and TS `DEFAULT_` constant ([[default]]), the closed-value type ([[const]]) — while a shape named after the position it was written in (an inline object hoisted to ``, see [[properties]]) keeps that name, and is renamed instead by authoring the shape in `$defs` and `$ref`ing it. A value constant is synthesized from the *value*, so it has its own `x--const-name` ([[const]]). Every escape hatch has to reach the name it is offered for: an override that moved the member but not a name derived from it would leave the collision unresolvable, which is the fix-it lying about the remedy. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. +15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy), and which name it moves follows from where the synthesized name came from: **a name synthesized from a member moves with that member; a name synthesized from a position stays with the position.** So the override on the *declaring* property moves the member identifier and everything derived from it — the Go `OrDefault()` accessor and TS `DEFAULT_` constant ([[default]]), the closed-value type ([[const]]) — while a shape named after the position it was written in (an inline object hoisted to ``, see [[properties]]) keeps that name, and is renamed instead by authoring the shape in `$defs` and `$ref`ing it. A value constant is synthesized from the *value*, so it has its own `x--const-name` ([[const]]). Every escape hatch has to reach the name it is offered for: an override that moved the member but not a name derived from it would leave the collision unresolvable, which is the fix-it lying about the remedy. A **scope** is whatever unit the target actually resolves names in, which is not the same everywhere: for Python, TypeScript and Java it is the **module** the declaring file emits into, so two input files may each declare a `Page`; for **Go** it is the whole run, because every module flattens into one package ([[generated-file-layout]]) and two modules declaring `Page` are one redeclaration. A service is checked in the module that declares it, alongside that module's types. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. ### Surface diff --git a/specs/json-schema/generated-file-layout.md b/specs/json-schema/generated-file-layout.md index c4c004a5..2da27c96 100644 --- a/specs/json-schema/generated-file-layout.md +++ b/specs/json-schema/generated-file-layout.md @@ -186,7 +186,10 @@ override or rename): - two inputs flattening to the same module (`full/name` vs `full_name`); - an input flattening onto a reserved generated name (a root-level `definitions.json`); -- (type-name collisions are handled the same way — see [[ref]]); +- a **type name** declared by two different input files — `a/page.json` + and `b/page.json` both emitting `Page` is one redeclaration in the flat + package, so Go's collision scope is the whole run rather than the module + (see [[ref]] and PRINCIPLES §15); the diagnostic names both modules; - a generated **service binding** colliding with a model (or synthesized I/O) type — service `ChatService` against a `$defs/ChatService`; see [[services]], which shares this one namespace. diff --git a/specs/json-schema/services.md b/specs/json-schema/services.md index 5e8a7d81..edea7ca9 100644 --- a/specs/json-schema/services.md +++ b/specs/json-schema/services.md @@ -61,8 +61,9 @@ Rationale (citing [[PRINCIPLES.md]]): Nexus document — live in [[input-files]].) - **P14 / P15 ([[generated-file-layout]])**: bindings emit into the declaring file's module (Java the one-class-per-file exception); every - synthesized identifier and I/O type name enters the single per-package - collision pass. + synthesized identifier and I/O type name enters the collision pass for + that module — which in Go is the whole package, since every module + flattens into one. ## Document gating @@ -400,19 +401,26 @@ Per [[generated-file-layout]]: './'`), `__init__.py` (`__all__`). Go/Java rely on exported visibility (capitalized / `public`). - Service identifiers, operation field identifiers, and synthesized I/O - type names all live in the one package-wide identifier namespace (P15) - and are checked **per emitted target** (normalization differs per - language, like [[properties]] / [[ref]]). + type names all live in the identifier namespace of the **declaring + module** (P15) — package-wide in Go, where every module flattens into one + package — and are checked **per emitted target** (normalization differs + per language, like [[properties]] / [[ref]]). A service declared in a + non-root input file is checked in that file's module, not the root's. > **A generated service and a generated model that resolve to the same > identifier collide, and the generator fails the build (P7.1/P15).** A -> service binding occupies the *same* per-package namespace as the -> `$defs` model types — it is not a separate namespace. So a service +> service binding occupies the *same* namespace as the `$defs` model types +> of its module — it is not a separate namespace. So a service > `ChatService` and a `$defs/ChatService` model both claim the identifier -> `ChatService` (Go: a `var ChatService` against a `type ChatService`; -> Python: two `class ChatService`; TS: a `const chatService` against a -> recased model; Java: two top-level `ChatService` types). This is a -> **load reject** with a fix-it, **never silently mangled** — exactly the +> `ChatService` in Go (a `var ChatService` against a `type ChatService`), +> Python (two `class ChatService`) and Java (two top-level `ChatService` +> types). **TypeScript is the exception**: it binds a service to a +> lower-camel `const`, so `chatService` and the model's `ChatService` are +> distinct identifiers and the pair generates cleanly. What a TypeScript +> service *can* collide with is another lower-camel module binding — most +> readily a model's `TransferTypeConverter`, which a service named +> `ChatServiceTransferTypeConverter` would claim. This is a **load reject** +> with a fix-it, **never silently mangled** — exactly the > synthesized-I/O-vs-`$defs` rule above, one level up. Resolve it by > renaming the `$defs` model or applying `x--name` to the service > (the `fqn` wire name is unaffected — only the *code identifier* diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index d1acb77b..e1ff69cb 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -5701,15 +5701,37 @@ pub(crate) struct ManifestService { /// (`x--name`), if the active target carries one. `None` derives from /// `name`. pub(crate) code_name: Option, + /// The module the declaring file emits into — the scope this service's + /// identifier occupies. Empty for the single-input root. + pub(crate) module_key: String, } impl ManifestService { /// The emitted service code identifier for `language`: the verbatim override /// when present, else the derived name. + /// + /// TypeScript binds a service to a lower-camel `const` (`chatService`), not a + /// type name, so it derives through the member pipeline; Go's `var`, Python's + /// `class`, and Java's class all carry the name as authored. Deriving all four + /// as type names claimed a TypeScript service collided with a same-named + /// model, which it never can — the emitted identifiers differ in case. fn code_ident(&self, language: Language) -> String { - self.code_name - .clone() - .unwrap_or_else(|| recase_type_name(language, &self.name)) + self.code_name.clone().unwrap_or_else(|| match language { + Language::TypeScript => recase_member(Language::TypeScript, &self.name), + _ => recase_type_name(language, &self.name), + }) + } + + /// How this service is named in a collision diagnostic. The module qualifier + /// matters in Go, whose scope spans every module: two same-named services in + /// different modules are a real clash, and identical origin text would make + /// them read as one declaration seen twice. + fn origin_label(&self) -> String { + if self.module_key.is_empty() { + format!("service `{}`", self.name) + } else { + format!("service `{}` in module `{}`", self.name, self.module_key) + } } } @@ -5749,7 +5771,7 @@ pub(crate) fn build_name_manifest( .insert(model.full_name.clone(), type_ident.clone()); ns_models.push(NsModel { module_key: model.module_key.clone(), - full_name: model.local_name.clone(), + full_name: model.full_name.clone(), type_ident, schema, }); @@ -5759,17 +5781,29 @@ pub(crate) fn build_name_manifest( return Ok(manifest); } - // Group modules → their own top-level namespace. + // Each emitted scope gets its own top-level namespace. For most targets that + // is the module, which maps to one emitted file set. **Go is different**: it + // flattens every module into a single package, so two same-named types in + // different modules are redeclarations in one package — its scope is the whole + // closure, and `None` below means "every module at once". + // + // A module with services but no models still has a scope, so its service + // identifiers are checked against the boilerplate. let module_keys: BTreeSet = ns_models .iter() .map(|model| model.module_key.clone()) + .chain(services.iter().map(|service| service.module_key.clone())) .collect(); - for module_key in &module_keys { + let scopes: Vec> = if language == Language::Go { + vec![None] + } else { + module_keys.into_iter().map(Some).collect() + }; + for scope in &scopes { + let in_scope = |key: &str| scope.as_deref().is_none_or(|scope| scope == key); + let module_key: &str = scope.as_deref().unwrap_or_default(); let mut top = Namespace::default(); - for model in ns_models - .iter() - .filter(|model| &model.module_key == module_key) - { + for model in ns_models.iter().filter(|model| in_scope(&model.module_key)) { top.insert( language, model.type_ident.clone(), @@ -5796,16 +5830,20 @@ pub(crate) fn build_name_manifest( format!("generated runtime identifier `{ident}`"), )?; } - // Services and their derived bindings live in the root module scope of - // the file that declares them (the single-input scope). - if module_key.is_empty() { - for service in services { - top.insert( - language, - service.code_ident(language), - format!("service `{}`", service.name), - )?; - } + // A service's bindings live in the module scope of the file that declares + // it — which is the root module only in single-input mode. Keying the + // insert on an empty module key meant that in multi-input mode services + // never entered the pass at all, so a service clashing with a model in its + // own module generated uncompilable code without a diagnostic. + for service in services + .iter() + .filter(|service| in_scope(&service.module_key)) + { + top.insert( + language, + service.code_ident(language), + service.origin_label(), + )?; } // TypeScript `DEFAULT_` / `_CONST` constants and per-model // transfer type converters share the module scope; make them participate @@ -5897,6 +5935,7 @@ fn manifest_inputs_from_spec( .map(|service| ManifestService { name: service.name.clone(), code_name: service.code_name.for_language(language).map(str::to_string), + module_key: spec.module_path.as_module_key(), }) .collect(); (models, services) @@ -9110,6 +9149,58 @@ properties: parse_for(Language::Go, input).expect("override resolves the Go collision"); } + /// TypeScript binds a service to a lower-camel `const`, so a service and a + /// model of the same name emit `thing` and `Thing` and never collide. Deriving + /// the service identifier as a type name claimed they did, rejecting a schema + /// that generates cleanly — while missing the clash that can actually happen, + /// a service whose lower-camel form lands on a model's converter const. + #[test] + fn typescript_service_identifier_is_lower_camel() { + let service_and_model = r##" +$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Thing: + fqn: example.v1.Thing + operations: + doIt: + input: { $ref: "#/$defs/Thing" } +$defs: + Thing: + type: object + properties: + id: { type: string } +"##; + parse_for(Language::TypeScript, service_and_model) + .expect("`thing` and `Thing` are distinct TypeScript identifiers"); + // Python names the service class `Thing`, so there it is a real clash. + let error = reject_for(Language::Python, service_and_model); + assert!( + error.contains("collision") && error.contains("Thing"), + "{error}" + ); + + // The clash TypeScript does have: the service's lower-camel form is the + // model's converter identifier. + let converter_clash = r##" +$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + ThingTransferTypeConverter: + fqn: example.v1.Thing + operations: + doIt: + input: { $ref: "#/$defs/Thing" } +$defs: + Thing: + type: object + properties: + id: { type: string } +"##; + let error = reject_for(Language::TypeScript, converter_clash); + assert!(error.contains("thingTransferTypeConverter"), "{error}"); + } + /// A name synthesized *from a member* follows that member's override (P15). /// Two default-bearing members that recase alike collide on the TS /// `DEFAULT_` constant; the override has to reach the constant, or the diff --git a/src/planning/emitted_names.rs b/src/planning/emitted_names.rs index b5fe9791..a578f96e 100644 --- a/src/planning/emitted_names.rs +++ b/src/planning/emitted_names.rs @@ -269,6 +269,7 @@ fn manifest_services(language: Language, api_plan: &PlannedSpec) -> Vec Date: Fri, 14 Aug 2026 15:39:31 -0700 Subject: [PATCH 08/10] Scope TypeScript and Python collisions to the package barrel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TypeScript and Python had the same cross-module defect Go did, by a different route. Both emit a root barrel that lifts every module's top-level names into a single namespace — `index.ts` re-exports each module with `export *`, `__init__.py` re-exports them by name — so two input files each declaring a `Page` collide there, even though the two modules are separate scopes on their own. Left to the target, TypeScript emitted a barrel its own compiler rejects (TS2308, "Module './a' has already exported a member named 'Page'"), and the model's `pageTransferTypeConverter` collided alongside the interface. Python was worse: `from .a import Page` followed by `from .b import Page` raises nothing, binds the second, and lists the name once in `__all__`, so `from pkg import Page` quietly resolved to the wrong model — the silent incorrectness P7 exists to prevent. Both now reject at load with the same module-qualified diagnostic Go gives. Java and .NET keep per-module scoping. Each module lands in its own sub-package/namespace (`com.example.api.a.page`, `Nexgen.Generated.A.Page`) and neither emits an aggregating barrel, so two `Page` types stay distinct; this was checked against both generators, not assumed from the layout. The scope is now chosen by a documented predicate over the emitted layout rather than a Go special case, since the reason differs per target: Go flattens to one package, TS and Python re-aggregate through a barrel. Three specs asserted the superseded rule and are corrected: PRINCIPLES §15, generated-file-layout.md, and ref.md, which had claimed one package-wide namespace for every target. --- CHANGELOG.md | 18 +++++- specs/json-schema/PRINCIPLES.md | 2 +- specs/json-schema/features/ref.md | 5 +- specs/json-schema/generated-file-layout.md | 26 ++++++++- specs/json-schema/services.md | 7 ++- src/parser/json_schema.rs | 43 ++++++++++++-- tests/generate_go.rs | 15 +++-- tests/generate_python.rs | 65 ++++++++++++++++++++++ tests/generate_typescript.rs | 56 +++++++++++++++++++ 9 files changed, 216 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443fc589..784afe1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,9 +138,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 scope is the whole input closure; its diagnostic names the module each declaration came from (`a/page#Page` and `b/page#Page`), which it could not do while two same-named models in different modules produced identical origin text - and were mistaken for one declaration seen twice. The other three targets are - unchanged — separate modules keep the names apart, as - `generated-file-layout.md` already specified. + and were mistaken for one declaration seen twice. +- JSON Schema: TypeScript and Python had the same cross-module defect as Go, by a + different route: both emit a **root barrel** that lifts every module's top-level + names into one namespace — `index.ts` re-exporting each module with `export *`, + `__init__.py` re-exporting them by name — so two input files each declaring a + `Page` collide there. TypeScript emitted a barrel the compiler rejects (`TS2308`, + and the model's `pageTransferTypeConverter` collided alongside the interface), + while **Python emitted silently wrong code**: `from .a import Page` followed by + `from .b import Page` binds the second and drops the first off the package + surface, so `from pkg import Page` quietly resolved to the wrong model (P7). Both + now reject at load with the same module-qualified diagnostic Go gives, resolvable + with `x-ts-name` / `x-py-name`. Java and .NET are genuinely unaffected: each + module lands in its own sub-package/namespace and neither emits an aggregating + barrel, so two `Page` classes stay distinct — verified against both generators + rather than assumed. - JSON Schema: A TypeScript service identifier was derived as a *type* name in the collision pass while the generator emits a lower-camel `const`, so the pass rejected a service and a model of the same name — `chatService` and diff --git a/specs/json-schema/PRINCIPLES.md b/specs/json-schema/PRINCIPLES.md index 1dc391c1..11bb2928 100644 --- a/specs/json-schema/PRINCIPLES.md +++ b/specs/json-schema/PRINCIPLES.md @@ -50,7 +50,7 @@ renumber them. ### Output organization 14. **One module per input file; merge recursion.** Each input schema file maps to one generated module. Python, TypeScript, and Java mirror the input directory tree (Python subpackages, TS `index.ts` barrels, Java packages); **Go is the exception** — it collapses every input into a single flat package, because a Go package cannot participate in an import cycle and a cross-directory mutual reference would otherwise be an illegal cyclic import (with no cross-package hoist available). Cross-file reference *cycles* hoist only the cycle's strongly-connected types into one shared module (Python `_recursive.py`; Go resolves them within its flat package; TS/Java handle cycles natively). See [[ref]], [[generated-file-layout]]. -15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy), and which name it moves follows from where the synthesized name came from: **a name synthesized from a member moves with that member; a name synthesized from a position stays with the position.** So the override on the *declaring* property moves the member identifier and everything derived from it — the Go `OrDefault()` accessor and TS `DEFAULT_` constant ([[default]]), the closed-value type ([[const]]) — while a shape named after the position it was written in (an inline object hoisted to ``, see [[properties]]) keeps that name, and is renamed instead by authoring the shape in `$defs` and `$ref`ing it. A value constant is synthesized from the *value*, so it has its own `x--const-name` ([[const]]). Every escape hatch has to reach the name it is offered for: an override that moved the member but not a name derived from it would leave the collision unresolvable, which is the fix-it lying about the remedy. A **scope** is whatever unit the target actually resolves names in, which is not the same everywhere: for Python, TypeScript and Java it is the **module** the declaring file emits into, so two input files may each declare a `Page`; for **Go** it is the whole run, because every module flattens into one package ([[generated-file-layout]]) and two modules declaring `Page` are one redeclaration. A service is checked in the module that declares it, alongside that module's types. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. +15. **One identifier namespace per scope; synthesized-name collisions reject at load time — never silently mangled.** Beyond the declared properties and types, the generator *synthesizes* identifiers (type aliases, value constants, accessors). These do not live in a private namespace — each enters the same per-scope identifier set as the declared names and as each other. The generator runs **one collision pass** over that union (after case-mapping) and rejects any coincidence at load with a fix-it diagnostic. We **never auto-mangle**: an auto-suffixed name would be unstable under schema evolution (a P13 break) and is exactly the silently-incorrect output the mission rejects (P7/P7.1). The escape hatch is the per-language `x--name` override (resolved in the [[properties]] case-mapping policy), and which name it moves follows from where the synthesized name came from: **a name synthesized from a member moves with that member; a name synthesized from a position stays with the position.** So the override on the *declaring* property moves the member identifier and everything derived from it — the Go `OrDefault()` accessor and TS `DEFAULT_` constant ([[default]]), the closed-value type ([[const]]) — while a shape named after the position it was written in (an inline object hoisted to ``, see [[properties]]) keeps that name, and is renamed instead by authoring the shape in `$defs` and `$ref`ing it. A value constant is synthesized from the *value*, so it has its own `x--const-name` ([[const]]). Every escape hatch has to reach the name it is offered for: an override that moved the member but not a name derived from it would leave the collision unresolvable, which is the fix-it lying about the remedy. A **scope** is whatever unit the target actually resolves names in, which is a property of the emitted layout rather than of the schema, and is not the same everywhere. For **Go, TypeScript and Python** it is the **whole run**, so two input files may not each declare a `Page`: Go flattens every module into one package ([[generated-file-layout]]), making the second declaration a redeclaration; TypeScript and Python do emit a namespace per module, but each also emits a root barrel that lifts every module's top-level names into one namespace — `index.ts` re-exporting each module with `export *`, and `__init__.py` re-exporting them by name — so the second declaration collides there. TypeScript rejects the barrel outright, while Python silently binds whichever import runs last and drops the other model off the package surface, which is precisely the silent incorrectness P7 forbids. For **Java and .NET** the scope is the **module** the declaring file emits into: each module lands in its own sub-package/namespace and neither target emits an aggregating barrel, so two input files may each declare a `Page` and stay unambiguous. A service is checked in the module that declares it, alongside that module's types. Which identifiers are synthesized, and the per-language scopes they share, live in [[const]], [[default]], [[properties]]. ### Surface diff --git a/specs/json-schema/features/ref.md b/specs/json-schema/features/ref.md index 178b7624..9b1ddc6a 100644 --- a/specs/json-schema/features/ref.md +++ b/specs/json-schema/features/ref.md @@ -153,7 +153,10 @@ a reference from another input file names exactly the identifier the declaring file's own module emits — including its `x--name` override, which the referencing file does not restate. -**Collision.** All type names occupy **one package-wide namespace** +**Collision.** For Go, TypeScript and Python all type names occupy **one +package-wide namespace** — Go flattens to a single package, and the TypeScript +and Python barrels re-aggregate every module into one. Java and .NET resolve +per module instead, so there the namespace is the module ([[generated-file-layout]]). A collision → **load reject, no mangling**; the escape hatch is `x--name` / root `title` (**P15**, scope widened from per-object to per-package). Consistent with [[properties]], diff --git a/specs/json-schema/generated-file-layout.md b/specs/json-schema/generated-file-layout.md index 2da27c96..db5cf9cc 100644 --- a/specs/json-schema/generated-file-layout.md +++ b/specs/json-schema/generated-file-layout.md @@ -189,13 +189,15 @@ override or rename): - a **type name** declared by two different input files — `a/page.json` and `b/page.json` both emitting `Page` is one redeclaration in the flat package, so Go's collision scope is the whole run rather than the module - (see [[ref]] and PRINCIPLES §15); the diagnostic names both modules; + (see [[ref]] and PRINCIPLES §15); the diagnostic names both modules. + TypeScript and Python reject the same closure, by way of their barrels + rather than a flat package — see below; - a generated **service binding** colliding with a model (or synthesized I/O) type — service `ChatService` against a `$defs/ChatService`; see [[services]], which shares this one namespace. -**Python / TypeScript / Java** — nesting keeps distinct input files in -distinct modules, so files no longer contend for one flat name. What +**Python / TypeScript / Java / .NET** — nesting keeps distinct input files in +distinct modules, so files no longer contend for one flat *module* name. What remains is a small set of **reserved generated names** per scope; an input file or directory that maps onto one → load reject with the same fix-it: @@ -204,6 +206,24 @@ file or directory that maps onto one → load reject with the same fix-it: - within a **per-input directory**: `models`, `services`, and that directory's own aggregator. +Distinct modules do **not**, however, buy every target a distinct *type* +namespace, because two of them re-aggregate: + +- **TypeScript and Python** emit a root barrel that lifts every module's + top-level names into one namespace — `index.ts` re-exports each module with + `export *`, `__init__.py` re-exports them by name — so a type name declared + by two input files collides there and is rejected run-wide, exactly as in + Go's flat package. Left to the target, TypeScript rejects the barrel + (`TS2308`, "has already exported a member named `Page`") and Python silently + binds whichever import runs last, dropping the other model off the package + surface — the silent incorrectness P7 forbids. Names synthesized beside a + type travel with it, so TypeScript's `TransferTypeConverter` collides + in the same breath as the interface. +- **Java and .NET** give each module its own sub-package/namespace + (`com.example.api.content.page`, `Nexgen.Generated.Content.Page`) and emit + no aggregating barrel, so two input files may each declare a `Page` and stay + unambiguous. Their scope really is the module. + Within a single module's exported namespace the type/service/synthesized-name collision surface is unchanged (service `ChatService` in `services.py` against a `$defs/ChatService` in `models.py` share the per-input diff --git a/specs/json-schema/services.md b/specs/json-schema/services.md index edea7ca9..e6b34db0 100644 --- a/specs/json-schema/services.md +++ b/specs/json-schema/services.md @@ -469,9 +469,10 @@ the same identifier the type declaration uses, so an `x-ts-name` override moves the type and its converter together. Because it is derived and lower-camel-casing folds names the type namespace keeps apart (`HTTPError` and `HttpError` both yield `httpErrorTransferTypeConverter`), the converter -identifier also enters the module's identifier namespace for the -PRINCIPLES §15 collision pass: a fold rejects at load with a fix-it rather -than emitting one `export const` twice. Converters declared in another +identifier also enters TypeScript's identifier namespace for the +PRINCIPLES §15 collision pass — which the package barrel makes run-wide, so +the converter is checked against every module's, not just its own: a fold +rejects at load with a fix-it rather than emitting one `export const` twice. Converters declared in another input file's module import as **values** from that module (beside the type-only model import), following the same module resolution as any cross-module reference ([[ref]], [[generated-file-layout]]). diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index e1ff69cb..e240dd98 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -5781,11 +5781,23 @@ pub(crate) fn build_name_manifest( return Ok(manifest); } - // Each emitted scope gets its own top-level namespace. For most targets that - // is the module, which maps to one emitted file set. **Go is different**: it - // flattens every module into a single package, so two same-named types in - // different modules are redeclarations in one package — its scope is the whole - // closure, and `None` below means "every module at once". + // Each emitted scope gets its own top-level namespace. Which scope that is + // depends on how the target resolves a name across the emitted file set, so + // it is a property of the generator's layout rather than of the schema: + // + // - **Go, TypeScript, Python** resolve run-wide, so `None` below means "every + // module at once". Go flattens every module into a single package, so two + // same-named types in different modules are plain redeclarations. TS and + // Python do keep a namespace per module, but each emits a root barrel that + // re-exports every module's top-level names into one namespace — `index.ts` + // with `export *` per module, and `__init__.py` with named re-exports — so a + // name emitted twice collides there. TypeScript rejects the barrel (TS2308, + // "has already exported a member named ..."); Python silently binds whichever + // import runs last, which is exactly the silent incorrectness P7 forbids. + // - **Java and .NET** resolve per module: each module lands in its own + // sub-package/namespace (`com.example.api.content.page`, + // `Nexgen.Generated.Content.Page`) and neither emits an aggregating barrel, + // so the same type name in two modules is two distinct qualified names. // // A module with services but no models still has a scope, so its service // identifiers are checked against the boilerplate. @@ -5794,7 +5806,7 @@ pub(crate) fn build_name_manifest( .map(|model| model.module_key.clone()) .chain(services.iter().map(|service| service.module_key.clone())) .collect(); - let scopes: Vec> = if language == Language::Go { + let scopes: Vec> = if scope_is_run_wide(language) { vec![None] } else { module_keys.into_iter().map(Some).collect() @@ -5905,6 +5917,25 @@ fn boilerplate_idents(language: Language) -> &'static [&'static str] { } } +/// Whether `language` resolves top-level names across the whole run rather than +/// per module — that is, whether two modules may each declare the same name. +/// +/// This is a property of the emitted layout, not of the schema: +/// +/// - Go flattens every module into one package, so a name emitted twice is a +/// redeclaration in that package. +/// - TypeScript and Python do emit a namespace per module, but both also emit a +/// root barrel (`index.ts` / `__init__.py`) that lifts every module's top-level +/// names into a single namespace, so a name emitted twice collides there. +/// - Java and .NET give each module its own sub-package/namespace and emit no +/// aggregating barrel, so the same name in two modules stays unambiguous. +const fn scope_is_run_wide(language: Language) -> bool { + match language { + Language::Go | Language::TypeScript | Language::Python => true, + Language::Java | Language::Dotnet | Language::Ruby => false, + } +} + /// Adapts an authored [`ApiSpec`] into [`build_name_manifest`] inputs for /// `language` (which selects each service's per-language `code_name` override). fn manifest_inputs_from_spec( diff --git a/tests/generate_go.rs b/tests/generate_go.rs index 4b45680b..4578203c 100644 --- a/tests/generate_go.rs +++ b/tests/generate_go.rs @@ -2032,6 +2032,11 @@ services: operations: one: input: { $ref: "a/page.json" } + output: + type: object + additionalProperties: false + properties: { ok: { type: boolean } } + required: [ok] two: input: { $ref: "b/page.json" } "##, @@ -2058,18 +2063,20 @@ services: assert!(error.contains("b/page#Page"), "{error}"); // The same closure is fine in a language whose modules are separate scopes. + // Java gives each module its own sub-package (`…pkg.a.page`, `…pkg.b.page`) + // and emits no aggregating barrel, so the two `Page` classes stay distinct. generate_to_file(&GenerateRequest { - language: nexgen::language::Language::Python, + language: nexgen::language::Language::Java, input_paths: vec![input_dir], support_paths: Vec::new(), descriptor_paths: Vec::new(), - output_path: temp_dir.join("out-py"), + output_path: temp_dir.join("pkg"), format: false, generate_native_api: false, - java_package_name: None, + java_package_name: Some("com.example.pkg".to_string()), ts_date_time_types: Default::default(), }) - .expect("separate Python modules keep `Page` apart"); + .expect("separate Java packages keep `Page` apart"); fs::remove_dir_all(temp_dir).unwrap(); } diff --git a/tests/generate_python.rs b/tests/generate_python.rs index 05c6c2eb..f035449d 100644 --- a/tests/generate_python.rs +++ b/tests/generate_python.rs @@ -1076,3 +1076,68 @@ fn python_json_cross_module_py_name_override_moves_every_reference() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// The package barrel (`__init__.py`) re-exports every module by name, so two +/// modules declaring the same type name produce `from .a import Page` followed by +/// `from .b import Page`. Python raises nothing: the second binding silently wins +/// and `__all__` lists the name once, so `from pkg import Page` quietly resolves to +/// the wrong model. That silent incorrectness is what P7 forbids, so the generator +/// rejects at load. See `specs/json-schema/PRINCIPLES.md` §15. +#[test] +fn python_json_rejects_same_type_name_in_two_modules() { + let temp_dir = unique_output_path("py-json-barrel-collision"); + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::create_dir_all(input_dir.join("b")).unwrap(); + fs::write( + input_dir.join("a/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}}}"#, + ) + .unwrap(); + fs::write( + input_dir.join("b/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"count":{"type":"integer"}}}"#, + ) + .unwrap(); + + let request = |output: &str| GenerateRequest { + language: nexgen::language::Language::Python, + input_paths: vec![input_dir.clone()], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: temp_dir.join(output), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }; + + let error = generate_to_file(&request("out")) + .expect_err("two modules declaring `Page` collide in the package barrel") + .to_string(); + // The diagnostic names both modules — the bare type name appears twice and + // would otherwise read as one declaration seen twice. + assert!(error.contains("collision"), "{error}"); + assert!(error.contains("a/page#Page"), "{error}"); + assert!(error.contains("b/page#Page"), "{error}"); + assert!(error.contains("x-py-name"), "{error}"); + + // The documented escape hatch resolves it, and the barrel then re-exports both. + fs::write( + input_dir.join("b/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","x-py-name":"BPage","additionalProperties":false,"properties":{"count":{"type":"integer"}}}"#, + ) + .unwrap(); + let output_path = temp_dir.join("out-renamed"); + generate_to_file(&request("out-renamed")).expect("the override resolves the collision"); + let barrel = fs::read_to_string(output_path.join("__init__.py")).unwrap(); + for expected in [ + "from .a import Page", + "from .b import BPage", + "\"BPage\",", + "\"Page\",", + ] { + assert!(barrel.contains(expected), "{expected}\n{barrel}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_typescript.rs b/tests/generate_typescript.rs index 50527d21..847d752c 100644 --- a/tests/generate_typescript.rs +++ b/tests/generate_typescript.rs @@ -1187,3 +1187,59 @@ fn typescript_json_override_moves_member_derived_names_only() { assert!(!rendered.contains("ProbeLocation")); fs::remove_dir_all(temp_dir).unwrap(); } + +/// The package barrel (`index.ts`) re-exports every module with `export *`, so two +/// modules declaring the same type name land in one namespace there. TypeScript +/// rejects that barrel outright (TS2308, "has already exported a member named +/// `Page`"), and the model's `TransferTypeConverter` collides alongside the +/// type — so the generator must reject at load instead of emitting uncompilable +/// output. See `specs/json-schema/PRINCIPLES.md` §15. +#[test] +fn typescript_json_rejects_same_type_name_in_two_modules() { + let temp_dir = unique_output_path("ts-json-barrel-collision"); + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::create_dir_all(input_dir.join("b")).unwrap(); + let page = r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}}}"#; + fs::write(input_dir.join("a/page.json"), page).unwrap(); + fs::write(input_dir.join("b/page.json"), page).unwrap(); + + let request = |output: &str| GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_dir.clone()], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: temp_dir.join(output), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }; + + let error = generate_to_file(&request("out")) + .expect_err("two modules declaring `Page` collide in the package barrel") + .to_string(); + // The diagnostic names both modules — the bare type name appears twice and + // would otherwise read as one declaration seen twice. + assert!(error.contains("collision"), "{error}"); + assert!(error.contains("a/page#Page"), "{error}"); + assert!(error.contains("b/page#Page"), "{error}"); + assert!(error.contains("x-ts-name"), "{error}"); + + // The documented escape hatch resolves it, and moves the converter with the + // type so the barrel re-exports two distinct pairs. + fs::write( + input_dir.join("b/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","x-ts-name":"BPage","additionalProperties":false,"properties":{"title":{"type":"string"}}}"#, + ) + .unwrap(); + let output_path = temp_dir.join("out-renamed"); + generate_to_file(&request("out-renamed")).expect("the override resolves the collision"); + let models = fs::read_to_string(output_path.join("b/page/models.ts")).unwrap(); + assert!(models.contains("export interface BPage {"), "{models}"); + assert!( + models.contains("export const bPageTransferTypeConverter"), + "{models}" + ); + fs::remove_dir_all(temp_dir).unwrap(); +} From a2f069e4272fd5147357c626a8243fa647eb8f23 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 14 Aug 2026 16:01:16 -0700 Subject: [PATCH 09/10] Emit a $ref'd type from its declaring module only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A module re-emitted the types it only `$ref`d from another input file, instead of importing them, whenever that module declared no types of its own — a service file whose every operation type is a `$ref` elsewhere. Every target produced a second copy. Go put two `type Page struct` in its flat package (`Page redeclared in this block`, from the Go compiler); Java wrote `Page.java` twice and failed the build with a generated-file conflict; TypeScript emitted a module that both imports and redeclares the name (TS2440) and a barrel that re-exports it twice (TS2308); Python emitted two classes and a barrel importing both, silently binding one and dropping the other off the package surface, which is the P7 case. The cause was in reachability pruning, which decides whether to drop declarations another module owns. It inferred "this front end does not scope declarations by module" from the module owning nothing — a state indistinguishable from "this module declares nothing" while the flag is a bool, so a service-only module looked like a WIT spec and kept everything it could reach. A declaration now records `Foreign` distinctly from `Unscoped`, which is the distinction the prune was actually reaching for. Dropping the re-emitted copies leaves a module that declares nothing with an empty models file, so TypeScript no longer emits one, and its barrel no longer re-exports `./models`: re-exporting a file with no exports is itself an error (TS2306). The checked-in samples do not change, because every sample service module happens to declare at least one inline operation type — which is why this went unnoticed. Each new test was confirmed to fail without the fix, and the repaired output was checked with the Go compiler, javac, tsc, and a Python import rather than by inspection. --- CHANGELOG.md | 15 ++++ specs/json-schema/generated-file-layout.md | 11 +++ src/generator/python.rs | 2 +- src/generator/typescript.rs | 56 +++++++++---- src/parser/json_schema.rs | 8 +- src/parser/wit.rs | 11 ++- src/planning/reachability.rs | 16 +++- src/spec.rs | 47 ++++++++++- tests/generate_go.rs | 56 +++++++++++++ tests/generate_java.rs | 57 +++++++++++++ tests/generate_python.rs | 59 +++++++++++++ tests/generate_typescript.rs | 96 ++++++++++++++++++++++ 12 files changed, 403 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784afe1e..47b8a018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- JSON Schema: A module re-emitted the types it only `$ref`d from **another input + file**, instead of importing them, whenever that module declared no types of its + own — a service file whose every operation type is a `$ref` elsewhere. Every + target emitted a second copy of each referenced type: `Page redeclared in this + block` in Go's flat package, a duplicate `Page.java` that failed the build with a + generated-file conflict, a TypeScript module that both imported and redeclared + the name (`TS2440`), and in Python a barrel importing two copies and silently + binding one (P7). The cause was reachability pruning inferring "this front end + does not scope declarations by module" from a module owning nothing, which is + indistinguishable from "this module declares nothing" while the flag is a + `bool`; declarations now record *foreign* distinctly from *unscoped*. A module + that declares nothing now emits no models file, and its TypeScript barrel stops + re-exporting `./models` — re-exporting a file with no exports is itself an error + (`TS2306`). The checked-in samples were unaffected, because every sample service + module happens to declare at least one inline operation type. - JSON Schema: The P15 collision pass used the wrong scope in two ways, so **multi-input** runs accepted schemas that generate uncompilable code. Services were only entered into the namespace of the root module, which in multi-input diff --git a/specs/json-schema/generated-file-layout.md b/specs/json-schema/generated-file-layout.md index db5cf9cc..e86bd28c 100644 --- a/specs/json-schema/generated-file-layout.md +++ b/specs/json-schema/generated-file-layout.md @@ -83,6 +83,17 @@ Per input file `/`: | **Go** | `.go` in the one flat package (`` = flattened path) | `definitions.go` (same package) | — | — (capitalized = exported) | | **Java** | one `.java` per exported class, in a package mirroring `//` | each runtime class its own file in the root package (`ValidationException.java`, `Violation.java`, `SpecNumbers.java`, …) | — | — (`public` = exported) | +**A module emits only the types its own input file declares.** A type reached by +`$ref` into another file belongs to the module that declares it, and is imported +from there — never re-emitted into the referencing module, which would put a +second copy of the type in the package (a redeclaration in Go's flat package, a +duplicate class file in Java, an import-versus-local-declaration conflict in +TypeScript, and a shadowed re-import in Python). A service file whose every +operation type is `$ref`d from elsewhere therefore declares nothing of its own, +and emits no models file at all — for TypeScript that also means its `index.ts` +does not re-export `./models`, since a barrel re-exporting a file with no exports +is itself an error (`TS2306`). + **`_recursive` is Python-only and is a single file at the package root** (`/_recursive.py`), **never** per-input. It holds every hoisted cross-file SCC in the whole closure. See Recursion below. diff --git a/src/generator/python.rs b/src/generator/python.rs index 375b9975..a668beae 100644 --- a/src/generator/python.rs +++ b/src/generator/python.rs @@ -305,7 +305,7 @@ fn leaf_export_names( fn planned_module_export_model_names(plan: &PlannedSpec) -> BTreeSet { plan.types .values() - .filter(|entry| entry.module_exported) + .filter(|entry| entry.is_module_export()) .filter_map(|entry| match &entry.declaration { TypeDeclSpec::Record(record) => Some(record.name.clone()), TypeDeclSpec::Enum(enumeration) => Some(enumeration.name.clone()), diff --git a/src/generator/typescript.rs b/src/generator/typescript.rs index 68e107df..39f3c3ec 100644 --- a/src/generator/typescript.rs +++ b/src/generator/typescript.rs @@ -3056,29 +3056,38 @@ fn render_module_files( let json_runtime_files = external_models.render_support_files()?; let has_json_runtime_module = json_runtime_files.contains_key(&PathBuf::from("definitions.ts")); let mut files = BTreeMap::::new(); + let models_source = render_models_module( + enums, + flags, + variants, + models, + external_models, + model_fragments, + language_imports, + support_exports.as_ref(), + api_plan, + mode, + ); + // A module whose every operation type is `$ref`d from another file declares + // nothing of its own. Emitting the empty `models.ts` anyway would leave the + // barrel re-exporting a file with no exports, which TypeScript rejects + // outright (TS2306, "is not a module"). + let has_models_module = !is_blank_generated_module(&models_source); files.insert( "index.ts".into(), if mode == GenerationMode::NativeApi { render_index_module(services, &model_fragments.type_exported_names) } else { - render_definitions_only_index_module(services, has_json_runtime_module) + render_definitions_only_index_module( + services, + has_json_runtime_module, + has_models_module, + ) }, ); - files.insert( - "models.ts".into(), - render_models_module( - enums, - flags, - variants, - models, - external_models, - model_fragments, - language_imports, - support_exports.as_ref(), - api_plan, - mode, - ), - ); + if has_models_module { + files.insert("models.ts".into(), models_source); + } if !services.is_empty() { files.insert( "services.ts".into(), @@ -3390,9 +3399,20 @@ fn render_generated_module(imports: String, body: String) -> String { output } +/// Whether a rendered module carries nothing but the generated header, so +/// emitting it would produce a file with no exports. +fn is_blank_generated_module(source: &str) -> bool { + source + .strip_prefix(GENERATED_HEADER) + .unwrap_or(source) + .trim() + .is_empty() +} + fn render_definitions_only_index_module( services: &[RenderedService<'_>], has_json_runtime_module: bool, + has_models_module: bool, ) -> String { let mut output = String::new(); output.push_str(GENERATED_HEADER); @@ -3400,7 +3420,9 @@ fn render_definitions_only_index_module( if !services.is_empty() { output.push_str("export * from './services';\n"); } - output.push_str("export * from './models';\n"); + if has_models_module { + output.push_str("export * from './models';\n"); + } if services.iter().any(|service| !service.resources.is_empty()) { output.push_str("export * from './resources';\n"); } diff --git a/src/parser/json_schema.rs b/src/parser/json_schema.rs index e240dd98..4d140f8c 100644 --- a/src/parser/json_schema.rs +++ b/src/parser/json_schema.rs @@ -634,7 +634,11 @@ fn api_spec_from_parsed_json_documents( if module_exported { TypeDeclEntry::module_export(declaration) } else { - TypeDeclEntry::new(declaration) + // Declared by another input file. Marking it foreign rather + // than merely "not exported" is what lets a service file that + // declares no types of its own still import these instead of + // re-emitting them into its own module. + TypeDeclEntry::foreign(declaration) }, ) }) @@ -6293,7 +6297,7 @@ $defs: value: { type: string } "##, ); - assert!(spec.types.values().all(|entry| entry.module_exported)); + assert!(spec.types.values().all(|entry| entry.is_module_export())); assert_eq!(spec.types.len(), 2); } diff --git a/src/parser/wit.rs b/src/parser/wit.rs index bd92d90e..8bc5975f 100644 --- a/src/parser/wit.rs +++ b/src/parser/wit.rs @@ -103,7 +103,7 @@ fn api_spec_from_wit( for type_id in interface.types.values() { let full_name = wit_type_full_name(resolve, *type_id); if let Some(entry) = types.get_mut(&full_name) { - entry.module_exported = true; + entry.module_export = crate::spec::ModuleExport::Owned; } } } @@ -3580,7 +3580,7 @@ interface types { assert_eq!(variant.cases[0].wire_name, "some_value"); assert_eq!(variant.cases[1].name, "type"); assert_eq!(variant.cases[1].wire_name, "type"); - assert!(spec.types["types.choice"].module_exported); + assert!(spec.types["types.choice"].is_module_export()); let service = parse( Language::Python, @@ -3593,7 +3593,12 @@ interface api { } "#, ); - assert!(service.types.values().all(|entry| !entry.module_exported)); + assert!( + service + .types + .values() + .all(|entry| !entry.is_module_export()) + ); } const GENERIC_WIT: &str = r#" diff --git a/src/planning/reachability.rs b/src/planning/reachability.rs index 3a443950..06d5dd22 100644 --- a/src/planning/reachability.rs +++ b/src/planning/reachability.rs @@ -5,6 +5,7 @@ //! a side table produced by a previous pass. use super::*; +use crate::spec::ModuleExport; pub(crate) struct ReachabilityPass; @@ -31,10 +32,17 @@ fn prune(spec: &mut PlannedSpec) { let mut reachable = spec .types .iter() - .filter(|(_, entry)| entry.module_exported) + .filter(|(_, entry)| entry.is_module_export()) .map(|(name, _)| name.clone()) .collect::>(); - let has_module_exports = !reachable.is_empty(); + // Whether this spec's front end assigns declarations to modules at all. A + // module that owns nothing still counts as scoped when it carries foreign + // declarations, which is how a service file whose every operation type is + // `$ref`d from another file avoids re-emitting all of them. + let module_scoped = spec + .types + .values() + .any(|entry| entry.module_export != ModuleExport::Unscoped); for service in &spec.services { for operation in &service.operations { pending.extend( @@ -72,8 +80,8 @@ fn prune(spec: &mut PlannedSpec) { spec.types.retain(|name, entry| { reachable.contains(name) && (!matches!(entry.declaration, TypeDeclSpec::External(_)) - || !has_module_exports - || entry.module_exported) + || !module_scoped + || entry.is_module_export()) }); } diff --git a/src/spec.rs b/src/spec.rs index 0c4cf9e1..16667322 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -415,28 +415,67 @@ where } } +/// How a declaration relates to the module of the spec that carries it. +/// +/// The distinction that matters is between a front end that assigns modules at +/// all and one that does not: a spec whose declarations are all [`Unscoped`] +/// emits everything it can reach, while a spec that participates in module +/// scoping emits only what it [`Owned`]s. Collapsing these two into "has no +/// module exports" would make a module that declares nothing — a JSON service +/// file whose every operation type is `$ref`d from elsewhere — look like a front +/// end that does not scope, and re-emit every referenced type into it. +/// +/// [`Unscoped`]: ModuleExport::Unscoped +/// [`Owned`]: ModuleExport::Owned +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) enum ModuleExport { + /// The front end does not scope declarations by module (WIT), so the one + /// module emits every declaration it references. + #[default] + Unscoped, + /// This module declares the type, and is the module that emits it. + Owned, + /// Another module declares the type; this spec only references it, and must + /// import rather than re-emit it. + Foreign, +} + #[derive(Debug, Clone, PartialEq)] pub struct TypeDeclEntry { pub declaration: TypeDeclSpec, /// Whether this declaration is a public root of its containing module. - pub(crate) module_exported: bool, + pub(crate) module_export: ModuleExport, } impl TypeDeclEntry { pub fn new(declaration: TypeDeclSpec) -> Self { Self { declaration, - module_exported: false, + module_export: ModuleExport::Unscoped, } } pub(crate) fn module_export(declaration: TypeDeclSpec) -> Self { Self { declaration, - module_exported: true, + module_export: ModuleExport::Owned, } } + /// A declaration this spec references but another module emits. + pub(crate) fn foreign(declaration: TypeDeclSpec) -> Self { + Self { + declaration, + module_export: ModuleExport::Foreign, + } + } + + /// Whether this module declares the type (as opposed to importing it, or + /// belonging to a front end that does not scope by module). + pub(crate) fn is_module_export(&self) -> bool { + self.module_export == ModuleExport::Owned + } + fn map_names_with(self, map: &mut M) -> TypeDeclEntry where G: TypeFamily, @@ -444,7 +483,7 @@ impl TypeDeclEntry { { TypeDeclEntry { declaration: self.declaration.map_names_with(map), - module_exported: self.module_exported, + module_export: self.module_export, } } } diff --git a/tests/generate_go.rs b/tests/generate_go.rs index 4578203c..eb0c73c4 100644 --- a/tests/generate_go.rs +++ b/tests/generate_go.rs @@ -2139,3 +2139,59 @@ $defs: ); fs::remove_dir_all(temp_dir).unwrap(); } + +/// Go flattens every module into one package, so re-emitting a `$ref`d type into +/// the referencing service's module put two `type Page struct` in that package — +/// `Page redeclared in this block`, confirmed with the Go compiler. It happened +/// whenever the service module declared no types of its own, because reachability +/// pruning read "this module owns nothing" as "this front end does not scope by +/// module" and kept every referenced declaration. +#[test] +fn go_json_service_module_without_own_types_does_not_redeclare_refs() { + let temp_dir = unique_output_path("go-json-service-only-module"); + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::write( + input_dir.join("a/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}},"required":["title"]}"#, + ) + .unwrap(); + fs::write( + input_dir.join("svc.nexusrpc.yaml"), + r#"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Svc: + fqn: example.v1.Svc + operations: + one: + input: { $ref: "a/page.json" } +"#, + ) + .unwrap(); + + let output_path = temp_dir.join("out"); + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Go, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let rendered = read_go_output_files(&output_path) + .into_values() + .collect::>() + .join("\n"); + assert_eq!( + rendered.matches("type Page struct {").count(), + 1, + "`Page` must be declared once in the flat package\n{rendered}" + ); + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_java.rs b/tests/generate_java.rs index 05dfdd1a..ccbc2345 100644 --- a/tests/generate_java.rs +++ b/tests/generate_java.rs @@ -487,3 +487,60 @@ fn java_json_cross_module_java_name_override_moves_every_reference() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// Java gives each module its own package and one file per class, so re-emitting +/// a `$ref`d type into the referencing service's module wrote `Page.java` twice +/// and the build failed outright with a generated-file conflict. It happened +/// whenever the service module declared no types of its own. +#[test] +fn java_json_service_module_without_own_types_does_not_reemit_refs() { + let temp_dir = unique_output_path("java-json-service-only-module"); + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::write( + input_dir.join("a/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}},"required":["title"]}"#, + ) + .unwrap(); + fs::write( + input_dir.join("svc.nexusrpc.yaml"), + r#"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Svc: + fqn: example.v1.Svc + operations: + one: + input: { $ref: "a/page.json" } +"#, + ) + .unwrap(); + + let output_path = temp_dir.join("pkg"); + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Java, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: Some("com.example.pkg".to_string()), + ts_date_time_types: Default::default(), + }) + .unwrap(); + + // `Page` is emitted once, by the module that declares it. + let paths = read_java_files(&output_path) + .into_keys() + .filter(|path| path.file_name().is_some_and(|name| name == "Page.java")) + .collect::>(); + assert_eq!(paths, vec![PathBuf::from("a/page/Page.java")]); + // The service's own package holds only the service. + let service = fs::read_to_string(output_path.join("svc/Svc.java")).unwrap(); + assert!( + service.contains("import com.example.pkg.a.page.Page;"), + "{service}" + ); + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_python.rs b/tests/generate_python.rs index f035449d..e57e7762 100644 --- a/tests/generate_python.rs +++ b/tests/generate_python.rs @@ -1141,3 +1141,62 @@ fn python_json_rejects_same_type_name_in_two_modules() { } fs::remove_dir_all(temp_dir).unwrap(); } + +/// Re-emitting a `$ref`d type into the referencing service's module produced two +/// `class Page` in different modules, which the package barrel then imported +/// twice — `from .a import Page` followed by `from .svc import Page`, silently +/// binding one copy and dropping the other (P7). It happened whenever the service +/// module declared no types of its own, because reachability pruning read "this +/// module owns nothing" as "this front end does not scope by module". +#[test] +fn python_json_service_module_without_own_types_does_not_reemit_refs() { + let temp_dir = unique_output_path("py-json-service-only-module"); + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::write( + input_dir.join("a/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}},"required":["title"]}"#, + ) + .unwrap(); + fs::write( + input_dir.join("svc.nexusrpc.yaml"), + r#"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Svc: + fqn: example.v1.Svc + operations: + one: + input: { $ref: "a/page.json" } +"#, + ) + .unwrap(); + + let output_path = temp_dir.join("out"); + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::Python, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + let rendered = read_python_package_files(&output_path) + .into_values() + .collect::>() + .join("\n"); + assert_eq!( + rendered.matches("class Page(").count(), + 1, + "`Page` must be declared once\n{rendered}" + ); + // The root barrel binds each name exactly once. + let barrel = fs::read_to_string(output_path.join("__init__.py")).unwrap(); + assert_eq!(barrel.matches("import Page").count(), 1, "{barrel}"); + fs::remove_dir_all(temp_dir).unwrap(); +} diff --git a/tests/generate_typescript.rs b/tests/generate_typescript.rs index 847d752c..b352e4d9 100644 --- a/tests/generate_typescript.rs +++ b/tests/generate_typescript.rs @@ -1243,3 +1243,99 @@ fn typescript_json_rejects_same_type_name_in_two_modules() { ); fs::remove_dir_all(temp_dir).unwrap(); } + +/// Writes a closure whose service module declares no types of its own: both +/// operation types are `$ref`s into sibling files. Returns the input directory. +fn write_service_only_module_closure(temp_dir: &Path) -> PathBuf { + let input_dir = temp_dir.join("input"); + fs::create_dir_all(input_dir.join("a")).unwrap(); + fs::create_dir_all(input_dir.join("b")).unwrap(); + fs::write( + input_dir.join("a/page.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"title":{"type":"string"}},"required":["title"]}"#, + ) + .unwrap(); + fs::write( + input_dir.join("b/note.json"), + r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","additionalProperties":false,"properties":{"body":{"type":"string"}},"required":["body"]}"#, + ) + .unwrap(); + fs::write( + input_dir.join("svc.nexusrpc.yaml"), + r#"$schema: https://json-schema.org/draft/2020-12/schema +nexusrpc: "1.0.0" +services: + Svc: + fqn: example.v1.Svc + operations: + one: + input: { $ref: "a/page.json" } + output: { $ref: "b/note.json" } +"#, + ) + .unwrap(); + input_dir +} + +/// A module emits the types it declares; a type it only `$ref`s belongs to the +/// module that declares it. Reachability pruning inferred "this front end does +/// not scope by module" from a module owning nothing, so a service file whose +/// every operation type is `$ref`d from elsewhere re-emitted all of them into +/// its own module — a second copy of each interface and converter, which +/// TypeScript rejects (`TS2440`, import conflicts with local declaration) and +/// which the package barrel then re-exports twice (`TS2308`). +#[test] +fn typescript_json_service_module_without_own_types_imports_instead_of_reemitting() { + let temp_dir = unique_output_path("ts-json-service-only-module"); + let input_dir = write_service_only_module_closure(&temp_dir); + let output_path = temp_dir.join("out"); + generate_to_file(&GenerateRequest { + language: nexgen::language::Language::TypeScript, + input_paths: vec![input_dir], + support_paths: Vec::new(), + descriptor_paths: Vec::new(), + output_path: output_path.clone(), + format: false, + generate_native_api: false, + java_package_name: None, + ts_date_time_types: Default::default(), + }) + .unwrap(); + + // Each type and its converter are declared exactly once, in the module that + // declares the schema. + let files = read_typescript_output_files(&output_path) + .into_values() + .collect::>() + .join("\n"); + for declaration in [ + "export interface Page {", + "export interface Note {", + "export const pageTransferTypeConverter", + "export const noteTransferTypeConverter", + ] { + assert_eq!( + files.matches(declaration).count(), + 1, + "expected exactly one `{declaration}`\n{files}" + ); + } + + // The service module declares nothing, so it emits no `models.ts` at all — + // an empty one would leave its barrel re-exporting a file with no exports, + // which TypeScript rejects (`TS2306`, "is not a module"). + assert!(!output_path.join("svc/models.ts").exists()); + let module_index = fs::read_to_string(output_path.join("svc/index.ts")).unwrap(); + assert!(module_index.contains("export * from './services';")); + assert!(!module_index.contains("./models"), "{module_index}"); + + // It imports the types from the modules that own them. + let services = fs::read_to_string(output_path.join("svc/services.ts")).unwrap(); + for expected in [ + "import { pageTransferTypeConverter } from '../a/page/models';", + "import { noteTransferTypeConverter } from '../b/note/models';", + ] { + assert!(services.contains(expected), "{expected}\n{services}"); + } + fs::remove_dir_all(temp_dir).unwrap(); +} From 7c5984c4639039ec6162e9c5da7f3d9889cd297d Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 21 Aug 2026 09:29:27 -0700 Subject: [PATCH 10/10] Compact changelog and clarify module exports --- CHANGELOG.md | 135 ++++++--------------------------------------------- src/spec.rs | 29 ++++++----- 2 files changed, 32 insertions(+), 132 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b8a018..5be2a557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,29 +54,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 — the one sibling keyword treated this way, because it asserts nothing about the value, and the only way to rename a member whose type is a `$ref` (a member named `class` was otherwise unfixable in Python and Java). -- TypeScript: A model's companion converter is now an exported - `TransferTypeConverter` **instance** instead of a class. `class Mapper` - with `fromIntermediate`/`toIntermediate` becomes - `export const TransferTypeConverter = new class implements - TransferTypeConverter { … }()` with `fromTransferType`/`toTransferType`, - implementing the contract from - [nexus-rpc/sdk-typescript#40](https://github.com/nexus-rpc/sdk-typescript/pull/40). - Call sites drop the construction: `new UserMapper().fromIntermediate(raw)` - becomes `userTransferTypeConverter.fromTransferType(raw)`. The converter - identifier is the model's resolved type name lower-camel-cased, so an - `x-ts-name` override moves it. `models.ts` now carries a type-only - `import type { TransferTypeConverter } from "nexus-rpc"`. Because the - identifier is derived by lower-camel-casing, it takes part in the load-time - identifier-collision check: two models in one module whose converter names - coincide (for example `HTTPError` and `HttpError`) are now rejected with a - fix-it, as is a service whose `x-ts-name` lands on a converter name. -- TypeScript: Generated operations now carry operation type info. Each - non-void side of `nexus.operation` emits - `inputType`/`outputType` = `{ transferTypeConverter: … }` naming the I/O - model's converter, so a protocol integration can apply the conversion without - the caller wiring it by hand; a void side carries neither field. Applies to - JSON-Schema input only — WIT-input operations are unchanged. Requires a - `nexus-rpc` release that includes the type-info API. +- TypeScript: JSON models now export companion `TransferTypeConverter` + instances with `fromTransferType`/`toTransferType`, replacing the previous + mapper classes and intermediate-value terminology. +- TypeScript: JSON Schema operations now attach their model converters as + `inputType`/`outputType` metadata. WIT-generated operations are unchanged. - Generating into an existing `--output` directory no longer deletes it first. The directory is written into instead, so pre-existing files and subdirectories are preserved; generated files are still overwritten in place. @@ -126,95 +108,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- JSON Schema: A module re-emitted the types it only `$ref`d from **another input - file**, instead of importing them, whenever that module declared no types of its - own — a service file whose every operation type is a `$ref` elsewhere. Every - target emitted a second copy of each referenced type: `Page redeclared in this - block` in Go's flat package, a duplicate `Page.java` that failed the build with a - generated-file conflict, a TypeScript module that both imported and redeclared - the name (`TS2440`), and in Python a barrel importing two copies and silently - binding one (P7). The cause was reachability pruning inferring "this front end - does not scope declarations by module" from a module owning nothing, which is - indistinguishable from "this module declares nothing" while the flag is a - `bool`; declarations now record *foreign* distinctly from *unscoped*. A module - that declares nothing now emits no models file, and its TypeScript barrel stops - re-exporting `./models` — re-exporting a file with no exports is itself an error - (`TS2306`). The checked-in samples were unaffected, because every sample service - module happens to declare at least one inline operation type. -- JSON Schema: The P15 collision pass used the wrong scope in two ways, so - **multi-input** runs accepted schemas that generate uncompilable code. Services - were only entered into the namespace of the root module, which in multi-input - mode is no module at all — a service clashing with a model in its own file - rejected when that file was the sole input and was silently accepted when it sat - in a directory. And every target was scoped per module, but **Go flattens every - module into one package**: two input files each declaring a `Page` emitted the - type twice into one package (`Page redeclared in this block`) with no - diagnostic. Services now enter the namespace of their declaring module, and Go's - scope is the whole input closure; its diagnostic names the module each - declaration came from (`a/page#Page` and `b/page#Page`), which it could not do - while two same-named models in different modules produced identical origin text - and were mistaken for one declaration seen twice. -- JSON Schema: TypeScript and Python had the same cross-module defect as Go, by a - different route: both emit a **root barrel** that lifts every module's top-level - names into one namespace — `index.ts` re-exporting each module with `export *`, - `__init__.py` re-exporting them by name — so two input files each declaring a - `Page` collide there. TypeScript emitted a barrel the compiler rejects (`TS2308`, - and the model's `pageTransferTypeConverter` collided alongside the interface), - while **Python emitted silently wrong code**: `from .a import Page` followed by - `from .b import Page` binds the second and drops the first off the package - surface, so `from pkg import Page` quietly resolved to the wrong model (P7). Both - now reject at load with the same module-qualified diagnostic Go gives, resolvable - with `x-ts-name` / `x-py-name`. Java and .NET are genuinely unaffected: each - module lands in its own sub-package/namespace and neither emits an aggregating - barrel, so two `Page` classes stay distinct — verified against both generators - rather than assumed. -- JSON Schema: A TypeScript service identifier was derived as a *type* name in the - collision pass while the generator emits a lower-camel `const`, so the pass - rejected a service and a model of the same name — `chatService` and - `ChatService` are distinct TypeScript identifiers and generate cleanly — and - missed the clash TypeScript really has, a service whose lower-camel form lands - on a model's `TransferTypeConverter`. The identifier is now derived the - way it is emitted. Only the collision pass is affected; no emitted name changes. -- JSON Schema: An `x--name` override on a property did not move two of the - identifiers **synthesized from that property**, so a collision on either was - rejected with a fix-it the author could not act on. The TypeScript - `DEFAULT_` constant and the Go closed-value type `` (with - its value constants) were derived from the JSON key rather than the emitted - member identifier: two default-bearing members that recase alike - (`retryCount` + `retry_count`) collided on `DEFAULT_RETRY_COUNT`, and the - printed `disambiguate with an x-ts-name override` moved the *members* apart - while leaving both constants on the colliding name — with no remaining escape - short of renaming the JSON property, i.e. changing the wire contract. The Go - closed-value type had the same misfire against a declared type name, and - disagreed with Java, whose nested value class already followed the override. - Both are now named off the emitted member identifier, so the documented escape - hatch resolves the clash and Go and Java agree on the synthesized type's name. - The governing rule — **a name synthesized from a member moves with that - member; a name synthesized from a position stays with the position** — is now - stated in PRINCIPLES §15; an inline object hoisted to `` is - position-derived and is still renamed by authoring it in `$defs` instead. - Emitted output is unchanged for any schema that does not put a name override - on a `default`- or `const`-bearing property. -- JSON Schema: A file's **root type and a same-file `$defs` entry of the same - name** silently collapsed into one type. `thing.yaml` declaring a root object - plus `$defs.Thing` — `Thing` being the name the root derives from the file name - — emitted a single `Thing` carrying the *root's* shape in all four languages, - with the `$defs` entry's members gone and every reference to it retargeted at - the root: Go emitted `Nested *Thing`, TypeScript `nested?: Thing`, Python - `nested: Thing | None`, Java `@Nullable Thing nested`, all pointing at a - self-reference the schema never declared. The coincidence is now a load-time - rejection for every target, naming the identifier and both origins (the root - schema's file-name derivation and the `$defs` entry) with the two renames that - resolve it — the `$defs` key, or the file the root name derives from. A name - **synthesized** for an inline shape that lands on the root type's name is - rejected the same way, reported at the position the shape was written in - (`$defs.User.properties.profile`). This is a new rejection: a schema that hit - the collapse used to generate and now fails to load. An `x--name` - override does not resolve it — the override moves one target's emitted - identifier, while the derived name is the model's identity. Any other route to - one identity is rejected too, rather than dropping a shape: two files whose - root types derive the same name in a module-less in-process load, for - instance. +- JSON Schema: Modules that own no types now import foreign `$ref` targets + without re-emitting duplicate declarations. +- JSON Schema: Identifier collisions now use each target's actual emitted + namespace, including Go's flat package and the TypeScript/Python root barrels. + TypeScript service constants are checked under their emitted lower-camel names. +- JSON Schema: Member-derived synthesized names now follow `x--name` + overrides, including TypeScript default constants and Go closed-value types. +- JSON Schema: A root model can no longer silently collapse with a same-named + `$defs` or synthesized model; the loader reports the conflicting origins. - JSON Schema: A **non-object `oneOf` branch's own constraints** were dropped in three of four languages: only Go carried them, in the synthesized `` variant's `Validate`. TypeScript cast the narrowed value @@ -310,13 +212,8 @@ array"` at runtime, though `items.md` accepts them. Both now decode elementwise, a time.") added that package to the import block, and an unused import is a Go compile error. Package use is now read off the emitted code, not the doc comments. -- JSON Schema: An `x--name` override on a model was ignored by every - *other* input file that referenced it, in all four languages. The consuming - module emitted the pre-override identifier — a dangling operation generic, - model import, and (in TypeScript) converter import; a dangling field type for a - cross-file `$ref` property; and, in Go's flat package, a reference to a type - name that is never declared. Emitted identifiers are now resolved once over the - whole input closure, so a cross-file reference names the overridden type. +- JSON Schema: Cross-file `$ref` and operation references now honor the target + model's `x--name` override. - JSON Schema: A `oneOf` with an inline object branch generated uncompilable Go (a marker method on an undeclared `Object` type) and uncompilable TypeScript (a converter named after the anonymous `Record` diff --git a/src/spec.rs b/src/spec.rs index 16667322..700f9e04 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -415,28 +415,31 @@ where } } -/// How a declaration relates to the module of the spec that carries it. +/// Which module, if any, is responsible for emitting a declaration. /// -/// The distinction that matters is between a front end that assigns modules at -/// all and one that does not: a spec whose declarations are all [`Unscoped`] -/// emits everything it can reach, while a spec that participates in module -/// scoping emits only what it [`Owned`]s. Collapsing these two into "has no -/// module exports" would make a module that declares nothing — a JSON service -/// file whose every operation type is `$ref`d from elsewhere — look like a front -/// end that does not scope, and re-emit every referenced type into it. +/// Reachability needs all three states. WIT produces one unscoped spec, so that +/// spec emits every reachable declaration. JSON Schema produces one spec per +/// input file: a declaration is either owned by that file and emitted there, or +/// foreign and imported from the file that owns it. In particular, a JSON +/// service file may own no types at all while still referencing foreign types; +/// treating that case as unscoped would emit duplicate declarations in the +/// service module. /// /// [`Unscoped`]: ModuleExport::Unscoped /// [`Owned`]: ModuleExport::Owned #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) enum ModuleExport { - /// The front end does not scope declarations by module (WIT), so the one - /// module emits every declaration it references. + /// No input module owns the declaration, so the containing spec must emit + /// it when reachable. This preserves front ends such as WIT that build one + /// spec without assigning declarations to source modules. #[default] Unscoped, - /// This module declares the type, and is the module that emits it. + /// The containing spec's input module declares the type, so it both seeds + /// reachability and is the one module allowed to emit the declaration. Owned, - /// Another module declares the type; this spec only references it, and must - /// import rather than re-emit it. + /// Another input module owns the declaration. Keeping the declaration here + /// lets planning follow references to it, while this distinct state keeps a + /// module that owns no types from looking unscoped and re-emitting it. Foreign, }