From 73f52665963c291796b4ab9c344dbea5de5b4ec4 Mon Sep 17 00:00:00 2001 From: Romain d'Alverny Date: Thu, 8 Jun 2017 03:10:21 +0200 Subject: [PATCH] [Swift3] Set more liberal Swift3 decoder, behind lenientTypeCast option (#5795) * [Swift3] Add lenientTypeCast option When set to true, this generates a client JSON decoder that will accept and cast mistyped values. Here: - String => Bool ("true" instead of true), - String => Int ("123" instead of 123), - NSNumber => String (123 instead of "123"). The point is to allow the same client code to handle several server implementations that may (sadly) not be up to spec, or still be "evolving". The conversion is not guaranteed if the input Not a perfect/complete solution, not sure if it should be activated along other casts, so kept behind an option. * Update Petstore client code --- .../codegen/languages/Swift3Codegen.java | 12 ++- .../src/main/resources/swift3/Models.mustache | 20 ++++- .../options/Swift3OptionsProvider.java | 2 + .../codegen/swift3/Swift3OptionsTest.java | 2 + .../Classes/Swaggers/Models.swift | 82 +++++++++---------- 5 files changed, 73 insertions(+), 45 deletions(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/Swift3Codegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/Swift3Codegen.java index 1f167ae4a369..d73e1eaf71a0 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/Swift3Codegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/Swift3Codegen.java @@ -39,11 +39,13 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig { public static final String POD_DOCUMENTATION_URL = "podDocumentationURL"; public static final String SWIFT_USE_API_NAMESPACE = "swiftUseApiNamespace"; public static final String DEFAULT_POD_AUTHORS = "Swagger Codegen"; + public static final String LENIENT_TYPE_CAST = "lenientTypeCast"; protected static final String LIBRARY_PROMISE_KIT = "PromiseKit"; protected static final String LIBRARY_RX_SWIFT = "RxSwift"; protected static final String[] RESPONSE_LIBRARIES = {LIBRARY_PROMISE_KIT, LIBRARY_RX_SWIFT}; protected String projectName = "SwaggerClient"; protected boolean unwrapRequired; + protected boolean lenientTypeCast = false; protected boolean swiftUseApiNamespace; protected String[] responseAs = new String[0]; protected String sourceFolder = "Classes" + File.separator + "Swaggers"; @@ -171,12 +173,14 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig { cliOptions.add(new CliOption(SWIFT_USE_API_NAMESPACE, "Flag to make all the API classes inner-class of {{projectName}}API")); cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") .defaultValue(Boolean.TRUE.toString())); - + cliOptions.add(new CliOption(LENIENT_TYPE_CAST, "Accept and cast values for simple types (string->bool, string->int, int->string)") + .defaultValue(Boolean.FALSE.toString())); } @Override public void processOpts() { super.processOpts(); + // default HIDE_GENERATION_TIMESTAMP to true if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); @@ -225,6 +229,8 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig { additionalProperties.put(POD_AUTHORS, DEFAULT_POD_AUTHORS); } + setLenientTypeCast(convertPropertyToBooleanAndWriteBack(LENIENT_TYPE_CAST)); + supportingFiles.add(new SupportingFile("Podspec.mustache", "", projectName + ".podspec")); supportingFiles.add(new SupportingFile("Cartfile.mustache", "", "Cartfile")); supportingFiles.add(new SupportingFile("APIHelper.mustache", sourceFolder, "APIHelper.swift")); @@ -473,6 +479,10 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig { this.unwrapRequired = unwrapRequired; } + public void setLenientTypeCast(boolean lenientTypeCast) { + this.lenientTypeCast = lenientTypeCast; + } + public void setResponseAs(String[] responseAs) { this.responseAs = responseAs; } diff --git a/modules/swagger-codegen/src/main/resources/swift3/Models.mustache b/modules/swagger-codegen/src/main/resources/swift3/Models.mustache index 7f6e5bd94034..43b63fe4186a 100644 --- a/modules/swagger-codegen/src/main/resources/swift3/Models.mustache +++ b/modules/swagger-codegen/src/main/resources/swift3/Models.mustache @@ -70,10 +70,10 @@ class Decoders { static func decode(clazz: T.Type, source: AnyObject, instance: AnyObject?) -> T { initialize() if T.self is Int32.Type && source is NSNumber { - return (source as! NSNumber).int32Value as! T; + return (source as! NSNumber).int32Value as! T } if T.self is Int64.Type && source is NSNumber { - return source.int64Value as! T; + return (source as! NSNumber).int64Value as! T } if T.self is UUID.Type && source is String { return UUID(uuidString: source as! String) as! T @@ -84,6 +84,20 @@ class Decoders { if T.self is Data.Type && source is String { return Data(base64Encoded: source as! String) as! T } + {{#lenientTypeCast}} + if T.self is Int32.Type && source is String { + return (source as! NSString).intValue as! T + } + if T.self is Int64.Type && source is String { + return (source as! NSString).intValue as! T + } + if T.self is Bool.Type && source is String { + return (source as! NSString).boolValue as! T + } + if T.self is String.Type && source is NSNumber { + return String(describing: source) as! T + } + {{/lenientTypeCast}} let key = "\(T.self)" if let decoder = decoders[key] { @@ -234,4 +248,4 @@ class Decoders { static fileprivate func initialize() { _ = Decoders.__once } -} \ No newline at end of file +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/Swift3OptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/Swift3OptionsProvider.java index 4e4bed0f8267..0a72783a1593 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/Swift3OptionsProvider.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/Swift3OptionsProvider.java @@ -13,6 +13,7 @@ public class Swift3OptionsProvider implements OptionsProvider { public static final String PROJECT_NAME_VALUE = "Swagger"; public static final String RESPONSE_AS_VALUE = "test"; public static final String UNWRAP_REQUIRED_VALUE = "true"; + public static final String LENIENT_TYPE_CAST_VALUE = "false"; public static final String POD_SOURCE_VALUE = "{ :git => 'git@github.com:swagger-api/swagger-mustache.git'," + " :tag => 'v1.0.0-SNAPSHOT' }"; public static final String POD_VERSION_VALUE = "v1.0.0-SNAPSHOT"; @@ -41,6 +42,7 @@ public class Swift3OptionsProvider implements OptionsProvider { .put(Swift3Codegen.PROJECT_NAME, PROJECT_NAME_VALUE) .put(Swift3Codegen.RESPONSE_AS, RESPONSE_AS_VALUE) .put(Swift3Codegen.UNWRAP_REQUIRED, UNWRAP_REQUIRED_VALUE) + .put(Swift3Codegen.LENIENT_TYPE_CAST, LENIENT_TYPE_CAST_VALUE) .put(Swift3Codegen.POD_SOURCE, POD_SOURCE_VALUE) .put(CodegenConstants.POD_VERSION, POD_VERSION_VALUE) .put(Swift3Codegen.POD_AUTHORS, POD_AUTHORS_VALUE) diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/swift3/Swift3OptionsTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/swift3/Swift3OptionsTest.java index ddfc65bc9080..7e0166926ab7 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/swift3/Swift3OptionsTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/swift3/Swift3OptionsTest.java @@ -33,6 +33,8 @@ public class Swift3OptionsTest extends AbstractOptionsTest { times = 1; clientCodegen.setUnwrapRequired(Boolean.valueOf(Swift3OptionsProvider.UNWRAP_REQUIRED_VALUE)); times = 1; + clientCodegen.setLenientTypeCast(Boolean.valueOf(Swift3OptionsProvider.LENIENT_TYPE_CAST_VALUE)); + times = 1; }}; } } diff --git a/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/Models.swift b/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/Models.swift index eab5861e8ef2..4ca7ad3a8e08 100644 --- a/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/Models.swift +++ b/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/Models.swift @@ -70,10 +70,10 @@ class Decoders { static func decode(clazz: T.Type, source: AnyObject, instance: AnyObject?) -> T { initialize() if T.self is Int32.Type && source is NSNumber { - return (source as! NSNumber).int32Value as! T; + return (source as! NSNumber).int32Value as! T } if T.self is Int64.Type && source is NSNumber { - return source.int64Value as! T; + return (source as! NSNumber).int64Value as! T } if T.self is UUID.Type && source is String { return UUID(uuidString: source as! String) as! T @@ -273,25 +273,6 @@ class Decoders { } - // Decoder for [Cat] - Decoders.addDecoder(clazz: [Cat].self) { (source: AnyObject, instance: AnyObject?) -> [Cat] in - return Decoders.decode(clazz: [Cat].self, source: source) - } - // Decoder for Cat - Decoders.addDecoder(clazz: Cat.self) { (source: AnyObject, instance: AnyObject?) -> Cat in - let sourceDictionary = source as! [AnyHashable: Any] - let result = instance == nil ? Cat() : instance as! Cat - if decoders["\(Animal.self)"] != nil { - _ = Decoders.decode(clazz: Animal.self, source: source, instance: result) - } - - result.className = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["className"] as AnyObject?) - result.color = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["color"] as AnyObject?) - result.declawed = Decoders.decodeOptional(clazz: Bool.self, source: sourceDictionary["declawed"] as AnyObject?) - return result - } - - // Decoder for [Category] Decoders.addDecoder(clazz: [Category].self) { (source: AnyObject, instance: AnyObject?) -> [Category] in return Decoders.decode(clazz: [Category].self, source: source) @@ -335,25 +316,6 @@ class Decoders { } - // Decoder for [Dog] - Decoders.addDecoder(clazz: [Dog].self) { (source: AnyObject, instance: AnyObject?) -> [Dog] in - return Decoders.decode(clazz: [Dog].self, source: source) - } - // Decoder for Dog - Decoders.addDecoder(clazz: Dog.self) { (source: AnyObject, instance: AnyObject?) -> Dog in - let sourceDictionary = source as! [AnyHashable: Any] - let result = instance == nil ? Dog() : instance as! Dog - if decoders["\(Animal.self)"] != nil { - _ = Decoders.decode(clazz: Animal.self, source: source, instance: result) - } - - result.className = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["className"] as AnyObject?) - result.color = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["color"] as AnyObject?) - result.breed = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["breed"] as AnyObject?) - return result - } - - // Decoder for [EnumArrays] Decoders.addDecoder(clazz: [EnumArrays].self) { (source: AnyObject, instance: AnyObject?) -> [EnumArrays] in return Decoders.decode(clazz: [EnumArrays].self, source: source) @@ -741,9 +703,47 @@ class Decoders { result.userStatus = Decoders.decodeOptional(clazz: Int32.self, source: sourceDictionary["userStatus"] as AnyObject?) return result } + + + // Decoder for [Cat] + Decoders.addDecoder(clazz: [Cat].self) { (source: AnyObject, instance: AnyObject?) -> [Cat] in + return Decoders.decode(clazz: [Cat].self, source: source) + } + // Decoder for Cat + Decoders.addDecoder(clazz: Cat.self) { (source: AnyObject, instance: AnyObject?) -> Cat in + let sourceDictionary = source as! [AnyHashable: Any] + let result = instance == nil ? Cat() : instance as! Cat + if decoders["\(Animal.self)"] != nil { + _ = Decoders.decode(clazz: Animal.self, source: source, instance: result) + } + + result.className = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["className"] as AnyObject?) + result.color = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["color"] as AnyObject?) + result.declawed = Decoders.decodeOptional(clazz: Bool.self, source: sourceDictionary["declawed"] as AnyObject?) + return result + } + + + // Decoder for [Dog] + Decoders.addDecoder(clazz: [Dog].self) { (source: AnyObject, instance: AnyObject?) -> [Dog] in + return Decoders.decode(clazz: [Dog].self, source: source) + } + // Decoder for Dog + Decoders.addDecoder(clazz: Dog.self) { (source: AnyObject, instance: AnyObject?) -> Dog in + let sourceDictionary = source as! [AnyHashable: Any] + let result = instance == nil ? Dog() : instance as! Dog + if decoders["\(Animal.self)"] != nil { + _ = Decoders.decode(clazz: Animal.self, source: source, instance: result) + } + + result.className = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["className"] as AnyObject?) + result.color = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["color"] as AnyObject?) + result.breed = Decoders.decodeOptional(clazz: String.self, source: sourceDictionary["breed"] as AnyObject?) + return result + } }() static fileprivate func initialize() { _ = Decoders.__once } -} \ No newline at end of file +}