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
12 changes: 12 additions & 0 deletions bin/configs/swift5-frozenEnums.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
generatorName: swift5
outputDir: samples/client/petstore/swift5/frozenEnums
inputSpec: modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml
templateDir: modules/openapi-generator/src/main/resources/swift5
generateAliasAsModel: true
additionalProperties:
podAuthors: ""
podSummary: PetstoreClient
sortParamsByRequiredFlag: false
generateFrozenEnums: false
projectName: PetstoreClient
podHomepage: https://github.com/openapitools/openapi-generator
1 change: 1 addition & 0 deletions docs/generators/swift5.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiNamePrefix|Prefix that will be appended to all API names ('tags'). Default: empty string. e.g. Pet => Pet.| |null|
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|generateFrozenEnums|Generate model enums that are "frozen". A frozen enum strictly represents the cases described in the spec, and nothing else. A non-frozen enum includes an unknown case that can represent future cases not yet described in the spec. (default: true)| |true|
|generateModelAdditionalProperties|Generate model additional properties (default: true)| |true|
|hashableModels|Make hashable models (default: true)| |true|
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
public static final String SWIFT_PACKAGE_PATH = "swiftPackagePath";
public static final String USE_CLASSES = "useClasses";
public static final String USE_BACKTICK_ESCAPES = "useBacktickEscapes";
public static final String GENERATE_FROZEN_ENUMS = "generateFrozenEnums";
public static final String GENERATE_MODEL_ADDITIONAL_PROPERTIES = "generateModelAdditionalProperties";
public static final String HASHABLE_MODELS = "hashableModels";
public static final String MAP_FILE_BINARY_TO_DATA = "mapFileBinaryToData";
Expand All @@ -90,6 +91,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
protected boolean useClasses = false;
protected boolean useBacktickEscapes = false;
protected boolean generateModelAdditionalProperties = true;
protected boolean generateFrozenEnums = true;
protected boolean hashableModels = true;
protected boolean mapFileBinaryToData = false;
protected String[] responseAs = new String[0];
Expand Down Expand Up @@ -281,6 +283,9 @@ public Swift5ClientCodegen() {
cliOptions.add(new CliOption(USE_BACKTICK_ESCAPES,
"Escape reserved words using backticks (default: false)")
.defaultValue(Boolean.FALSE.toString()));
cliOptions.add(new CliOption(GENERATE_FROZEN_ENUMS,
"Generate frozen enums (default: true)")
.defaultValue(Boolean.TRUE.toString()));
cliOptions.add(new CliOption(GENERATE_MODEL_ADDITIONAL_PROPERTIES,
"Generate model additional properties (default: true)")
.defaultValue(Boolean.TRUE.toString()));
Expand Down Expand Up @@ -485,6 +490,13 @@ public void processOpts() {
setUseBacktickEscapes(convertPropertyToBooleanAndWriteBack(USE_BACKTICK_ESCAPES));
}

// Setup generateFrozenEnums option. If true, enums will strictly include
// cases matching the spec. If false, enums will also include an extra catch-all case.
if (additionalProperties.containsKey(GENERATE_FROZEN_ENUMS)) {
setGenerateFrozenEnums(convertPropertyToBooleanAndWriteBack(GENERATE_FROZEN_ENUMS));
}
additionalProperties.put(GENERATE_FROZEN_ENUMS, generateFrozenEnums);

if (additionalProperties.containsKey(GENERATE_MODEL_ADDITIONAL_PROPERTIES)) {
setGenerateModelAdditionalProperties(convertPropertyToBooleanAndWriteBack(GENERATE_MODEL_ADDITIONAL_PROPERTIES));
}
Expand Down Expand Up @@ -943,6 +955,10 @@ public void setUseBacktickEscapes(boolean useBacktickEscapes) {
this.useBacktickEscapes = useBacktickEscapes;
}

public void setGenerateFrozenEnums(boolean generateFrozenEnums) {
this.generateFrozenEnums = generateFrozenEnums;
}

public void setGenerateModelAdditionalProperties(boolean generateModelAdditionalProperties) {
this.generateModelAdditionalProperties = generateModelAdditionalProperties;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}, CaseIterable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}, CaseIterable{{^generateFrozenEnums}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{/generateFrozenEnums}} {
{{#allowableValues}}
{{#enumVars}}
case {{{name}}} = {{{value}}}
{{/enumVars}}
{{/allowableValues}}
{{^generateFrozenEnums}}
// This case is a catch-all generated by OpenAPI such that the enum is "non-frozen".
// If new enum cases are added that are unknown to the spec/client, they are safely
// decoded to this case. The raw value of this case is a dummy value that attempts
// to avoids collisions with previously specified cases.
{{#isString}}
Copy link
Contributor

Choose a reason for hiding this comment

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

@jarrodparkes should we support more types? Maybe Double, Float, and more?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

now that we are specifying the raw values, yes 😄 — will add shortly

case unknownDefaultOpenAPI = "unknown_default_open_api"
{{/isString}}
{{#isNumeric}}
//
// 192, used to calculate the raw value, was the Swift Evolution proposal for
// frozen/non-frozen enums.
// [SE-0192](https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md)
//
{{/isNumeric}}
{{#isInteger}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isInteger}}
{{#isDouble}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isDouble}}
{{#isFloat}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isFloat}}
{{/generateFrozenEnums}}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}, CaseIterable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}, CaseIterable{{^generateFrozenEnums}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{#isContainer}}, CaseIterableDefaultsLast{{/isContainer}}{{/generateFrozenEnums}} {
{{#allowableValues}}
{{#enumVars}}
case {{{name}}} = {{{value}}}
{{/enumVars}}
{{/allowableValues}}
{{^generateFrozenEnums}}
Copy link
Contributor

Choose a reason for hiding this comment

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

@jarrodparkes please apply all the feedback from modelEnum.mustache also to this class

// This case is a catch-all generated by OpenAPI such that the enum is "non-frozen".
// If new enum cases are added that are unknown to the spec/client, they are safely
// decoded to this case. The raw value of this case is a dummy value that attempts
// to avoids collisions with previously specified cases.
{{#isString}}
case unknownDefaultOpenAPI = "unknown_default_open_api"
{{/isString}}
{{#isContainer}}
Copy link
Contributor

Choose a reason for hiding this comment

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

@jarrodparkes should this logic of the {{#isContainer}} be added to the file modelEnum.mustache?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@4brunu

I don't believe so — the only thing that gave me the hunch to use {{#isContainer}} on modelInlineEnumDeclaration.mustache was the fact that I already saw that clause used earlier in the file to help construct the enum's raw type. If you look at modelEnum.mustache, no such clause exists.

Copy link
Contributor

Choose a reason for hiding this comment

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

You are right 👍

case unknownDefaultOpenAPI = "unknown_default_open_api"
{{/isContainer}}
{{#isNumeric}}
//
// 192, used to calculate the raw value, was the Swift Evolution proposal for
// frozen/non-frozen enums.
// [SE-0192](https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md)
//
{{/isNumeric}}
{{#isInteger}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isInteger}}
{{#isDouble}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isDouble}}
{{#isFloat}}
case unknownDefaultOpenAPI = -11184809 // Int.min / 192
{{/isFloat}}
{{/generateFrozenEnums}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class Swift5OptionsProvider implements OptionsProvider {
public static final String REMOVE_MIGRATION_PROJECT_NAME_CLASS_VALUE = "false";
public static final String SWIFT_USE_API_NAMESPACE_VALUE = "swiftUseApiNamespace";
public static final String USE_BACKTICKS_ESCAPES_VALUE = "false";
public static final String GENERATE_FROZEN_ENUMS_VALUE = "true";
public static final String GENERATE_MODEL_ADDITIONAL_PROPERTIES_VALUE = "true";
public static final String HASHABLE_MODELS_VALUE = "true";
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
Expand Down Expand Up @@ -95,6 +96,7 @@ public Map<String, String> createOptions() {
.put(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, "true")
.put(Swift5ClientCodegen.USE_SPM_FILE_STRUCTURE, USE_SPM_FILE_STRUCTURE_VALUE)
.put(Swift5ClientCodegen.SWIFT_PACKAGE_PATH, SWIFT_PACKAGE_PATH_VALUE)
.put(Swift5ClientCodegen.GENERATE_FROZEN_ENUMS, GENERATE_FROZEN_ENUMS_VALUE)
.put(Swift5ClientCodegen.GENERATE_MODEL_ADDITIONAL_PROPERTIES, GENERATE_MODEL_ADDITIONAL_PROPERTIES_VALUE)
.put(Swift5ClientCodegen.HASHABLE_MODELS, HASHABLE_MODELS_VALUE)
.put(Swift5ClientCodegen.MAP_FILE_BINARY_TO_DATA, "false")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ protected void verifyOptions() {
verify(clientCodegen).setPrependFormOrBodyParameters(Boolean.valueOf(Swift5OptionsProvider.PREPEND_FORM_OR_BODY_PARAMETERS_VALUE));
verify(clientCodegen).setReadonlyProperties(Boolean.parseBoolean(Swift5OptionsProvider.READONLY_PROPERTIES_VALUE));
verify(clientCodegen).setRemoveMigrationProjectNameClass(Boolean.parseBoolean(Swift5OptionsProvider.REMOVE_MIGRATION_PROJECT_NAME_CLASS_VALUE));
verify(clientCodegen).setGenerateFrozenEnums(Boolean.parseBoolean(Swift5OptionsProvider.GENERATE_FROZEN_ENUMS_VALUE));
verify(clientCodegen).setGenerateModelAdditionalProperties(Boolean.parseBoolean(Swift5OptionsProvider.GENERATE_MODEL_ADDITIONAL_PROPERTIES_VALUE));
verify(clientCodegen).setHashableModels(Boolean.parseBoolean(Swift5OptionsProvider.HASHABLE_MODELS_VALUE));
}
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,7 @@
<module>samples/client/petstore/swift5/combineLibrary</module>
<module>samples/client/petstore/swift5/default</module>
<module>samples/client/petstore/swift5/deprecated</module>
<module>samples/client/petstore/swift5/frozenEnums</module>
<module>samples/client/petstore/swift5/nonPublicApi</module>
<module>samples/client/petstore/swift5/objcCompatible</module>
<module>samples/client/petstore/swift5/promisekitLibrary</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

public enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

public enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

public enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

public enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ extension JSONEncodable {
func encodeToJSON() -> Any { self }
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
public init(from decoder: Decoder) throws {
if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) {
self = value
} else if let lastValue = Self.allCases.last {
self = lastValue
} else {
throw DecodingError.valueNotFound(
Self.Type.self,
.init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast")
)
}
}
}

public enum ErrorResponse: Error {
case error(Int, Data?, URLResponse?, Error)
}
Expand Down
Loading