Skip to content

bugfix/preserve-casing-via-config#459

Open
iOSonntag wants to merge 4 commits into
Carapacik:mainfrom
iOSonntag:bugfix/preserve-casing-via-config
Open

bugfix/preserve-casing-via-config#459
iOSonntag wants to merge 4 commits into
Carapacik:mainfrom
iOSonntag:bugfix/preserve-casing-via-config

Conversation

@iOSonntag
Copy link
Copy Markdown

Add preserve_schema_casing option to honour spec-author casing

Fixes #458

Summary

Adds an opt-in config flag preserve_schema_casing (default false) that
projects 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, and
URL verbatim instead of normalising them to XmlHttpRequest,
KUserStatus, IOsDevice, and Url.

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:

Schema name Output Note
kUserStatus kUserStatus lowercase prefix preserved
XMLHttpRequest XMLHttpRequest internal acronym preserved
iOSDevice iOSDevice mixed-case prefix preserved
URL URL all-caps preserved
HTTPSConnection HTTPSConnection
UserStatus UserStatus unchanged
user_status userstatus separator stripped, rest verbatim
My-Class MyClass separator stripped
XML Http Request XMLHttpRequest spaces stripped, casing kept

The 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 in
    lib/src/parser/model/normalized_identifier.dart that strips separator
    characters and mirrors toPascal's Private prefix handling. Uses the
    same _separatorPattern already defined at the top of the file.

  • OpenApiCorrector: a one-line conditional swaps correctType.toPascal
    for correctType.toPreservedCase when the flag is on. This is where
    schema keys get rewritten in the JSON/YAML content before the parser
    reads it.

  • OpenApiParser: a small private helper
    String _schemaIdentifier(String input) picks between .toPreservedCase
    and .toPascal based on the flag. Wired into 6 call sites that handle
    user-controlled schema names (enum class name, inline schema type, $ref
    resolutions). 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 left
    unconditional — they should always be PascalCase.

  • Config wiring: SWPConfig.preserveSchemaCasing field + YAML key
    preserve_schema_casing + toParserConfig plumbing +
    ParserConfig.preserveSchemaCasing.

NormalizedIdentifier itself is untouched — no new fields, no new
getters 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 the
flag on, that would convert XMLHttpRequest back to XmlHttpRequest and
defeat the preservation. The fix changes them to
final className = dataClass.name; — dropping the call entirely.

This works because the parser already stores name in its final
target-language form for both modes:

  • Component class names are assigned from post-corrector keys (PascalCase
    in legacy mode, preserved-case in flag-on mode).
  • Enum class names go through _schemaIdentifier(uniqueName).
  • Synthesised wrapper names are pascalised at construction.

In other words: dataClass.name is already a final identifier by the
time it reaches a template, so .toPascal was always redundant. The
codebase already implicitly assumes this — see
dart_json_serializable_dto_template.dart:515 and
dart_dart_mappable_dto_template.dart:652, which
compare variantName == dataClass.name without any transformation.

Removing the redundant .toPascal from the templates is a no-op in
legacy mode (PascalCase in → PascalCase out) and necessary in flag-on
mode (preserved-case in → preserved-case out).

UniversalComponentClass.import is not touched. It still returns
name.toPascal, exactly as upstream. The only consumer adds the result
to an imports set that downstream templates snake-case via .toSnake
for filename derivation — and .toSnake('XMLHttpRequest') ==
.toSnake('XmlHttpRequest'), so the existing .toPascal is harmless
even 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 new
    String.toPreservedCase group covering: no-separator preservation,
    separator stripping, Private prefix handling, inner-underscore
    stripping after a private prefix, idempotency, and empty input.
  • test/config/swp_config_test.dart — a new Preserve Schema Casing Configuration group following the same shape as the existing
    Include If Null Configuration group (default value, YAML parsing
    true/false, root config inheritance, override, toParserConfig
    wiring). 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 fixture
    whose OpenAPI spec exercises kUserStatus, XMLHttpRequest,
    iOSDevice, and URL as top-level schemas, all referenced as fields
    of a User model. The expected generated files prove that the class
    names, type references, and (snake_case) filenames all line up.
  • test/e2e/e2e_test.dart — one new test entry registering the
    fixture above.

Total suite: 422 passing.

Files touched

 swagger_parser/lib/src/config/swp_config.dart                          | +32
 swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart | ±1
 swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart          | ±2
 swagger_parser/lib/src/generator/templates/dart_freezed_dto_template.dart       | ±1
 swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart | ±1
 swagger_parser/lib/src/generator/templates/dart_typedef_template.dart           | ±1
 swagger_parser/lib/src/parser/config/parser_config.dart                | +14
 swagger_parser/lib/src/parser/corrector/open_api_corrector.dart        | ±4
 swagger_parser/lib/src/parser/model/normalized_identifier.dart         | +27
 swagger_parser/lib/src/parser/parser/open_api_parser.dart              | ±20
 swagger_parser/test/config/swp_config_test.dart                        | +78
 swagger_parser/test/e2e/e2e_test.dart                                  | +14
 swagger_parser/test/src/parser/model/normalized_identifier_test.dart   | +49
 swagger_parser/test/e2e/tests/preserve_schema_casing/                  | new fixture

dart format . clean. dart analyze reports no issues. dart test
passes 422/422.

Copilot AI review requested due to automatic review settings May 17, 2026 08:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.toPreservedCase and 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, and URL.

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.

Comment thread swagger_parser/lib/src/config/swp_config.dart
Comment thread swagger_parser/lib/src/parser/model/normalized_identifier.dart Outdated
@Carapacik Carapacik self-requested a review May 21, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: lower case enum converted to upper case + fixing PR provided

2 participants