Add Objective-C compatibility for Optional scalars in swift4 just as we added in swift3. (#6158)

This change adds Objective-C compatibility for Swift Optional scalars in the swift4 language just like we added in swift3 here:

https://github.com/swagger-api/swagger-codegen/pull/6129

It also adds unit tests for the swift4 language. Currently those unit tests are the same as the swift3 unit tests, but will change soon as we iterate on the swift4 language generator.
This commit is contained in:
ehyche 2017-08-02 09:19:48 -04:00 committed by wing328
parent 04e53dfba1
commit 32906ea7ce
8 changed files with 430 additions and 1 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 -t modules/swagger-codegen/src/main/resources/swift4 -i modules/swagger-codegen/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -l swift4 -c ./bin/swift4-petstore-objcCompatible.json -o samples/client/petstore/swift4/objcCompatible"
java $JAVA_OPTS -jar $executable $ags

View File

@ -27,6 +27,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
public static final String PROJECT_NAME = "projectName"; public static final String PROJECT_NAME = "projectName";
public static final String RESPONSE_AS = "responseAs"; public static final String RESPONSE_AS = "responseAs";
public static final String UNWRAP_REQUIRED = "unwrapRequired"; 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_SOURCE = "podSource";
public static final String POD_AUTHORS = "podAuthors"; public static final String POD_AUTHORS = "podAuthors";
public static final String POD_SOCIAL_MEDIA_URL = "podSocialMediaURL"; public static final String POD_SOCIAL_MEDIA_URL = "podSocialMediaURL";
@ -45,6 +46,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
protected static final String[] RESPONSE_LIBRARIES = {LIBRARY_PROMISE_KIT, LIBRARY_RX_SWIFT}; protected static final String[] RESPONSE_LIBRARIES = {LIBRARY_PROMISE_KIT, LIBRARY_RX_SWIFT};
protected String projectName = "SwaggerClient"; protected String projectName = "SwaggerClient";
protected boolean unwrapRequired; protected boolean unwrapRequired;
protected boolean objcCompatible = false;
protected boolean lenientTypeCast = false; protected boolean lenientTypeCast = false;
protected boolean swiftUseApiNamespace; protected boolean swiftUseApiNamespace;
protected String[] responseAs = new String[0]; protected String[] responseAs = new String[0];
@ -159,6 +161,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
StringUtils.join(RESPONSE_LIBRARIES, ", ") + " are available.")); StringUtils.join(RESPONSE_LIBRARIES, ", ") + " are available."));
cliOptions.add(new CliOption(UNWRAP_REQUIRED, "Treat 'required' properties in response as non-optional " + 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")); "(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(POD_SOURCE, "Source information used for Podspec"));
cliOptions.add(new CliOption(CodegenConstants.POD_VERSION, "Version used for Podspec")); cliOptions.add(new CliOption(CodegenConstants.POD_VERSION, "Version used for Podspec"));
cliOptions.add(new CliOption(POD_AUTHORS, "Authors used for Podspec")); cliOptions.add(new CliOption(POD_AUTHORS, "Authors used for Podspec"));
@ -203,6 +206,12 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
} }
additionalProperties.put(UNWRAP_REQUIRED, unwrapRequired); 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 // Setup unwrapRequired option, which makes all the properties with "required" non-optional
if (additionalProperties.containsKey(RESPONSE_AS)) { if (additionalProperties.containsKey(RESPONSE_AS)) {
Object responseAsObject = additionalProperties.get(RESPONSE_AS); Object responseAsObject = additionalProperties.get(RESPONSE_AS);
@ -483,6 +492,10 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
this.unwrapRequired = unwrapRequired; this.unwrapRequired = unwrapRequired;
} }
public void setObjcCompatible(boolean objcCompatible) {
this.objcCompatible = objcCompatible;
}
public void setLenientTypeCast(boolean lenientTypeCast) { public void setLenientTypeCast(boolean lenientTypeCast) {
this.lenientTypeCast = lenientTypeCast; this.lenientTypeCast = lenientTypeCast;
} }
@ -583,6 +596,31 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
return postProcessModelsEnum(objs); 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 @Override
public String escapeQuotationMark(String input) { public String escapeQuotationMark(String input) {
// remove " to avoid code injection // remove " to avoid code injection

View File

@ -41,7 +41,12 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
{{/isEnum}} {{/isEnum}}
{{^isEnum}} {{^isEnum}}
{{#description}}/** {{description}} */ {{#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}} {{/isEnum}}
{{/vars}} {{/vars}}

View File

@ -0,0 +1,69 @@
package io.swagger.codegen.options;
import io.swagger.codegen.CodegenConstants;
import io.swagger.codegen.languages.Swift4Codegen;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
public class Swift4OptionsProvider implements OptionsProvider {
public static final String SORT_PARAMS_VALUE = "false";
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
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' }";
public static final String POD_VERSION_VALUE = "v1.0.0-SNAPSHOT";
public static final String POD_AUTHORS_VALUE = "podAuthors";
public static final String POD_SOCIAL_MEDIA_URL_VALUE = "podSocialMediaURL";
public static final String POD_DOCSET_URL_VALUE = "podDocsetURL";
public static final String POD_LICENSE_VALUE = "'Apache License, Version 2.0'";
public static final String POD_HOMEPAGE_VALUE = "podHomepage";
public static final String POD_SUMMARY_VALUE = "podSummary";
public static final String POD_DESCRIPTION_VALUE = "podDescription";
public static final String POD_SCREENSHOTS_VALUE = "podScreenshots";
public static final String POD_DOCUMENTATION_URL_VALUE = "podDocumentationURL";
public static final String SWIFT_USE_API_NAMESPACE_VALUE = "swiftUseApiNamespace";
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
@Override
public String getLanguage() {
return "swift4";
}
@Override
public Map<String, String> createOptions() {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<String, String>();
return builder.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE)
.put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE)
.put(Swift4Codegen.PROJECT_NAME, PROJECT_NAME_VALUE)
.put(Swift4Codegen.RESPONSE_AS, RESPONSE_AS_VALUE)
.put(Swift4Codegen.UNWRAP_REQUIRED, UNWRAP_REQUIRED_VALUE)
.put(Swift4Codegen.OBJC_COMPATIBLE, OBJC_COMPATIBLE_VALUE)
.put(Swift4Codegen.LENIENT_TYPE_CAST, LENIENT_TYPE_CAST_VALUE)
.put(Swift4Codegen.POD_SOURCE, POD_SOURCE_VALUE)
.put(CodegenConstants.POD_VERSION, POD_VERSION_VALUE)
.put(Swift4Codegen.POD_AUTHORS, POD_AUTHORS_VALUE)
.put(Swift4Codegen.POD_SOCIAL_MEDIA_URL, POD_SOCIAL_MEDIA_URL_VALUE)
.put(Swift4Codegen.POD_DOCSET_URL, POD_DOCSET_URL_VALUE)
.put(Swift4Codegen.POD_LICENSE, POD_LICENSE_VALUE)
.put(Swift4Codegen.POD_HOMEPAGE, POD_HOMEPAGE_VALUE)
.put(Swift4Codegen.POD_SUMMARY, POD_SUMMARY_VALUE)
.put(Swift4Codegen.POD_DESCRIPTION, POD_DESCRIPTION_VALUE)
.put(Swift4Codegen.POD_SCREENSHOTS, POD_SCREENSHOTS_VALUE)
.put(Swift4Codegen.POD_DOCUMENTATION_URL, POD_DOCUMENTATION_URL_VALUE)
.put(Swift4Codegen.SWIFT_USE_API_NAMESPACE, SWIFT_USE_API_NAMESPACE_VALUE)
.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true")
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
.build();
}
@Override
public boolean isServer() {
return false;
}
}

View File

@ -0,0 +1,125 @@
package io.swagger.codegen.swift4;
import io.swagger.codegen.CodegenOperation;
import io.swagger.codegen.DefaultCodegen;
import io.swagger.codegen.languages.Swift4Codegen;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
import io.swagger.parser.SwaggerParser;
import org.testng.Assert;
import org.testng.annotations.Test;
public class Swift4CodegenTest {
Swift4Codegen swiftCodegen = new Swift4Codegen();
@Test
public void testCapitalizedReservedWord() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("AS", null), "_as");
}
@Test
public void testReservedWord() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("Public", null), "_public");
}
@Test
public void shouldNotBreakNonReservedWord() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("Error", null), "error");
}
@Test
public void shouldNotBreakCorrectName() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("EntryName", null), "entryName");
}
@Test
public void testSingleWordAllCaps() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("VALUE", null), "value");
}
@Test
public void testSingleWordLowercase() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("value", null), "value");
}
@Test
public void testCapitalsWithUnderscore() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY_NAME", null), "entryName");
}
@Test
public void testCapitalsWithDash() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY-NAME", null), "entryName");
}
@Test
public void testCapitalsWithSpace() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY NAME", null), "entryName");
}
@Test
public void testLowercaseWithUnderscore() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("entry_name", null), "entryName");
}
@Test
public void testStartingWithNumber() throws Exception {
Assert.assertEquals(swiftCodegen.toEnumVarName("123EntryName", null), "_123entryName");
Assert.assertEquals(swiftCodegen.toEnumVarName("123Entry_name", null), "_123entryName");
Assert.assertEquals(swiftCodegen.toEnumVarName("123EntryName123", null), "_123entryName123");
}
@Test(description = "returns Data when response format is binary")
public void binaryDataTest() {
final Swagger model = new SwaggerParser().read("src/test/resources/2_0/binaryDataTest.json");
final DefaultCodegen codegen = new Swift4Codegen();
final String path = "/tests/binaryResponse";
final Operation p = model.getPaths().get(path).getPost();
final CodegenOperation op = codegen.fromOperation(path, "post", p, model.getDefinitions());
Assert.assertEquals(op.returnType, "Data");
Assert.assertEquals(op.bodyParam.dataType, "Data");
Assert.assertTrue(op.bodyParam.isBinary);
Assert.assertTrue(op.responses.get(0).isBinary);
}
@Test(description = "returns Date when response format is date")
public void dateTest() {
final Swagger model = new SwaggerParser().read("src/test/resources/2_0/datePropertyTest.json");
final DefaultCodegen codegen = new Swift4Codegen();
final String path = "/tests/dateResponse";
final Operation p = model.getPaths().get(path).getPost();
final CodegenOperation op = codegen.fromOperation(path, "post", p, model.getDefinitions());
Assert.assertEquals(op.returnType, "Date");
Assert.assertEquals(op.bodyParam.dataType, "Date");
}
@Test
public void testDefaultPodAuthors() throws Exception {
// Given
// When
swiftCodegen.processOpts();
// Then
final String podAuthors = (String) swiftCodegen.additionalProperties().get(Swift4Codegen.POD_AUTHORS);
Assert.assertEquals(podAuthors, Swift4Codegen.DEFAULT_POD_AUTHORS);
}
@Test
public void testPodAuthors() throws Exception {
// Given
final String swaggerDevs = "Swagger Devs";
swiftCodegen.additionalProperties().put(Swift4Codegen.POD_AUTHORS, swaggerDevs);
// When
swiftCodegen.processOpts();
// Then
final String podAuthors = (String) swiftCodegen.additionalProperties().get(Swift4Codegen.POD_AUTHORS);
Assert.assertEquals(podAuthors, swaggerDevs);
}
}

View File

@ -0,0 +1,112 @@
package io.swagger.codegen.swift4;
import io.swagger.codegen.CodegenModel;
import io.swagger.codegen.CodegenProperty;
import io.swagger.codegen.DefaultCodegen;
import io.swagger.codegen.languages.Swift4Codegen;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.*;
import org.testng.Assert;
import org.testng.annotations.Test;
@SuppressWarnings("static-method")
public class Swift4ModelTest {
@Test(description = "convert a simple java model")
public void simpleModelTest() {
final Model model = new ModelImpl()
.description("a sample model")
.property("id", new LongProperty())
.property("name", new StringProperty())
.property("createdAt", new DateTimeProperty())
.property("binary", new BinaryProperty())
.property("byte", new ByteArrayProperty())
.property("uuid", new UUIDProperty())
.property("dateOfBirth", new DateProperty())
.required("id")
.required("name")
.discriminator("test");
final DefaultCodegen codegen = new Swift4Codegen();
final CodegenModel cm = codegen.fromModel("sample", model);
Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.description, "a sample model");
Assert.assertEquals(cm.vars.size(), 7);
Assert.assertEquals(cm.discriminator,"test");
final CodegenProperty property1 = cm.vars.get(0);
Assert.assertEquals(property1.baseName, "id");
Assert.assertEquals(property1.datatype, "Int64");
Assert.assertEquals(property1.name, "id");
Assert.assertNull(property1.defaultValue);
Assert.assertEquals(property1.baseType, "Int64");
Assert.assertTrue(property1.hasMore);
Assert.assertTrue(property1.required);
Assert.assertTrue(property1.isPrimitiveType);
Assert.assertTrue(property1.isNotContainer);
final CodegenProperty property2 = cm.vars.get(1);
Assert.assertEquals(property2.baseName, "name");
Assert.assertEquals(property2.datatype, "String");
Assert.assertEquals(property2.name, "name");
Assert.assertNull(property2.defaultValue);
Assert.assertEquals(property2.baseType, "String");
Assert.assertTrue(property2.hasMore);
Assert.assertTrue(property2.required);
Assert.assertTrue(property2.isPrimitiveType);
Assert.assertTrue(property2.isNotContainer);
final CodegenProperty property3 = cm.vars.get(2);
Assert.assertEquals(property3.baseName, "createdAt");
Assert.assertEquals(property3.datatype, "Date");
Assert.assertEquals(property3.name, "createdAt");
Assert.assertNull(property3.defaultValue);
Assert.assertEquals(property3.baseType, "Date");
Assert.assertTrue(property3.hasMore);
Assert.assertFalse(property3.required);
Assert.assertTrue(property3.isNotContainer);
final CodegenProperty property4 = cm.vars.get(3);
Assert.assertEquals(property4.baseName, "binary");
Assert.assertEquals(property4.datatype, "Data");
Assert.assertEquals(property4.name, "binary");
Assert.assertNull(property4.defaultValue);
Assert.assertEquals(property4.baseType, "Data");
Assert.assertTrue(property4.hasMore);
Assert.assertFalse(property4.required);
Assert.assertTrue(property4.isNotContainer);
final CodegenProperty property5 = cm.vars.get(4);
Assert.assertEquals(property5.baseName, "byte");
Assert.assertEquals(property5.datatype, "Data");
Assert.assertEquals(property5.name, "byte");
Assert.assertNull(property5.defaultValue);
Assert.assertEquals(property5.baseType, "Data");
Assert.assertTrue(property5.hasMore);
Assert.assertFalse(property5.required);
Assert.assertTrue(property5.isNotContainer);
final CodegenProperty property6 = cm.vars.get(5);
Assert.assertEquals(property6.baseName, "uuid");
Assert.assertEquals(property6.datatype, "UUID");
Assert.assertEquals(property6.name, "uuid");
Assert.assertNull(property6.defaultValue);
Assert.assertEquals(property6.baseType, "UUID");
Assert.assertTrue(property6.hasMore);
Assert.assertFalse(property6.required);
Assert.assertTrue(property6.isNotContainer);
final CodegenProperty property7 = cm.vars.get(6);
Assert.assertEquals(property7.baseName, "dateOfBirth");
Assert.assertEquals(property7.datatype, "Date");
Assert.assertEquals(property7.name, "dateOfBirth");
Assert.assertNull(property7.defaultValue);
Assert.assertEquals(property7.baseType, "Date");
Assert.assertFalse(property7.hasMore);
Assert.assertFalse(property7.required);
Assert.assertTrue(property7.isNotContainer);
}
}

View File

@ -0,0 +1,42 @@
package io.swagger.codegen.swift4;
import io.swagger.codegen.AbstractOptionsTest;
import io.swagger.codegen.CodegenConfig;
import io.swagger.codegen.languages.Swift4Codegen;
import io.swagger.codegen.options.Swift4OptionsProvider;
import mockit.Expectations;
import mockit.Tested;
public class Swift4OptionsTest extends AbstractOptionsTest {
@Tested
private Swift4Codegen clientCodegen;
public Swift4OptionsTest() {
super(new Swift4OptionsProvider());
}
@Override
protected CodegenConfig getCodegenConfig() {
return clientCodegen;
}
@SuppressWarnings("unused")
@Override
protected void setExpectations() {
new Expectations(clientCodegen) {{
clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(Swift4OptionsProvider.SORT_PARAMS_VALUE));
times = 1;
clientCodegen.setProjectName(Swift4OptionsProvider.PROJECT_NAME_VALUE);
times = 1;
clientCodegen.setResponseAs(Swift4OptionsProvider.RESPONSE_AS_VALUE.split(","));
times = 1;
clientCodegen.setUnwrapRequired(Boolean.valueOf(Swift4OptionsProvider.UNWRAP_REQUIRED_VALUE));
times = 1;
clientCodegen.setObjcCompatible(Boolean.valueOf(Swift4OptionsProvider.OBJC_COMPATIBLE_VALUE));
times = 1;
clientCodegen.setLenientTypeCast(Boolean.valueOf(Swift4OptionsProvider.LENIENT_TYPE_CAST_VALUE));
times = 1;
}};
}
}