Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions swagger_parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.44.0

- Add `preserve_schema_casing` option to preserve original casing of schema-derived identifiers, defaults to `false` for backwards compatibility, which normalises to PascalCase

## 1.43.1
- Fix escaping `$unknown` in generated enum `toJson` error messages
- Fix generated code when using `json_serializable`
Expand Down
32 changes: 32 additions & 0 deletions swagger_parser/lib/src/config/swp_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SWPConfig {
this.useFlutterCompute = false,
this.generateUrlsConstants = false,
this.fieldParsers = const [],
this.preserveSchemaCasing = false,
});

/// Internal constructor of [SWPConfig]
Expand Down Expand Up @@ -98,6 +99,7 @@ class SWPConfig {
required this.useFlutterCompute,
required this.generateUrlsConstants,
required this.fieldParsers,
required this.preserveSchemaCasing,
this.fallbackUnion,
});

Expand Down Expand Up @@ -357,6 +359,9 @@ class SWPConfig {
final generateUrlsConstants = yamlMap['generate_urls_constants'] as bool? ??
rootConfig?.generateUrlsConstants;

final preserveSchemaCasing = yamlMap['preserve_schema_casing'] as bool? ??
rootConfig?.preserveSchemaCasing;

final rawFieldParsers = yamlMap['field_parsers'] as YamlList?;
List<FieldParser>? fieldParsers;
if (rawFieldParsers != null) {
Expand Down Expand Up @@ -432,6 +437,7 @@ class SWPConfig {
useFlutterCompute: useFlutterCompute ?? dc.useFlutterCompute,
includePaths: includePathsList ?? dc.includePaths,
generateUrlsConstants: generateUrlsConstants ?? dc.generateUrlsConstants,
preserveSchemaCasing: preserveSchemaCasing ?? dc.preserveSchemaCasing,
);
}

Expand Down Expand Up @@ -674,6 +680,31 @@ class SWPConfig {
/// {@endtemplate}
final List<FieldParser> fieldParsers;

/// Optional. When `true`, schema and enum names are projected into the
/// target language by stripping separator characters (spaces, dashes,
/// dots, underscores) while preserving the casing of every other
/// character. Defaults to `false` — the default behaviour, which
/// normalises every name to PascalCase and loses internal acronym and
/// lowercase-prefix casing (e.g. `XMLHttpRequest` becomes
/// `XmlHttpRequest`).
Comment thread
iOSonntag marked this conversation as resolved.
///
/// Examples (flag `true`):
/// - `kUserStatus` → `kUserStatus`
/// - `XMLHttpRequest` → `XMLHttpRequest`
/// - `iOSDevice` → `iOSDevice`
/// - `URL` → `URL`
/// - `HTTPSConnection` → `HTTPSConnection`
/// - `UserStatus` → `UserStatus`
/// - `user_status` → `userstatus`
/// - `My-Class` → `MyClass`
///
/// Useful when the spec author has deliberate casing intent the
/// generated code should honour (e.g. `XMLHttpRequest`, a `k`-prefixed
/// constant-style enum). `replacement_rules` cannot recover this because
/// the normalisation runs first; this flag opts out of the normalisation
/// instead.
final bool preserveSchemaCasing;

/// Convert [SWPConfig] to [GeneratorConfig]
GeneratorConfig toGeneratorConfig() {
return GeneratorConfig(
Expand Down Expand Up @@ -731,6 +762,7 @@ class SWPConfig {
includePaths: includePaths,
fallbackClient: fallbackClient,
inferRequiredFromNullable: inferRequiredFromNullable,
preserveSchemaCasing: preserveSchemaCasing,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ String dartDartMappableDtoTemplate(
// Use fallback union only if explicitly provided
// Auto-fallback is disabled to avoid breaking existing tests
final effectiveFallbackUnion = fallbackUnion;
final originalClassName = dataClass.name.toPascal;
final originalClassName = dataClass.name;
final discriminator = dataClass.discriminator;
final isUndiscriminatedUnion =
dataClass.undiscriminatedUnionVariants?.isNotEmpty ?? false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ String dartEnumDtoTemplate(
useFlutterCompute: useFlutterCompute,
);
} else {
final className = enumClass.name.toPascal;
final className = enumClass.name;
final jsonParam = unknownEnumValue || enumsToJson;
final asyncImport = useFlutterCompute ? "import 'dart:async';\n\n" : '';

Expand Down Expand Up @@ -63,7 +63,7 @@ String _dartEnumDartMappableTemplate(
required bool unknownEnumValue,
required bool useFlutterCompute,
}) {
final className = enumClass.name.toPascal;
final className = enumClass.name;
final jsonParam = unknownEnumValue || enumsToJson;
final asyncImport = useFlutterCompute ? "import 'dart:async';\n\n" : '';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ String dartFreezedDtoTemplate(
bool useFlutterCompute = false,
String? fallbackUnion,
}) {
final className = dataClass.name.toPascal;
final className = dataClass.name;
final discriminator = dataClass.discriminator;
final isUndiscriminatedUnion =
dataClass.undiscriminatedUnionVariants?.isNotEmpty ?? false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ String dartJsonSerializableDtoTemplate(
bool useFlutterCompute = false,
String? fallbackUnion,
}) {
final originalClassName = dataClass.name.toPascal;
final originalClassName = dataClass.name;

// Check if this is a union type
final isUnion = dataClass.discriminator != null ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:swagger_parser/src/utils/type_utils.dart';
/// Provides template for generating dart typedefs using JSON serializable
String dartTypeDefTemplate(UniversalComponentClass dataClass,
{required bool useMultipartFile}) {
final className = dataClass.name.toPascal;
final className = dataClass.name;
final type = dataClass.parameters.firstOrNull;
final import = dataClass.imports.firstOrNull;
if (type == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
enum class ${dataClass.name.toPascal} {${_parameters(dataClass)}
enum class ${dataClass.name} {${_parameters(dataClass)}
}
''';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:swagger_parser/src/generator/model/programming_language.dart';
import 'package:swagger_parser/src/parser/model/normalized_identifier.dart';
import 'package:swagger_parser/src/parser/swagger_parser_core.dart';
import 'package:swagger_parser/src/utils/base_utils.dart';
import 'package:swagger_parser/src/utils/type_utils.dart';
Expand All @@ -11,7 +10,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

${descriptionComment(dataClass.description)}@JsonClass(generateAdapter = true)
data class ${dataClass.name.toPascal}(${_parameters(dataClass.parameters)}${dataClass.parameters.isNotEmpty ? '\n)' : ')'}
data class ${dataClass.name}(${_parameters(dataClass.parameters)}${dataClass.parameters.isNotEmpty ? '\n)' : ')'}
''';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import 'package:collection/collection.dart';
import 'package:swagger_parser/src/generator/model/programming_language.dart';
import 'package:swagger_parser/src/parser/model/normalized_identifier.dart';
import 'package:swagger_parser/src/parser/swagger_parser_core.dart';
import 'package:swagger_parser/src/utils/base_utils.dart';
import 'package:swagger_parser/src/utils/type_utils.dart';

/// Provides template for generating dart typedefs using JSON serializable
String kotlinTypeDefTemplate(UniversalComponentClass dataClass) {
final className = dataClass.name.toPascal;
final className = dataClass.name;
final type = dataClass.parameters.firstOrNull;
if (type == null) {
return '';
Expand Down
14 changes: 14 additions & 0 deletions swagger_parser/lib/src/parser/config/parser_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ParserConfig {
this.includePaths,
this.fallbackClient = 'fallback',
this.inferRequiredFromNullable = false,
this.preserveSchemaCasing = false,
});

/// Specification file content as [String]
Expand Down Expand Up @@ -88,4 +89,17 @@ class ParserConfig {
/// Properties without nullable: true in schema are marked as required.
/// Only applies when schema has no explicit required array.
final bool inferRequiredFromNullable;

/// When `true`, schema and enum names are projected by stripping
/// separator characters while preserving the casing of every other
/// character.
///
/// Examples (flag `true`):
/// - `kUserStatus` → `kUserStatus`
/// - `XMLHttpRequest` → `XMLHttpRequest`
/// - `iOSDevice` → `iOSDevice`
/// - `URL` → `URL`
/// - `user_status` → `userstatus`
/// - `My-Class` → `MyClass`
final bool preserveSchemaCasing;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ class OpenApiCorrector {
for (final rule in config.replacementRules) {
correctType = rule.apply(correctType)!;
}
correctType = correctType.toPascal;
correctType = config.preserveSchemaCasing
? correctType.toPreservedCase
: correctType.toPascal;
if (correctType != type) {
typeCorrections[type] = correctType;
}
Expand Down
27 changes: 27 additions & 0 deletions swagger_parser/lib/src/parser/model/normalized_identifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,33 @@ extension StringToCaseX on String {
: identifier.pascalCase;
}

/// Strip separator characters (spaces, dashes, dots, underscores, etc.)
/// while preserving the casing of every other character.
///
/// - `XMLHttpRequest` → `XMLHttpRequest`
/// - `kUserStatus` → `kUserStatus`
/// - `iOSDevice` → `iOSDevice`
/// - `URL` → `URL`
/// - `user_status` → `userstatus`
/// - `My-Class` → `MyClass`
/// - `XML Http Request` → `XMLHttpRequest`
///
/// Mirrors [toPascal]'s `Private` prefix handling for inputs that start
/// with an underscore.
///
/// Used by the `preserve_schema_casing` config option to project
/// spec-author identifiers into the target language verbatim where
/// possible.
String get toPreservedCase {
if (isEmpty) {
return '';
}
final isPrivate = startsWith('_');
final body = isPrivate ? substring(1) : this;
final stripped = body.replaceAll(_separatorPattern, '');
return isPrivate ? 'Private$stripped' : stripped;
}

/// Return text formatted to snake_case
///
/// The result is prefixed with `private_` if the given text indicates a private entity
Expand Down
20 changes: 13 additions & 7 deletions swagger_parser/lib/src/parser/parser/open_api_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class OpenApiParser {
/// [ParserConfig] that [OpenApiParser] use
final ParserConfig config;

/// Normalises a schema-derived identifier — preserves the spec author's
/// casing (stripping separators only) when [ParserConfig.preserveSchemaCasing]
/// is on, otherwise falls back to PascalCase.
String _schemaIdentifier(String input) =>
config.preserveSchemaCasing ? input.toPreservedCase : input.toPascal;

/// `info` section in specification
late final OpenApiInfo _apiInfo;

Expand Down Expand Up @@ -129,7 +135,7 @@ class OpenApiParser {

return UniversalEnumClass(
originalName: name,
name: uniqueName.toPascal,
name: _schemaIdentifier(uniqueName),
type: type,
items: items,
defaultValue: defaultValue,
Expand Down Expand Up @@ -1533,7 +1539,7 @@ class OpenApiParser {

final (parameters, imports) = _findParametersAndImports(map);

var type = newName.toPascal;
var type = _schemaIdentifier(newName);

for (final replacementRule in config.replacementRules) {
type = replacementRule.apply(type)!;
Expand Down Expand Up @@ -1991,15 +1997,15 @@ class OpenApiParser {
String? import;
String type;
if (map.containsKey(_refConst)) {
import = _formatRef(map).toPascal;
import = _schemaIdentifier(_formatRef(map));
} else if (map.containsKey(_additionalPropertiesConst) &&
map[_additionalPropertiesConst] is Map<String, dynamic> &&
(map[_additionalPropertiesConst] as Map<String, dynamic>).containsKey(
_refConst,
)) {
import = _formatRef(
import = _schemaIdentifier(_formatRef(
map[_additionalPropertiesConst] as Map<String, dynamic>,
).toPascal;
));
}

if (map.containsKey(_typeConst)) {
Expand All @@ -2010,7 +2016,7 @@ class OpenApiParser {
type = import ?? _objectConst;
}
if (import != null) {
type = type.toPascal;
type = _schemaIdentifier(type);
}

final defaultValue = map[_defaultConst]?.toString();
Expand Down Expand Up @@ -2178,7 +2184,7 @@ class OpenApiParser {

for (final item in otherItems) {
if (item.containsKey(_refConst)) {
final refName = _formatRef(item).toPascal;
final refName = _schemaIdentifier(_formatRef(item));

// Locate the referenced component to get its properties if available.
if (_definitionFileContent
Expand Down
2 changes: 1 addition & 1 deletion swagger_parser/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: swagger_parser
description: Package that generates REST clients and data classes from OpenApi definition file
version: 1.43.1
version: 1.44.0
repository: https://github.com/Carapacik/swagger_parser
topics:
- swagger
Expand Down
Loading