bugfix/preserve-casing-via-config#459
Open
iOSonntag wants to merge 4 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an opt-in preserve_schema_casing / preserveSchemaCasing configuration to stop forced PascalCase normalization of schema/enum identifiers, allowing generated identifiers to retain spec-author casing while stripping separator characters.
Changes:
- Introduces
String.toPreservedCaseand threads a single decision point through the corrector + parser to preserve casing when enabled. - Updates Dart generator templates to stop re-PascalCasing already-normalized
dataClass.name/enumClass.name. - Adds unit/config tests and a new e2e fixture to validate preserved casing for
kUserStatus,XMLHttpRequest,iOSDevice, andURL.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| swagger_parser/lib/src/config/swp_config.dart | Adds preserveSchemaCasing option, YAML parsing/wiring to ParserConfig, and documents behavior. |
| swagger_parser/lib/src/parser/config/parser_config.dart | Adds preserveSchemaCasing flag to parser configuration. |
| swagger_parser/lib/src/parser/model/normalized_identifier.dart | Adds String.toPreservedCase implementation. |
| swagger_parser/lib/src/parser/corrector/open_api_corrector.dart | Uses preserved casing vs PascalCase when rewriting schema keys/refs based on config. |
| swagger_parser/lib/src/parser/parser/open_api_parser.dart | Introduces _schemaIdentifier() helper and applies it to schema-derived identifiers and $ref resolutions. |
| swagger_parser/lib/src/generator/templates/dart_typedef_template.dart | Stops re-applying .toPascal to dataClass.name for typedef generation. |
| swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart | Stops re-applying .toPascal to dataClass.name for JSON-serializable DTOs. |
| swagger_parser/lib/src/generator/templates/dart_freezed_dto_template.dart | Stops re-applying .toPascal to dataClass.name for Freezed DTOs. |
| swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart | Stops re-applying .toPascal to enumClass.name in enum templates. |
| swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart | Stops re-applying .toPascal to dataClass.name for dart_mappable DTOs. |
| swagger_parser/test/config/swp_config_test.dart | Adds coverage for default/YAML/root override behavior + wiring into ParserConfig. |
| swagger_parser/test/src/parser/model/normalized_identifier_test.dart | Adds unit tests for String.toPreservedCase. |
| swagger_parser/test/e2e/e2e_test.dart | Registers a new e2e scenario enabling preserveSchemaCasing. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/openapi.yaml | New fixture OpenAPI spec exercising mixed-case schema names. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/rest_client.dart | Expected generated output for the new e2e fixture. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/export.dart | Expected barrel export output for the new e2e fixture. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/clients/fallback_client.dart | Expected client output for the new e2e fixture. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/models/user.dart | Expected model output showing preserved schema casing in type refs/imports. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/models/k_user_status.dart | Expected enum output preserving kUserStatus. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/models/i_os_device.dart | Expected model output preserving iOSDevice. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/models/xml_http_request.dart | Expected model output preserving XMLHttpRequest. |
| swagger_parser/test/e2e/tests/preserve_schema_casing/expected_files/models/url.dart | Expected typedef output preserving URL. |
| swagger_parser/pubspec.yaml | Bumps package version to 1.44.0. |
| swagger_parser/CHANGELOG.md | Documents the new preserve_schema_casing option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add
preserve_schema_casingoption to honour spec-author casingFixes #458
Summary
Adds an opt-in config flag
preserve_schema_casing(defaultfalse) thatprojects schema and enum names into the target language by stripping
separator characters while preserving the casing of every other character.
This lets generators emit
XMLHttpRequest,kUserStatus,iOSDevice, andURLverbatim instead of normalising them toXmlHttpRequest,KUserStatus,IOsDevice, andUrl.Currently the only way around the normalisation is
replacement_rules,which doesn't work for this case because the rules run after the
PascalCase normalisation has already destroyed the casing information.
Behaviour
When
preserve_schema_casing: true:kUserStatuskUserStatusXMLHttpRequestXMLHttpRequestiOSDeviceiOSDeviceURLURLHTTPSConnectionHTTPSConnectionUserStatusUserStatususer_statususerstatusMy-ClassMyClassXML Http RequestXMLHttpRequestThe rule is uniform: strip separator characters, preserve the casing of
every other character. No special-casing, no carve-outs.
Default (
false) is the existing PascalCase normalisation — unchanged.Implementation
The change adds one new String extension and threads a single decision
point through the existing flow:
String.toPreservedCase(new): one small extension getter inlib/src/parser/model/normalized_identifier.dartthat strips separatorcharacters and mirrors
toPascal'sPrivateprefix handling. Uses thesame
_separatorPatternalready defined at the top of the file.OpenApiCorrector: a one-line conditional swapscorrectType.toPascalfor
correctType.toPreservedCasewhen the flag is on. This is whereschema keys get rewritten in the JSON/YAML content before the parser
reads it.
OpenApiParser: a small private helperString _schemaIdentifier(String input)picks between.toPreservedCaseand
.toPascalbased on the flag. Wired into 6 call sites that handleuser-controlled schema names (enum class name, inline schema type,
$refresolutions). The corrector's YAML rewrite is skipped when the preserved
form equals the input, so the parser still has to honour the flag for
those cases. Synthesised wrapper names (
'X Union'.toPascal,'$name $additionalName'.toPascal) are intentionally leftunconditional — they should always be PascalCase.
Config wiring:
SWPConfig.preserveSchemaCasingfield + YAML keypreserve_schema_casing+toParserConfigplumbing +ParserConfig.preserveSchemaCasing.NormalizedIdentifieritself is untouched — no new fields, no newgetters on the class. The only addition is one String extension at the
bottom of the same file.
Why the DTO templates changed (and why it doesn't actually matter)
Five templates had
final className = dataClass.name.toPascal;. With theflag on, that would convert
XMLHttpRequestback toXmlHttpRequestanddefeat the preservation. The fix changes them to
final className = dataClass.name;— dropping the call entirely.This works because the parser already stores
namein its finaltarget-language form for both modes:
in legacy mode, preserved-case in flag-on mode).
_schemaIdentifier(uniqueName).In other words:
dataClass.nameis already a final identifier by thetime it reaches a template, so
.toPascalwas always redundant. Thecodebase already implicitly assumes this — see
dart_json_serializable_dto_template.dart:515 and
dart_dart_mappable_dto_template.dart:652, which
compare
variantName == dataClass.namewithout any transformation.Removing the redundant
.toPascalfrom the templates is a no-op inlegacy mode (PascalCase in → PascalCase out) and necessary in flag-on
mode (preserved-case in → preserved-case out).
UniversalComponentClass.importis not touched. It still returnsname.toPascal, exactly as upstream. The only consumer adds the resultto an
importsset that downstream templates snake-case via.toSnakefor filename derivation — and
.toSnake('XMLHttpRequest')==.toSnake('XmlHttpRequest'), so the existing.toPascalis harmlesseven in flag-on mode.
Tests
No existing tests were modified — none of the changes affect behaviour
when the flag is off, and all 421 pre-existing tests pass unchanged.
New tests added:
test/src/parser/model/normalized_identifier_test.dart— a newString.toPreservedCasegroup covering: no-separator preservation,separator stripping,
Privateprefix handling, inner-underscorestripping after a private prefix, idempotency, and empty input.
test/config/swp_config_test.dart— a newPreserve Schema Casing Configurationgroup following the same shape as the existingInclude If Null Configurationgroup (default value, YAML parsingtrue/false, root config inheritance, override,
toParserConfigwiring). Plus three assertions added to the existing
constructor/YAML-loading tests to cover the new field.
test/e2e/tests/preserve_schema_casing/— a new e2e fixturewhose OpenAPI spec exercises
kUserStatus,XMLHttpRequest,iOSDevice, andURLas top-level schemas, all referenced as fieldsof a
Usermodel. The expected generated files prove that the classnames, type references, and (snake_case) filenames all line up.
test/e2e/e2e_test.dart— one new test entry registering thefixture above.
Total suite: 422 passing.
Files touched
dart format .clean.dart analyzereports no issues.dart testpasses 422/422.