Skip to content

Commit

Permalink
Swift4: make generated models structs instead of classes (#7345)
Browse files Browse the repository at this point in the history
* Split up model template into partials

* Change models from class to struct.

This fixes issue #6941.

In this change, we make our Swift4 generated model objects struct instead of class. However, in order to do this, we needed to handle the following edge cases:

* Inheritance and polymorphism (allOf)
  * With classes, we use inheritance. So therefore, the parent properties are ONLY on the parent generated class, and the model object which derives from the parent class picks up those properties through inheritance.
  * However, structs do not support inheritance. So we simply duplicate the parent allOf properties in the child struct.
* We have to handle the case where the property name on the struct may be different than the property name in the JSON. By default, the Codable protocol assumes that the JSON property name is the same as the struct property name. If they need to be different, then we generate a CodingKeys string enum, which contains the mapping between struct property name and JSON property name.
* additionalProperties. We cannot use the default Codable implementation for the additionalProperties, since it will look for an actual dictionary called "additionalProperties" in the JSON. Therefore, for model objects which have additionalProperties, we must generate our own implementation for the Decodable and Encodable protocols.

I have run ./bin/swift4-all.sh and ./bin/swift4-test.sh to re-generate all of the sources, and I have verified that the generated code in samples/clients/test/swift4/default builds and the unit tests pass.

* Update VERSION in .swagger-codegen

* Update generated code for swift4-test schema
  • Loading branch information
ehyche authored and wing328 committed Jan 25, 2018
1 parent 2b84118 commit a3d0f1d
Show file tree
Hide file tree
Showing 180 changed files with 1,350 additions and 3,980 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,38 @@ public String toEnumName(CodegenProperty property) {

@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
// process enum in models
return postProcessModelsEnum(objs);
Map<String, Object> postProcessedModelsEnum = postProcessModelsEnum(objs);

// We iterate through the list of models, and also iterate through each of the
// properties for each model. For each property, if:
//
// CodegenProperty.name != CodegenProperty.baseName
//
// then we set
//
// CodegenProperty.vendorExtensions["x-codegen-escaped-property-name"] = true
//
// Also, if any property in the model has x-codegen-escaped-property-name=true, then we mark:
//
// CodegenModel.vendorExtensions["x-codegen-has-escaped-property-names"] = true
//
List<Object> models = (List<Object>) postProcessedModelsEnum.get("models");
for (Object _mo : models) {
Map<String, Object> mo = (Map<String, Object>) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
boolean modelHasPropertyWithEscapedName = false;
for (CodegenProperty prop : cm.allVars) {
if (!prop.name.equals(prop.baseName)) {
prop.vendorExtensions.put("x-codegen-escaped-property-name", true);
modelHasPropertyWithEscapedName = true;
}
}
if (modelHasPropertyWithEscapedName) {
cm.vendorExtensions.put("x-codegen-has-escaped-property-names", true);
}
}

return postProcessedModelsEnum;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{/parent}} {
public struct {{classname}}: Codable {
{{#vars}}
{{#allVars}}
{{#isEnum}}
{{> modelInlineEnumDeclaration}}
{{/isEnum}}
{{/vars}}
{{#vars}}
{{/allVars}}
{{#allVars}}
{{#isEnum}}
{{#description}}/** {{description}} */
{{/description}}public var {{name}}: {{{datatypeWithEnum}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
Expand All @@ -20,7 +20,7 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
}
}{{/vendorExtensions.x-swift-optional-scalar}}{{/objcCompatible}}
{{/isEnum}}
{{/vars}}
{{/allVars}}

{{#additionalPropertiesType}}
public var additionalProperties: [String:{{{additionalPropertiesType}}}] = [:]
Expand All @@ -37,47 +37,39 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
additionalProperties[key] = newValue
}
}
{{/additionalPropertiesType}}

{{^parent}}{{#hasVars}}
public init({{#vars}}{{name}}: {{{datatypeWithEnum}}}{{^required}}?{{/required}}{{#hasMore}}, {{/hasMore}}{{/vars}}) {
{{#vars}}
self.{{name}} = {{name}}
{{/vars}}
}
{{/hasVars}}{{/parent}}

// Encodable protocol methods

public {{#parent}}override {{/parent}}func encode(to encoder: Encoder) throws {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: String.self)
{{#vars}}
{{#allVars}}
try container.encode{{^required}}IfPresent{{/required}}({{{name}}}, forKey: "{{{baseName}}}")
{{/vars}}
{{#additionalPropertiesType}}
{{/allVars}}
try container.encodeMap(additionalProperties)
{{/additionalPropertiesType}}
}

// Decodable protocol methods

public required init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)
{{#vars}}
{{#allVars}}
{{name}} = try container.decode{{^required}}IfPresent{{/required}}({{{datatypeWithEnum}}}.self, forKey: "{{{baseName}}}")
{{/vars}}
{{#additionalPropertiesType}}
{{/allVars}}
var nonAdditionalPropertyKeys = Set<String>()
{{#vars}}
{{#allVars}}
nonAdditionalPropertyKeys.insert("{{{baseName}}}")
{{/vars}}
{{/allVars}}
additionalProperties = try container.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys)
{{/additionalPropertiesType}}
{{#parent}}
try super.init(from: decoder)
{{/parent}}
}

{{/additionalPropertiesType}}
{{^additionalPropertiesType}}{{#vendorExtensions.x-codegen-has-escaped-property-names}}
public enum CodingKeys: String, CodingKey { {{#allVars}}
case {{name}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}}{{/allVars}}
}
{{/vendorExtensions.x-codegen-has-escaped-property-names}}{{/additionalPropertiesType}}

}
91 changes: 91 additions & 0 deletions modules/swagger-codegen/src/test/resources/2_0/swift4Test.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@
"for": {
"description": "This property name is a reserved word in most languages, including Swift 4.",
"type": "string"
},
"normalName": {
"description": "This model object property name should be unchanged from the JSON property name.",
"type": "string"
}
}
},
Expand Down Expand Up @@ -370,6 +374,93 @@
"additionalProperties": {
"type": "string"
}
},
"SampleBase": {
"type": "object",
"description": "This is an base class object from which other classes will derive.",
"properties": {
"baseClassStringProp": {
"type": "string"
},
"baseClassIntegerProp": {
"type": "integer",
"format": "int32"
}
}
},
"SampleSubClass": {
"description": "This is an subclass defived from the SampleBase class.",
"allOf": [
{
"$ref": "#/definitions/SampleBase"
},
{
"type": "object",
"properties": {
"subClassStringProp": {
"type": "string"
},
"subClassIntegerProp": {
"type": "integer",
"format": "int32"
}
}
}
]
},
"BaseCard": {
"type": "object",
"description": "This is a base card object which uses a 'cardType' discriminator.",
"x-unit-tests": ["B45"],
"discriminator": "cardType",
"required": [
"cardType"
],
"properties": {
"cardType": {
"type": "string"
}
}
},
"PersonCard": {
"description": "This is an card object for a Person derived from BaseCard.",
"x-unit-tests": ["B45"],
"allOf": [
{
"$ref": "#/definitions/BaseCard"
},
{
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
}
}
]
},
"PlaceCard": {
"description": "This is an card object for a Person derived from BaseCard.",
"x-unit-tests": ["B45"],
"allOf": [
{
"$ref": "#/definitions/BaseCard"
},
{
"type": "object",
"properties": {
"placeName": {
"type": "string"
},
"placeAddress": {
"type": "string"
}
}
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.3.0-SNAPSHOT
2.4.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AnotherfakeAPI.swift
// AnotherFakeAPI.swift
//
// Generated by swagger-codegen
// https://github.com/swagger-api/swagger-codegen
Expand All @@ -10,7 +10,7 @@ import Alamofire



open class AnotherfakeAPI {
open class AnotherFakeAPI {
/**
To test special tags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,17 @@ import Foundation



open class AdditionalPropertiesClass: Codable {
public struct AdditionalPropertiesClass: Codable {

public var mapProperty: [String:String]?
public var mapOfMapProperty: [String:[String:String]]?



public init(mapProperty: [String:String]?, mapOfMapProperty: [String:[String:String]]?) {
self.mapProperty = mapProperty
self.mapOfMapProperty = mapOfMapProperty
public enum CodingKeys: String, CodingKey {
case mapProperty = "map_property"
case mapOfMapProperty = "map_of_map_property"
}


// Encodable protocol methods

public func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: String.self)

try container.encodeIfPresent(mapProperty, forKey: "map_property")
try container.encodeIfPresent(mapOfMapProperty, forKey: "map_of_map_property")
}

// Decodable protocol methods

public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)

mapProperty = try container.decodeIfPresent([String:String].self, forKey: "map_property")
mapOfMapProperty = try container.decodeIfPresent([String:[String:String]].self, forKey: "map_of_map_property")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,12 @@ import Foundation



open class Animal: Codable {
public struct Animal: Codable {

public var className: String
public var color: String?



public init(className: String, color: String?) {
self.className = className
self.color = color
}


// Encodable protocol methods

public func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: String.self)

try container.encode(className, forKey: "className")
try container.encodeIfPresent(color, forKey: "color")
}

// Decodable protocol methods

public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)

className = try container.decode(String.self, forKey: "className")
color = try container.decodeIfPresent(String.self, forKey: "color")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,13 @@ import Foundation



open class ApiResponse: Codable {
public struct ApiResponse: Codable {

public var code: Int?
public var type: String?
public var message: String?



public init(code: Int?, type: String?, message: String?) {
self.code = code
self.type = type
self.message = message
}


// Encodable protocol methods

public func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: String.self)

try container.encodeIfPresent(code, forKey: "code")
try container.encodeIfPresent(type, forKey: "type")
try container.encodeIfPresent(message, forKey: "message")
}

// Decodable protocol methods

public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)

code = try container.decodeIfPresent(Int.self, forKey: "code")
type = try container.decodeIfPresent(String.self, forKey: "type")
message = try container.decodeIfPresent(String.self, forKey: "message")
}
}

Loading

0 comments on commit a3d0f1d

Please sign in to comment.