From 23c3344e590849c36f3a2bd9d56ddc2f1f0cd0cc Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 20 Jan 2026 12:16:57 -0800 Subject: [PATCH 1/4] Add flatten. --- CHANGELOG.md | 6 + crates/macros/src/common/field.rs | 8 + crates/schematic/tests/generator_test.rs | 3 + ...generator_test__json_schema__defaults.snap | 18 ++ ...rator_test__json_schema__not_required.snap | 17 ++ ...generator_test__json_schema__partials.snap | 42 +++++ ...est__json_schema__with_markdown_descs.snap | 18 ++ ...erator_test__json_schema__with_titles.snap | 19 +++ ...nerator_test__typescript__const_enums.snap | 2 + .../generator_test__typescript__defaults.snap | 2 + .../generator_test__typescript__enums.snap | 2 + ...erator_test__typescript__exclude_refs.snap | 2 + ...ator_test__typescript__external_types.snap | 2 + .../generator_test__typescript__no_refs.snap | 2 + ...ator_test__typescript__object_aliases.snap | 2 + .../generator_test__typescript__partials.snap | 4 + ...ator_test__typescript__props_optional.snap | 2 + ..._typescript__props_optional_undefined.snap | 2 + ...nerator_test__typescript__value_enums.snap | 2 + .../snapshots/partialize_test__compounds.snap | 4 + .../partialize_test__enum_adjacent.snap | 9 + .../partialize_test__enum_external.snap | 6 + .../partialize_test__enum_internal.snap | 2 + .../partialize_test__enum_untagged.snap | 1 + .../snapshots/partialize_test__enums.snap | 155 +++++++++++------- .../snapshots/partialize_test__nested.snap | 5 + .../partialize_test__nested_list.snap | 5 + .../partialize_test__nested_map.snap | 23 ++- .../partialize_test__primitives.snap | 8 + crates/types/src/schema.rs | 3 + 30 files changed, 306 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 538438f2..28614a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added `SchemaField.flatten` field. + ## 0.19.2 #### ⚙️ Internal diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs index 1c357b94..19b4648a 100644 --- a/crates/macros/src/common/field.rs +++ b/crates/macros/src/common/field.rs @@ -119,6 +119,11 @@ impl Field<'_> { self.args.extend } + #[cfg(feature = "schema")] + pub fn is_flatten(&self) -> bool { + self.serde_args.flatten || self.args.flatten + } + pub fn is_nested(&self) -> bool { self.args.nested } @@ -260,6 +265,7 @@ impl Field<'_> { let aliases = map_vec_field_quote("aliases", self.get_aliases()); let hidden = map_bool_field_quote("hidden", self.is_skipped()); + let flatten = map_bool_field_quote("flatten", self.is_flatten()); let nullable = map_bool_field_quote("nullable", self.is_nullable()); let optional = map_bool_field_quote("optional", self.is_optional()); let comment = map_option_field_quote("comment", extract_comment(&self.attrs)); @@ -305,6 +311,7 @@ impl Field<'_> { && comment.is_none() && deprecated.is_none() && env_var.is_none() + && flatten.is_none() && hidden.is_none() && nullable.is_none() && optional.is_none() @@ -320,6 +327,7 @@ impl Field<'_> { #comment #deprecated #env_var + #flatten #hidden #nullable #optional diff --git a/crates/schematic/tests/generator_test.rs b/crates/schematic/tests/generator_test.rs index 97a004cb..ef664c86 100644 --- a/crates/schematic/tests/generator_test.rs +++ b/crates/schematic/tests/generator_test.rs @@ -57,6 +57,9 @@ struct GenConfig { /// **Nested** field. #[setting(nested)] nested: AnotherConfig, + /// Flattened field... + #[setting(flatten)] + flattened: HashMap, // Types date: chrono::NaiveDate, diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap index c7e06dae..5ff8b4a3 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap @@ -14,6 +14,7 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", + "flattened", "float32", "float64", "indexmap", @@ -69,6 +70,23 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "description": "Flattened field...", + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "float32": { "type": "number" }, diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap index 55b93c42..cd0853fb 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap @@ -40,6 +40,23 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "description": "Flattened field...", + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "float32": { "type": "number" }, diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap index d068a346..1662eff1 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap @@ -74,6 +74,30 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "description": "Flattened field...", + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, "float32": { "anyOf": [ { @@ -407,6 +431,7 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", + "flattened", "float32", "float64", "indexmap", @@ -462,6 +487,23 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "description": "Flattened field...", + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "float32": { "type": "number" }, diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap index 6b5be0f9..77f9846e 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap @@ -14,6 +14,7 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", + "flattened", "float32", "float64", "indexmap", @@ -70,6 +71,23 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "description": "Flattened field...", + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "float32": { "type": "number" }, diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap index 1e594487..53645176 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap @@ -13,6 +13,7 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", + "flattened", "float32", "float64", "indexmap", @@ -74,6 +75,24 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, + "flattened": { + "title": "flattened", + "description": "Flattened field...", + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "float32": { "title": "float32", "type": "number" diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap index 3545c919..e9a5e59e 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap @@ -45,6 +45,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap index 06b53a40..235eae67 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap @@ -41,6 +41,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap index f6ab3a77..4542fac2 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap @@ -45,6 +45,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap index 5f13f4e9..9abb5145 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap @@ -38,6 +38,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap index 0fcc6862..16f87e61 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap @@ -43,6 +43,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap index 6dd5d79c..6e6b7a3c 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap @@ -41,6 +41,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: 'foo' | 'bar' | 'baz' | string; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap index 19c0e201..cbf14326 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap @@ -41,6 +41,8 @@ export type GenConfig = { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum, + /** Flattened field... */ + flattened: Record, float32: number, float64: number, indexmap: Record, diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap index 5f6b795d..bba26dab 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap @@ -41,6 +41,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; @@ -92,6 +94,8 @@ export interface PartialGenConfig { enums?: BasicEnum | null; /** @default 'foo' */ fallbackEnum?: FallbackEnum | null; + /** Flattened field... */ + flattened?: Record | null; float32?: number | null; float64?: number | null; indexmap?: Record | null; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap index ae69847d..5c454de3 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap @@ -41,6 +41,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum?: FallbackEnum; + /** Flattened field... */ + flattened?: Record; float32?: number; float64?: number; indexmap?: Record; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap index 0b0c4bc7..23fe61e6 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap @@ -41,6 +41,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum?: FallbackEnum | undefined; + /** Flattened field... */ + flattened?: Record | undefined; float32?: number | undefined; float64?: number | undefined; indexmap?: Record | undefined; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap index 2ca9dc39..19cb6cf5 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap @@ -45,6 +45,8 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; + /** Flattened field... */ + flattened: Record; float32: number; float64: number; indexmap: Record; diff --git a/crates/schematic/tests/snapshots/partialize_test__compounds.snap b/crates/schematic/tests/snapshots/partialize_test__compounds.snap index c0dfc28e..9ccf472b 100644 --- a/crates/schematic/tests/snapshots/partialize_test__compounds.snap +++ b/crates/schematic/tests/snapshots/partialize_test__compounds.snap @@ -97,6 +97,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -168,6 +169,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -275,6 +277,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -363,6 +366,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__enum_adjacent.snap b/crates/schematic/tests/snapshots/partialize_test__enum_adjacent.snap index bb1e53a3..c8f7f88c 100644 --- a/crates/schematic/tests/snapshots/partialize_test__enum_adjacent.snap +++ b/crates/schematic/tests/snapshots/partialize_test__enum_adjacent.snap @@ -43,6 +43,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -68,6 +69,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -104,6 +106,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -129,6 +132,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -173,6 +177,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -198,6 +203,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -284,6 +290,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -300,6 +307,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -325,6 +333,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__enum_external.snap b/crates/schematic/tests/snapshots/partialize_test__enum_external.snap index 1cb0fb7f..4ccc45fc 100644 --- a/crates/schematic/tests/snapshots/partialize_test__enum_external.snap +++ b/crates/schematic/tests/snapshots/partialize_test__enum_external.snap @@ -43,6 +43,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -79,6 +80,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -123,6 +125,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -233,6 +236,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -250,6 +254,7 @@ expression: "create_diff::()" 🟩 Schema { ⬛️ deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟩 description: None, 🟩 name: None, @@ -268,6 +273,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__enum_internal.snap b/crates/schematic/tests/snapshots/partialize_test__enum_internal.snap index a638182f..858e14ea 100644 --- a/crates/schematic/tests/snapshots/partialize_test__enum_internal.snap +++ b/crates/schematic/tests/snapshots/partialize_test__enum_internal.snap @@ -123,6 +123,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -179,6 +180,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__enum_untagged.snap b/crates/schematic/tests/snapshots/partialize_test__enum_untagged.snap index e674a3eb..621f5a6d 100644 --- a/crates/schematic/tests/snapshots/partialize_test__enum_untagged.snap +++ b/crates/schematic/tests/snapshots/partialize_test__enum_untagged.snap @@ -144,6 +144,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__enums.snap b/crates/schematic/tests/snapshots/partialize_test__enums.snap index fc82174a..0b818fb3 100644 --- a/crates/schematic/tests/snapshots/partialize_test__enums.snap +++ b/crates/schematic/tests/snapshots/partialize_test__enums.snap @@ -56,6 +56,58 @@ expression: "create_diff::()" 🟥 "a", 🟥 ), 🟥 }, +🟥 ), +🟥 }, +🟥 deprecated: None, +🟥 env_var: None, +🟥 flatten: false, +🟥 hidden: false, +🟥 nullable: false, +🟥 optional: false, +🟥 read_only: false, +🟥 write_only: false, +🟥 }, +🟥 "B": SchemaField { +🟥 aliases: [], +🟥 comment: None, +🟥 schema: Schema { +🟥 deprecated: None, +🟥 description: None, +🟥 name: None, +🟥 nullable: false, +🟥 ty: Literal( +🟥 LiteralType { +🟥 format: None, +🟥 value: String( +🟥 "b", +🟥 ), +🟥 }, +🟥 ), +🟥 }, +🟥 deprecated: None, +🟥 env_var: None, +🟥 flatten: false, +🟥 hidden: false, +🟥 nullable: false, +🟥 optional: false, +🟥 read_only: false, +🟥 write_only: false, +🟥 }, +🟥 "C": SchemaField { +🟥 aliases: [], +🟥 comment: None, +🟥 schema: Schema { +🟥 deprecated: None, +🟥 description: None, +🟥 name: None, +🟥 nullable: false, +🟥 ty: Literal( +🟥 LiteralType { +🟥 format: None, +🟥 value: String( +🟥 "c", +🟥 ), +🟥 }, 🟩 name: None, 🟩 nullable: true, 🟩 ty: Union( @@ -79,13 +131,14 @@ expression: "create_diff::()" 🟥 }, 🟥 deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, 🟥 read_only: false, 🟥 write_only: false, 🟥 }, -🟥 "B": SchemaField { +🟥 "Other": SchemaField { 🟥 aliases: [], 🟥 comment: None, 🟥 schema: Schema { @@ -93,12 +146,14 @@ expression: "create_diff::()" 🟥 description: None, 🟥 name: None, 🟥 nullable: false, -🟥 ty: Literal( -🟥 LiteralType { +🟥 ty: String( +🟥 StringType { +🟥 default: None, +🟥 enum_values: None, 🟥 format: None, -🟥 value: String( -🟥 "b", -🟥 ), +🟥 max_length: None, +🟥 min_length: None, +🟥 pattern: None, 🟩 values: [ 🟩 String( 🟩 "a", @@ -131,6 +186,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -156,6 +212,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -181,6 +238,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -208,6 +266,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -219,58 +278,7 @@ expression: "create_diff::()" ⬛️ }, 🟥 deprecated: None, 🟥 env_var: None, -🟥 hidden: false, -🟥 nullable: false, -🟥 optional: false, -🟥 read_only: false, -🟥 write_only: false, -🟥 }, -🟥 "C": SchemaField { -🟥 aliases: [], -🟥 comment: None, -🟥 schema: Schema { -🟥 deprecated: None, -🟥 description: None, -🟥 name: None, -🟥 nullable: false, -🟥 ty: Literal( -🟥 LiteralType { -🟥 format: None, -🟥 value: String( -🟥 "c", -🟥 ), -🟥 }, -🟥 ), -🟥 }, -🟥 deprecated: None, -🟥 env_var: None, -🟥 hidden: false, -🟥 nullable: false, -🟥 optional: false, -🟥 read_only: false, -🟥 write_only: false, -🟥 }, -🟥 "Other": SchemaField { -🟥 aliases: [], -🟥 comment: None, -🟥 schema: Schema { -🟥 deprecated: None, -🟥 description: None, -🟥 name: None, -🟥 nullable: false, -🟥 ty: String( -🟥 StringType { -🟥 default: None, -🟥 enum_values: None, -🟥 format: None, -🟥 max_length: None, -🟥 min_length: None, -🟥 pattern: None, -🟥 }, -🟥 ), -🟥 }, -🟥 deprecated: None, -🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -278,21 +286,22 @@ expression: "create_diff::()" 🟥 write_only: false, 🟥 }, 🟩 ), -🟩 }, +⬛️ }, +🟥 ), 🟩 Schema { 🟩 deprecated: None, 🟩 description: None, 🟩 name: None, 🟩 nullable: false, 🟩 ty: Null, -⬛️ }, -🟥 ), +🟩 }, 🟩 ], ⬛️ }, ⬛️ ), ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -360,6 +369,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -385,6 +395,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -410,6 +421,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -437,6 +449,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -461,6 +474,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -569,6 +583,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -586,6 +601,7 @@ expression: "create_diff::()" 🟩 Schema { ⬛️ deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟩 description: None, 🟩 name: None, @@ -638,6 +654,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -685,6 +702,7 @@ expression: "create_diff::()" 🟥 }, 🟥 deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -704,6 +722,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -800,6 +819,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -873,6 +893,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -904,6 +925,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -977,6 +999,7 @@ expression: "create_diff::()" 🟥 }, 🟥 deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -1002,6 +1025,7 @@ expression: "create_diff::()" 🟥 }, 🟥 deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -1054,6 +1078,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -1079,6 +1104,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -1104,6 +1130,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: false, 🟩 optional: false, @@ -1115,6 +1142,7 @@ expression: "create_diff::()" ⬛️ }, 🟥 deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -1137,6 +1165,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -1204,6 +1233,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -1229,6 +1259,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -1254,6 +1285,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: false, ⬛️ optional: false, @@ -1278,6 +1310,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__nested.snap b/crates/schematic/tests/snapshots/partialize_test__nested.snap index e6d5d476..9b514c47 100644 --- a/crates/schematic/tests/snapshots/partialize_test__nested.snap +++ b/crates/schematic/tests/snapshots/partialize_test__nested.snap @@ -103,6 +103,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -120,6 +121,7 @@ expression: "create_diff::()" 🟩 Schema { ⬛️ deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟩 description: None, 🟩 name: None, @@ -138,6 +140,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -224,6 +227,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -251,6 +255,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__nested_list.snap b/crates/schematic/tests/snapshots/partialize_test__nested_list.snap index a1b3d8e2..0dd01e03 100644 --- a/crates/schematic/tests/snapshots/partialize_test__nested_list.snap +++ b/crates/schematic/tests/snapshots/partialize_test__nested_list.snap @@ -71,6 +71,7 @@ expression: "create_diff::()" 🟩 items_type: Schema { ⬛️ deprecated: None, 🟥 env_var: None, +🟥 flatten: false, 🟥 hidden: false, 🟩 description: None, 🟩 name: Some( @@ -126,6 +127,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -169,6 +171,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -263,6 +266,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -298,6 +302,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__nested_map.snap b/crates/schematic/tests/snapshots/partialize_test__nested_map.snap index e6630bae..eb74599c 100644 --- a/crates/schematic/tests/snapshots/partialize_test__nested_map.snap +++ b/crates/schematic/tests/snapshots/partialize_test__nested_map.snap @@ -86,15 +86,10 @@ expression: "create_diff::()" 🟩 ty: Object( 🟩 ObjectType { 🟩 key_type: Schema { -⬛️ deprecated: None, -🟥 env_var: None, -🟥 hidden: false, +🟩 deprecated: None, 🟩 description: None, 🟩 name: None, -⬛️ nullable: false, -🟥 optional: false, -🟥 read_only: false, -🟥 write_only: false, +🟩 nullable: false, 🟩 ty: String( 🟩 StringType { 🟩 default: None, @@ -110,12 +105,18 @@ expression: "create_diff::()" 🟩 min_length: None, 🟩 required: None, 🟩 value_type: Schema { -🟩 deprecated: None, +⬛️ deprecated: None, +🟥 env_var: None, +🟥 flatten: false, +🟥 hidden: false, 🟩 description: None, 🟩 name: Some( 🟩 "PartialBasic", 🟩 ), -🟩 nullable: false, +⬛️ nullable: false, +🟥 optional: false, +🟥 read_only: false, +🟥 write_only: false, 🟩 ty: Struct( 🟩 StructType { 🟩 fields: { @@ -162,6 +163,7 @@ expression: "create_diff::()" 🟩 }, 🟩 deprecated: None, 🟩 env_var: None, +🟩 flatten: false, 🟩 hidden: false, 🟩 nullable: true, 🟩 optional: true, @@ -195,6 +197,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -310,6 +313,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -340,6 +344,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/schematic/tests/snapshots/partialize_test__primitives.snap b/crates/schematic/tests/snapshots/partialize_test__primitives.snap index 465afd9e..fc2c9a15 100644 --- a/crates/schematic/tests/snapshots/partialize_test__primitives.snap +++ b/crates/schematic/tests/snapshots/partialize_test__primitives.snap @@ -55,6 +55,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -102,6 +103,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -169,6 +171,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -225,6 +228,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -290,6 +294,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -345,6 +350,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, @@ -404,6 +410,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, 🟥 nullable: false, 🟥 optional: false, @@ -456,6 +463,7 @@ expression: "create_diff::()" ⬛️ }, ⬛️ deprecated: None, ⬛️ env_var: None, +⬛️ flatten: false, ⬛️ hidden: false, ⬛️ nullable: true, 🟥 optional: false, diff --git a/crates/types/src/schema.rs b/crates/types/src/schema.rs index 0432ea8f..8aeac8bc 100644 --- a/crates/types/src/schema.rs +++ b/crates/types/src/schema.rs @@ -258,6 +258,9 @@ pub struct SchemaField { )] pub env_var: Option, + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))] + pub flatten: bool, + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))] pub hidden: bool, From ee84ce20615349fc5407c4df8b903b672d29b7a5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 20 Jan 2026 15:41:38 -0800 Subject: [PATCH 2/4] Update json schema. --- CHANGELOG.md | 5 +- .../src/schema/renderers/json_schema.rs | 8 +- .../src/schema/renderers/jsonc_template.rs | 4 + .../src/schema/renderers/pkl_template.rs | 4 + .../src/schema/renderers/toml_template.rs | 4 + .../src/schema/renderers/yaml_template.rs | 4 + ...generator_test__json_schema__defaults.snap | 35 ++++---- ...rator_test__json_schema__not_required.snap | 34 ++++---- ...generator_test__json_schema__partials.snap | 83 +++++++++---------- ...est__json_schema__with_markdown_descs.snap | 35 ++++---- ...erator_test__json_schema__with_titles.snap | 36 ++++---- .../macros_test__generates_json_schema.snap | 81 +++++++++--------- 12 files changed, 169 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28614a6a..6c9b5086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ #### 🚀 Updates -- Added `SchemaField.flatten` field. +- Added `#[serde(flatten)]` support to schema generation. + - Added `SchemaField.flatten` field. + - Updated `JsonSchemaRenderer` to render flattened fields as `additionalProperties`. + - Updated `TemplateRenderer`s to skip rendering flattened fields. ## 0.19.2 diff --git a/crates/schematic/src/schema/renderers/json_schema.rs b/crates/schematic/src/schema/renderers/json_schema.rs index 6b67a5ea..b690cf58 100644 --- a/crates/schematic/src/schema/renderers/json_schema.rs +++ b/crates/schematic/src/schema/renderers/json_schema.rs @@ -438,6 +438,7 @@ impl SchemaRenderer for JsonSchemaRenderer { ) -> RenderResult { let mut properties = BTreeMap::new(); let mut required = BTreeSet::from_iter(structure.required.clone().unwrap_or_default()); + let mut additional_properties = JsonSchema::Bool(false); let exclude_aliases = self.options.exclude_aliases; for (name, field) in &structure.fields { @@ -445,6 +446,11 @@ impl SchemaRenderer for JsonSchemaRenderer { continue; } + if field.flatten { + additional_properties = self.render_schema_without_reference(&field.schema)?; + continue; + } + if !field.optional && self.options.mark_struct_fields_required { required.insert(name.to_owned()); } @@ -465,7 +471,7 @@ impl SchemaRenderer for JsonSchemaRenderer { metadata: Some(Box::new(self.create_metadata_from_schema(schema))), instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), object: Some(Box::new(ObjectValidation { - additional_properties: Some(Box::new(JsonSchema::Bool(false))), + additional_properties: Some(Box::new(additional_properties)), required, properties, ..Default::default() diff --git a/crates/schematic/src/schema/renderers/jsonc_template.rs b/crates/schematic/src/schema/renderers/jsonc_template.rs index c13d00af..b85ddde4 100644 --- a/crates/schematic/src/schema/renderers/jsonc_template.rs +++ b/crates/schematic/src/schema/renderers/jsonc_template.rs @@ -117,6 +117,10 @@ impl SchemaRenderer for JsoncTemplateRenderer { self.ctx.depth += 1; for (index, (name, field)) in structure.fields.iter().enumerate() { + if field.flatten { + continue; + } + self.ctx.push_stack(name); if !self.ctx.is_hidden(field) { diff --git a/crates/schematic/src/schema/renderers/pkl_template.rs b/crates/schematic/src/schema/renderers/pkl_template.rs index 6bd9bb15..a5e462c8 100644 --- a/crates/schematic/src/schema/renderers/pkl_template.rs +++ b/crates/schematic/src/schema/renderers/pkl_template.rs @@ -125,6 +125,10 @@ impl SchemaRenderer for PklTemplateRenderer { self.ctx.depth += 1; for (name, field) in &structure.fields { + if field.flatten { + continue; + } + self.ctx.push_stack(name); if !self.ctx.is_hidden(field) { diff --git a/crates/schematic/src/schema/renderers/toml_template.rs b/crates/schematic/src/schema/renderers/toml_template.rs index fc0e4039..fc5b2f04 100644 --- a/crates/schematic/src/schema/renderers/toml_template.rs +++ b/crates/schematic/src/schema/renderers/toml_template.rs @@ -172,6 +172,10 @@ impl SchemaRenderer for TomlTemplateRenderer { let mut out = vec![]; for (name, field) in &structure.fields { + if field.flatten { + continue; + } + self.ctx.push_stack(name); if !self.ctx.is_hidden(field) { diff --git a/crates/schematic/src/schema/renderers/yaml_template.rs b/crates/schematic/src/schema/renderers/yaml_template.rs index 61c2d874..4aa7351a 100644 --- a/crates/schematic/src/schema/renderers/yaml_template.rs +++ b/crates/schematic/src/schema/renderers/yaml_template.rs @@ -119,6 +119,10 @@ impl SchemaRenderer for YamlTemplateRenderer { let mut out = vec![]; for (name, field) in &structure.fields { + if field.flatten { + continue; + } + self.ctx.push_stack(name); if self.ctx.is_hidden(field) { diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap index 5ff8b4a3..fb9e0a5d 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__defaults.snap @@ -14,7 +14,6 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", - "flattened", "float32", "float64", "indexmap", @@ -70,23 +69,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "description": "Flattened field...", - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "float32": { "type": "number" }, @@ -230,7 +212,22 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "definitions": { "AnotherConfig": { "title": "AnotherConfig", diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap index cd0853fb..f67ad930 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__not_required.snap @@ -40,23 +40,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "description": "Flattened field...", - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "float32": { "type": "number" }, @@ -200,7 +183,22 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "definitions": { "AnotherConfig": { "title": "AnotherConfig", diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap index 1662eff1..842a712a 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__partials.snap @@ -74,30 +74,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "description": "Flattened field...", - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, - { - "type": "null" - } - ] - }, "float32": { "anyOf": [ { @@ -356,7 +332,29 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, "definitions": { "AnotherConfig": { "title": "AnotherConfig", @@ -431,7 +429,6 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", - "flattened", "float32", "float64", "indexmap", @@ -487,23 +484,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "description": "Flattened field...", - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "float32": { "type": "number" }, @@ -647,7 +627,22 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + } }, "PartialAnotherConfig": { "title": "PartialAnotherConfig", diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap index 77f9846e..5e731fe1 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__with_markdown_descs.snap @@ -14,7 +14,6 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", - "flattened", "float32", "float64", "indexmap", @@ -71,23 +70,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "description": "Flattened field...", - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "float32": { "type": "number" }, @@ -232,7 +214,22 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "definitions": { "AnotherConfig": { "title": "AnotherConfig", diff --git a/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap b/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap index 53645176..03520ae0 100644 --- a/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap +++ b/crates/schematic/tests/snapshots/generator_test__json_schema__with_titles.snap @@ -13,7 +13,6 @@ expression: "fs::read_to_string(file).unwrap()" "decimal", "enums", "fallbackEnum", - "flattened", "float32", "float64", "indexmap", @@ -75,24 +74,6 @@ expression: "fs::read_to_string(file).unwrap()" } ] }, - "flattened": { - "title": "flattened", - "description": "Flattened field...", - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "float32": { "title": "float32", "type": "number" @@ -257,7 +238,22 @@ expression: "fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, "definitions": { "AnotherConfig": { "description": "Some comment.", diff --git a/crates/schematic/tests/snapshots/macros_test__generates_json_schema.snap b/crates/schematic/tests/snapshots/macros_test__generates_json_schema.snap index 2bbdbb78..2193dabc 100644 --- a/crates/schematic/tests/snapshots/macros_test__generates_json_schema.snap +++ b/crates/schematic/tests/snapshots/macros_test__generates_json_schema.snap @@ -800,29 +800,6 @@ expression: "std::fs::read_to_string(file).unwrap()" } ] }, - "rest": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, - { - "type": "null" - } - ] - }, "s3_value": { "anyOf": [ { @@ -857,7 +834,29 @@ expression: "std::fs::read_to_string(file).unwrap()" ] } }, - "additionalProperties": false + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + }, + { + "type": "null" + } + ] + } }, "Serde": { "title": "Serde", @@ -992,7 +991,6 @@ expression: "std::fs::read_to_string(file).unwrap()" "enums", "map", "number", - "rest", "s3_value", "string", "vector" @@ -1021,22 +1019,6 @@ expression: "std::fs::read_to_string(file).unwrap()" "number": { "type": "number" }, - "rest": { - "type": "object", - "additionalProperties": { - "type": [ - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "propertyNames": { - "type": "string" - } - }, "s3_value": { "type": "string" }, @@ -1050,7 +1032,22 @@ expression: "std::fs::read_to_string(file).unwrap()" } } }, - "additionalProperties": false + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": [ + "boolean", + "object", + "array", + "number", + "string", + "integer" + ] + }, + "propertyNames": { + "type": "string" + } + } } } } From 537b2fdf2c97b34bac38a188c728e6a2a401301a Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 20 Jan 2026 17:02:55 -0800 Subject: [PATCH 3/4] Update typescript. --- CHANGELOG.md | 3 + Cargo.lock | 1 + Cargo.toml | 1 + crates/macros/Cargo.toml | 2 +- crates/schematic/Cargo.toml | 3 +- .../src/schema/renderers/typescript.rs | 80 ++++++++++++++++--- ...nerator_test__typescript__const_enums.snap | 8 +- .../generator_test__typescript__defaults.snap | 8 +- .../generator_test__typescript__enums.snap | 8 +- ...erator_test__typescript__exclude_refs.snap | 8 +- ...ator_test__typescript__external_types.snap | 8 +- .../generator_test__typescript__no_refs.snap | 8 +- ...ator_test__typescript__object_aliases.snap | 8 +- .../generator_test__typescript__partials.snap | 16 ++-- ...ator_test__typescript__props_optional.snap | 8 +- ..._typescript__props_optional_undefined.snap | 8 +- ...nerator_test__typescript__value_enums.snap | 8 +- .../macros_test__generates_typescript-2.snap | 14 +++- .../macros_test__generates_typescript.snap | 14 +++- 19 files changed, 156 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c9b5086..133b524b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ - Added `#[serde(flatten)]` support to schema generation. - Added `SchemaField.flatten` field. - Updated `JsonSchemaRenderer` to render flattened fields as `additionalProperties`. + - Updated `TypeScriptRenderer` to render flattened fields as index signatures & intersection + types. - Updated `TemplateRenderer`s to skip rendering flattened fields. +- Updated `#[serde(untagged)]` enums to render better error messages based on the variant types. ## 0.19.2 diff --git a/Cargo.lock b/Cargo.lock index f6426ff8..566840af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,6 +1897,7 @@ name = "schematic" version = "0.19.2" dependencies = [ "chrono", + "convert_case", "derive_more", "garde", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 2f457758..86c0571d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = ["crates/*"] [workspace.dependencies] chrono = "0.4.42" +convert_case = "0.10.0" indexmap = "2.13.0" miette = "7.6.0" regex = "1.12.2" diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 5f2b8156..ebd293fe 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -14,7 +14,7 @@ all-features = true proc-macro = true [dependencies] -convert_case = "0.10.0" +convert_case = { workspace = true } darling = "0.23.0" proc-macro2 = "1.0.105" quote = "1.0.43" diff --git a/crates/schematic/Cargo.toml b/crates/schematic/Cargo.toml index 39bc94d7..238d1eeb 100644 --- a/crates/schematic/Cargo.toml +++ b/crates/schematic/Cargo.toml @@ -37,6 +37,7 @@ regex = { workspace = true, optional = true } semver = { workspace = true, optional = true } # schema +convert_case = { workspace = true, optional = true } indexmap = { workspace = true, optional = true, features = ["serde"] } # json @@ -71,7 +72,7 @@ config = [ "dep:starbase_styles", "schematic_macros/config", ] -schema = ["dep:indexmap", "schematic_macros/schema"] +schema = ["dep:convert_case", "dep:indexmap", "schematic_macros/schema"] schema_serde = ["schema", "schematic_types/serde"] tracing = ["schematic_macros/tracing"] diff --git a/crates/schematic/src/schema/renderers/typescript.rs b/crates/schematic/src/schema/renderers/typescript.rs index 7abc8616..49a9d223 100644 --- a/crates/schematic/src/schema/renderers/typescript.rs +++ b/crates/schematic/src/schema/renderers/typescript.rs @@ -1,4 +1,5 @@ use crate::schema::{RenderResult, SchemaRenderer}; +use convert_case::{Case, Casing}; use indexmap::IndexMap; use schematic_types::*; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -154,13 +155,7 @@ impl TypeScriptRenderer { Ok(self.wrap_in_comment(schema.description.as_ref(), tags, output)) } - fn export_object_type( - &mut self, - name: &str, - structure: &StructType, - schema: &Schema, - ) -> RenderResult { - let value = self.render_struct(structure, schema)?; + fn export_object_type(&mut self, name: &str, schema: &Schema, value: String) -> RenderResult { let mut tags = vec![]; let output = if matches!(self.options.object_format, ObjectFormat::Interface) { @@ -180,6 +175,58 @@ impl TypeScriptRenderer { Ok(self.wrap_in_comment(schema.description.as_ref(), tags, output)) } + fn export_object_types( + &mut self, + name: &str, + structure: &StructType, + schema: &Schema, + ) -> RenderResult> { + let mut outputs = vec![]; + let mut extends = vec![]; + + // Extract flattened fields first as we'll need to use intersections + // to support them correctly in TypeScript + for (field_name, field) in &structure.fields { + if field.flatten { + let name = format!( + "{name}{}", + field_name.from_case(Case::Snake).to_case(Case::Pascal) + ); + let value = self.render_schema(&field.schema)?; + + outputs.push(self.export_object_type( + &name, + &field.schema, + format!("{{ [key: string]: {value} }}"), + )?); + extends.push(name); + } + } + + let value = self.render_struct(structure, schema)?; + + // If nothing to extend then we can render this as either an + // interface or a type alias, depending on the option + if extends.is_empty() { + outputs.push(self.export_object_type(name, schema, value)?); + } + // Otherwise we need to render the struct as a "base" type + // and then create a parent type alias thats an intersection + // of all the extended types + else { + let base_name = format!("{name}Base"); + + outputs.push(self.export_object_type(&base_name, schema, value)?); + + extends.push(base_name); + extends.reverse(); + + outputs.push(self.export_type_alias(name, extends.join(" & "))?) + } + + Ok(outputs) + } + fn render_enum_as_string_union(&mut self, enu: &EnumType, schema: &Schema) -> RenderResult { // Map using variants instead of values (when available), // so that the fallback variant is included @@ -471,6 +518,11 @@ impl SchemaRenderer for TypeScriptRenderer { }; for (name, field) in &structure.fields { + // Handle in `export_object_types` + if field.flatten { + continue; + } + if !exclude_aliases { for alias in &field.aliases { create_row(alias, field)?; @@ -538,14 +590,18 @@ impl SchemaRenderer for TypeScriptRenderer { continue; } - outputs.push(match &schema.ty { - SchemaType::Enum(inner) => self.export_enum_type(name, inner, schema)?, - SchemaType::Struct(inner) => self.export_object_type(name, inner, schema)?, + match &schema.ty { + SchemaType::Enum(inner) => { + outputs.push(self.export_enum_type(name, inner, schema)?) + } + SchemaType::Struct(inner) => { + outputs.extend(self.export_object_types(name, inner, schema)?); + } _ => { let out = self.render_schema_without_reference(schema)?; - self.export_type_alias(name, out)? + outputs.push(self.export_type_alias(name, out)?); } - }); + }; } Ok(outputs.join("\n\n")) diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap index e9a5e59e..9737b9a8 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap @@ -27,8 +27,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -45,8 +47,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -71,3 +71,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap index 235eae67..8a0e5702 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap @@ -23,8 +23,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -41,8 +43,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -67,3 +67,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap index 4542fac2..eb221e58 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap @@ -27,8 +27,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -45,8 +47,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -71,3 +71,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap index 9abb5145..2d3c1857 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap @@ -20,8 +20,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -38,8 +40,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -64,3 +64,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap index 16f87e61..3dbfbcd7 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap @@ -25,8 +25,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -43,8 +45,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -69,3 +69,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap index 6e6b7a3c..35192661 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap @@ -23,8 +23,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -41,8 +43,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: 'foo' | 'bar' | 'baz' | string; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -76,3 +76,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap index cbf14326..a87adbd8 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap @@ -23,8 +23,10 @@ export type AnotherConfig = { opt: string | null, }; +export type GenConfigFlattened = { [key: string]: Record }; + /** @deprecated */ -export type GenConfig = { +export type GenConfigBase = { boolean: boolean, date: string, datetime: string, @@ -41,8 +43,6 @@ export type GenConfig = { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum, - /** Flattened field... */ - flattened: Record, float32: number, float64: number, indexmap: Record, @@ -67,3 +67,5 @@ export type GenConfig = { versionReq: string, yamlValue: unknown, }; + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap index bba26dab..87b4ce2e 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap @@ -23,8 +23,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -41,8 +43,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -68,6 +68,8 @@ export interface GenConfig { yamlValue: unknown; } +export type GenConfig = GenConfigBase & GenConfigFlattened; + /** Some comment. */ export interface PartialAnotherConfig { /** @@ -80,8 +82,10 @@ export interface PartialAnotherConfig { opt?: string | null; } +export interface PartialGenConfigFlattened { [key: string]: Record | null } + /** @deprecated */ -export interface PartialGenConfig { +export interface PartialGenConfigBase { boolean?: boolean | null; date?: string | null; datetime?: string | null; @@ -94,8 +98,6 @@ export interface PartialGenConfig { enums?: BasicEnum | null; /** @default 'foo' */ fallbackEnum?: FallbackEnum | null; - /** Flattened field... */ - flattened?: Record | null; float32?: number | null; float64?: number | null; indexmap?: Record | null; @@ -120,3 +122,5 @@ export interface PartialGenConfig { versionReq?: string | null; yamlValue?: unknown | null; } + +export type PartialGenConfig = PartialGenConfigBase & PartialGenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap index 5c454de3..88bd0f56 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap @@ -23,8 +23,10 @@ export interface AnotherConfig { opt?: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean?: boolean; date?: string; datetime?: string; @@ -41,8 +43,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum?: FallbackEnum; - /** Flattened field... */ - flattened?: Record; float32?: number; float64?: number; indexmap?: Record; @@ -67,3 +67,5 @@ export interface GenConfig { versionReq?: string; yamlValue?: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap index 23fe61e6..c4500b1f 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap @@ -23,8 +23,10 @@ export interface AnotherConfig { opt?: string | null | undefined; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean?: boolean | undefined; date?: string | undefined; datetime?: string | undefined; @@ -41,8 +43,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum?: FallbackEnum | undefined; - /** Flattened field... */ - flattened?: Record | undefined; float32?: number | undefined; float64?: number | undefined; indexmap?: Record | undefined; @@ -67,3 +67,5 @@ export interface GenConfig { versionReq?: string | undefined; yamlValue?: unknown | undefined; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap index 19cb6cf5..9fec7a56 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap @@ -27,8 +27,10 @@ export interface AnotherConfig { opt: string | null; } +export interface GenConfigFlattened { [key: string]: Record } + /** @deprecated */ -export interface GenConfig { +export interface GenConfigBase { boolean: boolean; date: string; datetime: string; @@ -45,8 +47,6 @@ export interface GenConfig { * @type {'foo' | 'bar' | 'baz' | string} */ fallbackEnum: FallbackEnum; - /** Flattened field... */ - flattened: Record; float32: number; float64: number; indexmap: Record; @@ -71,3 +71,5 @@ export interface GenConfig { versionReq: string; yamlValue: unknown; } + +export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/macros_test__generates_typescript-2.snap b/crates/schematic/tests/snapshots/macros_test__generates_typescript-2.snap index 665720fb..8fbc2720 100644 --- a/crates/schematic/tests/snapshots/macros_test__generates_typescript-2.snap +++ b/crates/schematic/tests/snapshots/macros_test__generates_typescript-2.snap @@ -33,7 +33,9 @@ export const enum Aliased { Baz, } -export type ValueTypes = { +export type ValueTypesRest = { [key: string]: Record }; + +export type ValueTypesBase = { boolean: boolean, /** * @default 'a' @@ -42,12 +44,13 @@ export type ValueTypes = { enums: SomeEnum, map: Record, number: number, - rest: Record, s3_value: string, string: string, vector: string[], }; +export type ValueTypes = ValueTypesBase & ValueTypesRest; + export type OptionalValues = { optional: string | null, required: boolean, @@ -194,18 +197,21 @@ export type PartialDefaultValues = { vector?: number[] | null, }; -export type PartialValueTypes = { +export type PartialValueTypesRest = { [key: string]: Record | null }; + +export type PartialValueTypesBase = { boolean?: boolean | null, /** @default 'a' */ enums?: SomeEnum | null, map?: Record | null, number?: number | null, - rest?: Record | null, s3_value?: string | null, string?: string | null, vector?: string[] | null, }; +export type PartialValueTypes = PartialValueTypesBase & PartialValueTypesRest; + export type PartialNested = { list?: PartialValueTypes[] | null, map?: Record | null, diff --git a/crates/schematic/tests/snapshots/macros_test__generates_typescript.snap b/crates/schematic/tests/snapshots/macros_test__generates_typescript.snap index 998dedec..524f7482 100644 --- a/crates/schematic/tests/snapshots/macros_test__generates_typescript.snap +++ b/crates/schematic/tests/snapshots/macros_test__generates_typescript.snap @@ -16,7 +16,9 @@ export type OtherEnum = 'foo' | 'bar' | 'baz' | string; export type Aliased = 'foo' | 'bar' | 'baz'; -export interface ValueTypes { +export interface ValueTypesRest { [key: string]: Record } + +export interface ValueTypesBase { boolean: boolean; /** * @default 'a' @@ -25,12 +27,13 @@ export interface ValueTypes { enums: SomeEnum; map: Record; number: number; - rest: Record; s3_value: string; string: string; vector: string[]; } +export type ValueTypes = ValueTypesBase & ValueTypesRest; + export interface OptionalValues { optional: string | null; required: boolean; @@ -177,18 +180,21 @@ export interface PartialDefaultValues { vector?: number[] | null; } -export interface PartialValueTypes { +export interface PartialValueTypesRest { [key: string]: Record | null } + +export interface PartialValueTypesBase { boolean?: boolean | null; /** @default 'a' */ enums?: SomeEnum | null; map?: Record | null; number?: number | null; - rest?: Record | null; s3_value?: string | null; string?: string | null; vector?: string[] | null; } +export type PartialValueTypes = PartialValueTypesBase & PartialValueTypesRest; + export interface PartialNested { list?: PartialValueTypes[] | null; map?: Record | null; From 3d6f46ebd85dbfb75822f2c25b9e91fb3c3964af Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 20 Jan 2026 17:17:08 -0800 Subject: [PATCH 4/4] Handle comments. --- crates/schematic/src/schema/renderers/typescript.rs | 6 ++++-- .../snapshots/generator_test__typescript__const_enums.snap | 1 + .../snapshots/generator_test__typescript__defaults.snap | 1 + .../tests/snapshots/generator_test__typescript__enums.snap | 1 + .../snapshots/generator_test__typescript__exclude_refs.snap | 1 + .../generator_test__typescript__external_types.snap | 1 + .../snapshots/generator_test__typescript__no_refs.snap | 1 + .../generator_test__typescript__object_aliases.snap | 1 + .../snapshots/generator_test__typescript__partials.snap | 2 ++ .../generator_test__typescript__props_optional.snap | 1 + ...enerator_test__typescript__props_optional_undefined.snap | 1 + .../snapshots/generator_test__typescript__value_enums.snap | 1 + 12 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/schematic/src/schema/renderers/typescript.rs b/crates/schematic/src/schema/renderers/typescript.rs index 49a9d223..a70ff7d7 100644 --- a/crates/schematic/src/schema/renderers/typescript.rs +++ b/crates/schematic/src/schema/renderers/typescript.rs @@ -158,7 +158,9 @@ impl TypeScriptRenderer { fn export_object_type(&mut self, name: &str, schema: &Schema, value: String) -> RenderResult { let mut tags = vec![]; - let output = if matches!(self.options.object_format, ObjectFormat::Interface) { + let output = if !value.contains(" & ") + && matches!(self.options.object_format, ObjectFormat::Interface) + { format!("export interface {name} {value}") } else { self.export_type_alias(name, value)? @@ -221,7 +223,7 @@ impl TypeScriptRenderer { extends.push(base_name); extends.reverse(); - outputs.push(self.export_type_alias(name, extends.join(" & "))?) + outputs.push(self.export_object_type(name, schema, extends.join(" & "))?) } Ok(outputs) diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap index 9737b9a8..406b14dc 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__const_enums.snap @@ -72,4 +72,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap index 8a0e5702..12abaf0a 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__defaults.snap @@ -68,4 +68,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap index eb221e58..6ccc603a 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__enums.snap @@ -72,4 +72,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap index 2d3c1857..a7a90448 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__exclude_refs.snap @@ -65,4 +65,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap index 3dbfbcd7..fb9dc7e9 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__external_types.snap @@ -70,4 +70,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap index 35192661..c1b79f86 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__no_refs.snap @@ -77,4 +77,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap index a87adbd8..132d2172 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__object_aliases.snap @@ -68,4 +68,5 @@ export type GenConfigBase = { yamlValue: unknown, }; +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap index 87b4ce2e..c8e4db06 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__partials.snap @@ -68,6 +68,7 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; /** Some comment. */ @@ -123,4 +124,5 @@ export interface PartialGenConfigBase { yamlValue?: unknown | null; } +/** @deprecated */ export type PartialGenConfig = PartialGenConfigBase & PartialGenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap index 88bd0f56..d6b2ed1d 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional.snap @@ -68,4 +68,5 @@ export interface GenConfigBase { yamlValue?: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap index c4500b1f..0e2d5e85 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__props_optional_undefined.snap @@ -68,4 +68,5 @@ export interface GenConfigBase { yamlValue?: unknown | undefined; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened; diff --git a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap index 9fec7a56..bc8a3dc7 100644 --- a/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap +++ b/crates/schematic/tests/snapshots/generator_test__typescript__value_enums.snap @@ -72,4 +72,5 @@ export interface GenConfigBase { yamlValue: unknown; } +/** @deprecated */ export type GenConfig = GenConfigBase & GenConfigFlattened;