Add additional cli option and properties to swift3 for Objective-C compatibility. (#6129)

Currently, in the swift3 language, if you have an optional integer, number, or boolean property in a model, then the generated swift3 model class might look like:

class SomeModel {
    var someInt: Int?
    var someFloat: Float?
    var someDouble: Double?
    var someBool: Bool?
}

This works fine if you are accessing this model only from Swift code. However, it is very common for iOS codebases to contain both Swift AND Objective-C. If you need to access this model from Objective-C, then those 4 properties are not accessible, since Optional scalars do not translate to Objective-C.

Therefore, in the swift3 language, we want to add some code for Objective-C compatibility:

1. We add a "objCompatible" boolean command-line option. If objCompatible=true, then this enables some additional code generation to make these types of properties accessible from Objective-C. If objCompatible=false, then the generated code is exactly as it currently is. The default is objcCopmatible=false.

2. If objCompatible=true, then for these types of Objective-C-inaccessible properties (Optional scalars), then we add a "x-swift-optional-scalar=true" vendor extension in the CodegenProperty.

3. Then, in the model.mustache template, if we see x-swift-optional-scalar=true, then we add an additional computed property which returns an optional NSNumber.

So, for example, when objcCompatible=false (the default case), then the generated code for the "declawed" property of the Cat model looks like:

open class Cat: Animal {

    public var declawed: Bool?

    ...

But when objcCompatible=true, then it looks like:

open class Cat: Animal {

    public var declawed: Bool?
    public var declawedNum: NSNumber? {
        get {
            return declawed.map({ return NSNumber(value: $0) })
        }
    }

   ...
This commit is contained in:
ehyche 2017-08-02 06:20:01 -04:00 committed by wing328
parent 218273e9a8
commit dd46ba9ef6
6 changed files with 88 additions and 3 deletions

View File

@ -0,0 +1,7 @@
{
"podSummary": "PetstoreClient",
"podHomepage": "https://github.com/swagger-api/swagger-codegen",
"podAuthors": "",
"projectName": "PetstoreClient",
"objcCompatible": true
}

View File

@ -0,0 +1,31 @@
#!/bin/sh
SCRIPT="$0"
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi
executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar"
if [ ! -f "$executable" ]
then
mvn clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -v -t modules/swagger-codegen/src/main/resources/swift3 -i modules/swagger-codegen/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -l swift3 -c ./bin/swift3-petstore-objcCompatible.json -o samples/client/petstore/swift3/objcCompatible $@"
java $JAVA_OPTS -jar $executable $ags

View File

@ -27,6 +27,7 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
public static final String PROJECT_NAME = "projectName";
public static final String RESPONSE_AS = "responseAs";
public static final String UNWRAP_REQUIRED = "unwrapRequired";
public static final String OBJC_COMPATIBLE = "objcCompatible";
public static final String POD_SOURCE = "podSource";
public static final String POD_AUTHORS = "podAuthors";
public static final String POD_SOCIAL_MEDIA_URL = "podSocialMediaURL";
@ -45,6 +46,7 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
protected static final String[] RESPONSE_LIBRARIES = {LIBRARY_PROMISE_KIT, LIBRARY_RX_SWIFT};
protected String projectName = "SwaggerClient";
protected boolean unwrapRequired;
protected boolean objcCompatible = false;
protected boolean lenientTypeCast = false;
protected boolean swiftUseApiNamespace;
protected String[] responseAs = new String[0];
@ -158,6 +160,7 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
StringUtils.join(RESPONSE_LIBRARIES, ", ") + " are available."));
cliOptions.add(new CliOption(UNWRAP_REQUIRED, "Treat 'required' properties in response as non-optional " +
"(which would crash the app if api returns null as opposed to required option specified in json schema"));
cliOptions.add(new CliOption(OBJC_COMPATIBLE, "Add additional properties and methods for Objective-C compatibility (default: false)"));
cliOptions.add(new CliOption(POD_SOURCE, "Source information used for Podspec"));
cliOptions.add(new CliOption(CodegenConstants.POD_VERSION, "Version used for Podspec"));
cliOptions.add(new CliOption(POD_AUTHORS, "Authors used for Podspec"));
@ -202,6 +205,12 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
}
additionalProperties.put(UNWRAP_REQUIRED, unwrapRequired);
// Setup objcCompatible option, which adds additional properties and methods for Objective-C compatibility
if (additionalProperties.containsKey(OBJC_COMPATIBLE)) {
setObjcCompatible(convertPropertyToBooleanAndWriteBack(OBJC_COMPATIBLE));
}
additionalProperties.put(OBJC_COMPATIBLE, objcCompatible);
// Setup unwrapRequired option, which makes all the properties with "required" non-optional
if (additionalProperties.containsKey(RESPONSE_AS)) {
Object responseAsObject = additionalProperties.get(RESPONSE_AS);
@ -250,13 +259,13 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
}
@Override
public String escapeReservedWord(String name) {
public String escapeReservedWord(String name) {
if(this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name; // add an underscore to the name
}
@Override
public String modelFileFolder() {
return outputFolder + File.separator + sourceFolder + modelPackage().replace('.', File.separatorChar);
@ -479,6 +488,10 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
this.unwrapRequired = unwrapRequired;
}
public void setObjcCompatible(boolean objcCompatible) {
this.objcCompatible = objcCompatible;
}
public void setLenientTypeCast(boolean lenientTypeCast) {
this.lenientTypeCast = lenientTypeCast;
}
@ -579,6 +592,31 @@ public class Swift3Codegen extends DefaultCodegen implements CodegenConfig {
return postProcessModelsEnum(objs);
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
// The default template code has the following logic for assigning a type as Swift Optional:
//
// {{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}
//
// which means:
//
// boolean isSwiftOptional = !unwrapRequired || (unwrapRequired && !property.required);
//
// We can drop the check for unwrapRequired in (unwrapRequired && !property.required)
// due to short-circuit evaluation of the || operator.
boolean isSwiftOptional = !unwrapRequired || !property.required;
boolean isSwiftScalarType = property.isInteger || property.isLong || property.isFloat || property.isDouble || property.isBoolean;
if (isSwiftOptional && isSwiftScalarType) {
// Optional scalar types like Int?, Int64?, Float?, Double?, and Bool?
// do not translate to Objective-C. So we want to flag those
// properties in case we want to put special code in the templates
// which provide Objective-C compatibility.
property.vendorExtensions.put("x-swift-optional-scalar", true);
}
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection

View File

@ -44,7 +44,12 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}JSONEncod
{{/isEnum}}
{{^isEnum}}
{{#description}}/** {{description}} */
{{/description}}public var {{name}}: {{{datatype}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
{{/description}}public var {{name}}: {{{datatype}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{#objcCompatible}}{{#vendorExtensions.x-swift-optional-scalar}}
public var {{name}}Num: NSNumber? {
get {
return {{name}}.map({ return NSNumber(value: $0) })
}
}{{/vendorExtensions.x-swift-optional-scalar}}{{/objcCompatible}}
{{/isEnum}}
{{/vars}}

View File

@ -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 OBJC_COMPATIBLE_VALUE = "false";
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' }";
@ -42,6 +43,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.OBJC_COMPATIBLE, OBJC_COMPATIBLE_VALUE)
.put(Swift3Codegen.LENIENT_TYPE_CAST, LENIENT_TYPE_CAST_VALUE)
.put(Swift3Codegen.POD_SOURCE, POD_SOURCE_VALUE)
.put(CodegenConstants.POD_VERSION, POD_VERSION_VALUE)

View File

@ -33,6 +33,8 @@ public class Swift3OptionsTest extends AbstractOptionsTest {
times = 1;
clientCodegen.setUnwrapRequired(Boolean.valueOf(Swift3OptionsProvider.UNWRAP_REQUIRED_VALUE));
times = 1;
clientCodegen.setObjcCompatible(Boolean.valueOf(Swift3OptionsProvider.OBJC_COMPATIBLE_VALUE));
times = 1;
clientCodegen.setLenientTypeCast(Boolean.valueOf(Swift3OptionsProvider.LENIENT_TYPE_CAST_VALUE));
times = 1;
}};