diff --git a/.github/.test/samples.json b/.github/.test/samples.json index 86f0d8d410e..137dd78466d 100644 --- a/.github/.test/samples.json +++ b/.github/.test/samples.json @@ -1094,6 +1094,36 @@ "Client: Swift" ] }, + { + "input": "swift5-petstore-promisekit.sh", + "matches": [ + "Client: Swift" + ] + }, + { + "input": "swift5-petstore-rxswift.sh", + "matches": [ + "Client: Swift" + ] + }, + { + "input": "swift5-petstore-unwrapRequired.sh", + "matches": [ + "Client: Swift" + ] + }, + { + "input": "swift5-petstore.sh", + "matches": [ + "Client: Swift" + ] + }, + { + "input": "swift5-test.sh", + "matches": [ + "Client: Swift" + ] + }, { "input": "typescript-angular-v2-petstore-interfaces.sh", "matches": [ diff --git a/bin/swift5-all.sh b/bin/swift5-all.sh new file mode 100755 index 00000000000..0fabcabab42 --- /dev/null +++ b/bin/swift5-all.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +./bin/swift5-petstore-all.sh +./bin/swift5-test.sh diff --git a/bin/swift5-petstore-all.sh b/bin/swift5-petstore-all.sh new file mode 100755 index 00000000000..3142e44eca4 --- /dev/null +++ b/bin/swift5-petstore-all.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +./bin/swift5-petstore.sh +./bin/swift5-petstore-promisekit.sh +./bin/swift5-petstore-rxswift.sh +./bin/swift5-petstore-unwrapRequired.sh diff --git a/bin/swift5-petstore-promisekit.json b/bin/swift5-petstore-promisekit.json new file mode 100644 index 00000000000..d1a022e02d5 --- /dev/null +++ b/bin/swift5-petstore-promisekit.json @@ -0,0 +1,8 @@ +{ + "podSummary": "PetstoreClient", + "podHomepage": "https://github.com/openapitools/openapi-generator", + "podAuthors": "", + "projectName": "PetstoreClient", + "onlyPlainInterface": false, + "responseAs": "PromiseKit" +} diff --git a/bin/swift5-petstore-promisekit.sh b/bin/swift5-petstore-promisekit.sh new file mode 100755 index 00000000000..729956c04b2 --- /dev/null +++ b/bin/swift5-petstore-promisekit.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +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/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/swift5 -i modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml -g swift5 -c ./bin/swift5-petstore-promisekit.json -o samples/client/petstore/swift5/promisekit --generate-alias-as-model $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/swift5-petstore-rxswift.json b/bin/swift5-petstore-rxswift.json new file mode 100644 index 00000000000..81acaddfa0d --- /dev/null +++ b/bin/swift5-petstore-rxswift.json @@ -0,0 +1,8 @@ +{ + "podSummary": "PetstoreClient", + "podHomepage": "https://github.com/openapitools/openapi-generator", + "podAuthors": "", + "projectName": "PetstoreClient", + "onlyPlainInterface": false, + "responseAs": "RxSwift" +} diff --git a/bin/swift5-petstore-rxswift.sh b/bin/swift5-petstore-rxswift.sh new file mode 100755 index 00000000000..ceec25a1558 --- /dev/null +++ b/bin/swift5-petstore-rxswift.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +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/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/swift5 -i modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml -g swift5 -c ./bin/swift5-petstore-rxswift.json -o samples/client/petstore/swift5/rxswift --generate-alias-as-model $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/swift5-petstore-unwrapRequired.json b/bin/swift5-petstore-unwrapRequired.json new file mode 100644 index 00000000000..3d3152c52a2 --- /dev/null +++ b/bin/swift5-petstore-unwrapRequired.json @@ -0,0 +1,7 @@ +{ + "podSummary": "PetstoreClient", + "podHomepage": "https://github.com/openapitools/openapi-generator", + "podAuthors": "", + "projectName": "PetstoreClient", + "unwrapRequired": true +} diff --git a/bin/swift5-petstore-unwrapRequired.sh b/bin/swift5-petstore-unwrapRequired.sh new file mode 100755 index 00000000000..03d94c54628 --- /dev/null +++ b/bin/swift5-petstore-unwrapRequired.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +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/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/swift5 -i modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml -g swift5 -c ./bin/swift5-petstore-unwrapRequired.json -o samples/client/petstore/swift5/unwrapRequired --generate-alias-as-model $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/swift5-petstore.json b/bin/swift5-petstore.json new file mode 100644 index 00000000000..59bd94f43ef --- /dev/null +++ b/bin/swift5-petstore.json @@ -0,0 +1,6 @@ +{ + "podSummary": "PetstoreClient", + "podHomepage": "https://github.com/openapitools/openapi-generator", + "podAuthors": "", + "projectName": "PetstoreClient" +} diff --git a/bin/swift5-petstore.sh b/bin/swift5-petstore.sh new file mode 100755 index 00000000000..4320643c54d --- /dev/null +++ b/bin/swift5-petstore.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +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/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/swift5 -i modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml -g swift5 -c ./bin/swift5-petstore.json -o samples/client/petstore/swift5/default --generate-alias-as-model $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/swift5-test.json b/bin/swift5-test.json new file mode 100644 index 00000000000..9341b740a2a --- /dev/null +++ b/bin/swift5-test.json @@ -0,0 +1,6 @@ +{ + "podSummary": "TestClient", + "podHomepage": "https://github.com/openapitools/openapi-generator", + "podAuthors": "", + "projectName": "TestClient" +} diff --git a/bin/swift5-test.sh b/bin/swift5-test.sh new file mode 100755 index 00000000000..bc84c27303d --- /dev/null +++ b/bin/swift5-test.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +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/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/swift5 -i modules/openapi-generator/src/test/resources/2_0/swift5Test.json -g swift5 -c ./bin/swift5-test.json -o samples/client/test/swift5default $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/docs/generators.md b/docs/generators.md index ecb4edfd40c..8e2f07ee6be 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -54,6 +54,7 @@ The following generators are available: - [swift2-deprecated](generators/swift2-deprecated.md) (deprecated) - [swift3-deprecated](generators/swift3-deprecated.md) (deprecated) - [swift4](generators/swift4.md) + - [swift5](generators/swift5.md) - [typescript-angular](generators/typescript-angular.md) - [typescript-angularjs](generators/typescript-angularjs.md) - [typescript-aurelia](generators/typescript-aurelia.md) diff --git a/docs/generators/README.md b/docs/generators/README.md index 6c3328cc072..c87f988cd56 100644 --- a/docs/generators/README.md +++ b/docs/generators/README.md @@ -47,6 +47,7 @@ The following generators are available: - [swift2-deprecated](swift2-deprecated.md) - [swift3-deprecated](swift3-deprecated.md) - [swift4](swift4.md) + - [swift5](swift5.md) - [typescript-angular](typescript-angular.md) - [typescript-angularjs](typescript-angularjs.md) - [typescript-aurelia](typescript-aurelia.md) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5Codegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5Codegen.java new file mode 100644 index 00000000000..5c78c0afc88 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5Codegen.java @@ -0,0 +1,1013 @@ +/* + * Copyright 2019 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.text.WordUtils; +import org.openapitools.codegen.*; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.openapitools.codegen.utils.StringUtils.camelize; + + +public class Swift5Codegen extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(Swift5Codegen.class); + + public static final String PROJECT_NAME = "projectName"; + public static final String ONLY_PLAIN_INTERFACE = "onlyPlainInterface"; + public static final String RESPONSE_AS = "responseAs"; + public static final String UNWRAP_REQUIRED = "unwrapRequired"; + public static final String POD_SOURCE = "podSource"; + public static final String POD_AUTHORS = "podAuthors"; + public static final String POD_SOCIAL_MEDIA_URL = "podSocialMediaURL"; + public static final String POD_DOCSET_URL = "podDocsetURL"; + public static final String POD_LICENSE = "podLicense"; + public static final String POD_HOMEPAGE = "podHomepage"; + public static final String POD_SUMMARY = "podSummary"; + public static final String POD_DESCRIPTION = "podDescription"; + public static final String POD_SCREENSHOTS = "podScreenshots"; + public static final String POD_DOCUMENTATION_URL = "podDocumentationURL"; + public static final String DEPLOYMENT_TARGET = "deploymentTarget"; + public static final String SWIFT_USE_API_NAMESPACE = "swiftUseApiNamespace"; + public static final String DEFAULT_POD_AUTHORS = "OpenAPI Generator"; + 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 = "OpenAPIClient"; + protected boolean onlyPlainInterface = true; + protected boolean unwrapRequired; + protected boolean lenientTypeCast = false; + protected boolean swiftUseApiNamespace; + protected String[] responseAs = new String[0]; + protected String sourceFolder = "Classes" + File.separator + "OpenAPIs"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + /** + * Constructor for the 5 language codegen module. + */ + public Swift5Codegen() { + super(); + outputFolder = "generated-code" + File.separator + "swift"; + modelTemplateFiles.put("model.mustache", ".swift"); + apiTemplateFiles.put("api.mustache", ".swift"); + embeddedTemplateDir = templateDir = "swift5"; + apiPackage = File.separator + "APIs"; + modelPackage = File.separator + "Models"; + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + languageSpecificPrimitives = new HashSet<>( + Arrays.asList( + "Int", + "Int32", + "Int64", + "Float", + "Double", + "Bool", + "Void", + "String", + "URL", + "Data", + "Date", + "Character", + "UUID", + "URL", + "AnyObject", + "Any") + ); + defaultIncludes = new HashSet<>( + Arrays.asList( + "Data", + "Date", + "URL", // for file + "UUID", + "Array", + "Dictionary", + "Set", + "Any", + "Empty", + "AnyObject", + "Any") + ); + + reservedWords = new HashSet<>( + Arrays.asList( + // name used by swift client + "ErrorResponse", "Response", + + // Swift keywords. This list is taken from here: + // https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID410 + // + // Keywords used in declarations + "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", + "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", + "subscript", "typealias", "var", + // Keywords uses in statements + "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", + "in", "repeat", "return", "switch", "where", "while", + // Keywords used in expressions and types + "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", + // Keywords used in patterns + "_", + // Keywords that begin with a number sign + "#available", "#colorLiteral", "#column", "#else", "#elseif", "#endif", "#file", "#fileLiteral", "#function", "#if", + "#imageLiteral", "#line", "#selector", "#sourceLocation", + // Keywords reserved in particular contexts + "associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "indirect", "lazy", "left", + "mutating", "none", "nonmutating", "optional", "override", "postfix", "precedence", "prefix", "Protocol", + "required", "right", "set", "Type", "unowned", "weak", "willSet", + + // + // Swift Standard Library types + // https://developer.apple.com/documentation/swift + // + // Numbers and Basic Values + "Bool", "Int", "Double", "Float", "Range", "ClosedRange", "Error", "Optional", + // Special-Use Numeric Types + "UInt", "UInt8", "UInt16", "UInt32", "UInt64", "Int8", "Int16", "Int32", "Int64", "Float80", "Float32", "Float64", + // Strings and Text + "String", "Character", "Unicode", "StaticString", + // Collections + "Array", "Dictionary", "Set", "OptionSet", "CountableRange", "CountableClosedRange", + + // The following are commonly-used Foundation types + "URL", "Data", "Codable", "Encodable", "Decodable", + + // The following are other words we want to reserve + "Void", "AnyObject", "Class", "dynamicType", "COLUMN", "FILE", "FUNCTION", "LINE" + ) + ); + + typeMapping = new HashMap<>(); + typeMapping.put("array", "Array"); + typeMapping.put("List", "Array"); + typeMapping.put("map", "Dictionary"); + typeMapping.put("date", "Date"); + typeMapping.put("Date", "Date"); + typeMapping.put("DateTime", "Date"); + typeMapping.put("boolean", "Bool"); + typeMapping.put("string", "String"); + typeMapping.put("char", "Character"); + typeMapping.put("short", "Int"); + typeMapping.put("int", "Int"); + typeMapping.put("long", "Int64"); + typeMapping.put("integer", "Int"); + typeMapping.put("Integer", "Int"); + typeMapping.put("float", "Float"); + typeMapping.put("number", "Double"); + typeMapping.put("double", "Double"); + typeMapping.put("object", "Any"); + typeMapping.put("file", "URL"); + typeMapping.put("binary", "URL"); + typeMapping.put("ByteArray", "Data"); + typeMapping.put("UUID", "UUID"); + typeMapping.put("URI", "String"); + + importMapping = new HashMap<>(); + + cliOptions.add(new CliOption(PROJECT_NAME, "Project name in Xcode")); + cliOptions.add(new CliOption(RESPONSE_AS, + "Optionally use libraries to manage response. Currently " + + StringUtils.join(RESPONSE_LIBRARIES, ", ") + + " are available.")); + cliOptions.add(new CliOption(ONLY_PLAIN_INTERFACE, + "Only generate APIs and Models without no API Client and dependencies")); + 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(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")); + cliOptions.add(new CliOption(POD_SOCIAL_MEDIA_URL, "Social Media URL used for Podspec")); + cliOptions.add(new CliOption(POD_DOCSET_URL, "Docset URL used for Podspec")); + cliOptions.add(new CliOption(POD_LICENSE, "License used for Podspec")); + cliOptions.add(new CliOption(POD_HOMEPAGE, "Homepage used for Podspec")); + cliOptions.add(new CliOption(POD_SUMMARY, "Summary used for Podspec")); + cliOptions.add(new CliOption(POD_DESCRIPTION, "Description used for Podspec")); + cliOptions.add(new CliOption(POD_SCREENSHOTS, "Screenshots used for Podspec")); + cliOptions.add(new CliOption(POD_DOCUMENTATION_URL, + "Documentation URL used for Podspec")); + 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, + CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) + .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())); + } + + private static CodegenModel reconcileProperties(CodegenModel codegenModel, + CodegenModel parentCodegenModel) { + // To support inheritance in this generator, we will analyze + // the parent and child models, look for properties that match, and remove + // them from the child models and leave them in the parent. + // Because the child models extend the parents, the properties + // will be available via the parent. + + // Get the properties for the parent and child models + final List parentModelCodegenProperties = parentCodegenModel.vars; + List codegenProperties = codegenModel.vars; + codegenModel.allVars = new ArrayList(codegenProperties); + codegenModel.parentVars = parentCodegenModel.allVars; + + // Iterate over all of the parent model properties + boolean removedChildProperty = false; + + for (CodegenProperty parentModelCodegenProperty : parentModelCodegenProperties) { + // Now that we have found a prop in the parent class, + // and search the child class for the same prop. + Iterator iterator = codegenProperties.iterator(); + while (iterator.hasNext()) { + CodegenProperty codegenProperty = iterator.next(); + if (codegenProperty.baseName.equals(parentModelCodegenProperty.baseName)) { + // We found a property in the child class that is + // a duplicate of the one in the parent, so remove it. + iterator.remove(); + removedChildProperty = true; + } + } + } + + if (removedChildProperty) { + // If we removed an entry from this model's vars, we need to ensure hasMore is updated + int count = 0; + int numVars = codegenProperties.size(); + for (CodegenProperty codegenProperty : codegenProperties) { + count += 1; + codegenProperty.hasMore = count < numVars; + } + codegenModel.vars = codegenProperties; + } + + + return codegenModel; + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "swift5"; + } + + @Override + public String getHelp() { + return "Generates a Swift 5.x client library."; + } + + @Override + protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, + Schema schema) { + + final Schema additionalProperties = ModelUtils.getAdditionalProperties(schema); + + if (additionalProperties != null) { + codegenModel.additionalPropertiesType = getSchemaType(additionalProperties); + } + } + + @Override + public void processOpts() { + super.processOpts(); + + if (StringUtils.isEmpty(System.getenv("SWIFT_POST_PROCESS_FILE"))) { + LOGGER.info("Environment variable SWIFT_POST_PROCESS_FILE not defined so the Swift code may not be properly formatted. To define it, try 'export SWIFT_POST_PROCESS_FILE=/usr/local/bin/swiftformat' (Linux/Mac)"); + LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); + } + + // Setup project name + if (additionalProperties.containsKey(PROJECT_NAME)) { + setProjectName((String) additionalProperties.get(PROJECT_NAME)); + } else { + additionalProperties.put(PROJECT_NAME, projectName); + } + sourceFolder = projectName + File.separator + sourceFolder; + + // Setup unwrapRequired option, which makes all the + // properties with "required" non-optional + if (additionalProperties.containsKey(UNWRAP_REQUIRED)) { + setUnwrapRequired(convertPropertyToBooleanAndWriteBack(UNWRAP_REQUIRED)); + } + additionalProperties.put(UNWRAP_REQUIRED, unwrapRequired); + + // Setup unwrapRequired option, which makes all the properties with "required" non-optional + if (additionalProperties.containsKey(RESPONSE_AS)) { + Object responseAsObject = additionalProperties.get(RESPONSE_AS); + if (responseAsObject instanceof String) { + setResponseAs(((String) responseAsObject).split(",")); + } else { + setResponseAs((String[]) responseAsObject); + } + } + additionalProperties.put(RESPONSE_AS, responseAs); + if (Boolean.FALSE.equals(onlyPlainInterface)) { + if (ArrayUtils.contains(responseAs, LIBRARY_PROMISE_KIT)) { + additionalProperties.put("usePromiseKit", true); + } + if (ArrayUtils.contains(responseAs, LIBRARY_RX_SWIFT)) { + additionalProperties.put("useRxSwift", true); + } + } + + // Setup swiftUseApiNamespace option, which makes all the API + // classes inner-class of {{projectName}}API + if (additionalProperties.containsKey(SWIFT_USE_API_NAMESPACE)) { + setSwiftUseApiNamespace(convertPropertyToBooleanAndWriteBack(SWIFT_USE_API_NAMESPACE)); + } + + if (!additionalProperties.containsKey(POD_AUTHORS)) { + additionalProperties.put(POD_AUTHORS, DEFAULT_POD_AUTHORS); + } + + setLenientTypeCast(convertPropertyToBooleanAndWriteBack(LENIENT_TYPE_CAST)); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + supportingFiles.add(new SupportingFile("Podspec.mustache", + "", + projectName + ".podspec")); + supportingFiles.add(new SupportingFile("XcodeGen.mustache", + sourceFolder, + "project.yml")); + if (Boolean.FALSE.equals(onlyPlainInterface)) { + supportingFiles.add(new SupportingFile("Cartfile.mustache", + "", + "Cartfile")); + supportingFiles.add(new SupportingFile("APIHelper.mustache", + sourceFolder, + "APIHelper.swift")); + supportingFiles.add(new SupportingFile("AlamofireImplementations.mustache", + sourceFolder, + "AlamofireImplementations.swift")); + supportingFiles.add(new SupportingFile("Extensions.mustache", + sourceFolder, + "Extensions.swift")); + supportingFiles.add(new SupportingFile("CodableHelper.mustache", + sourceFolder, + "CodableHelper.swift")); + supportingFiles.add(new SupportingFile("JSONEncodableEncoding.mustache", + sourceFolder, + "JSONEncodableEncoding.swift")); + supportingFiles.add(new SupportingFile("JSONEncodingHelper.mustache", + sourceFolder, + "JSONEncodingHelper.swift")); + supportingFiles.add(new SupportingFile("Models.mustache", + sourceFolder, + "Models.swift")); + supportingFiles.add(new SupportingFile("Configuration.mustache", + sourceFolder, + "Configuration.swift")); + } + supportingFiles.add(new SupportingFile("APIs.mustache", + sourceFolder, + "APIs.swift")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", + "", + "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", + "", + ".gitignore")); + supportingFiles.add(new SupportingFile("README.mustache", + "", + "README.md")); + + } + + @Override + protected boolean isReservedWord(String word) { + return word != null && reservedWords.contains(word); //don't lowercase as super does + } + + @Override + 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); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (ModelUtils.isArraySchema(p)) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "[" + getTypeDeclaration(inner) + "]"; + } else if (ModelUtils.isMapSchema(p)) { + Schema inner = ModelUtils.getAdditionalProperties(p); + return "[String:" + getTypeDeclaration(inner) + "]"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type) || defaultIncludes.contains(type)) { + return type; + } + } else { + type = openAPIType; + } + return toModelName(type); + } + + @Override + public boolean isDataTypeFile(String dataType) { + return dataType != null && dataType.equals("URL"); + } + + @Override + public boolean isDataTypeBinary(final String dataType) { + return dataType != null && dataType.equals("Data"); + } + + /** + * Output the proper model name (capitalized). + * + * @param name the name of the model + * @return capitalized model name + */ + @Override + public String toModelName(String name) { + // FIXME parameter should not be assigned. Also declare it as "final" + name = sanitizeName(name); + + if (!StringUtils.isEmpty(modelNameSuffix)) { // set model suffix + name = name + "_" + modelNameSuffix; + } + + if (!StringUtils.isEmpty(modelNamePrefix)) { // set model prefix + name = modelNamePrefix + "_" + name; + } + + // camelize the model name + // phone_number => PhoneNumber + name = camelize(name); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + String modelName = "Model" + name; + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + + modelName); + return modelName; + } + + // model name starts with number + if (name.matches("^\\d.*")) { + // e.g. 200Response => Model200Response (after camelize) + String modelName = "Model" + name; + LOGGER.warn(name + + " (model name starts with number) cannot be used as model name." + + " Renamed to " + modelName); + return modelName; + } + + return name; + } + + /** + * Return the capitalized file name of the model. + * + * @param name the model name + * @return the file name of the model + */ + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String toDefaultValue(Schema p) { + if (p.getEnum() != null && !p.getEnum().isEmpty()) { + if (p.getDefault() != null) { + return "." + escapeText((String) p.getDefault()); + } + } + if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isStringSchema(p)) { + if (p.getDefault() != null) { + return "\"" + escapeText((String) p.getDefault()) + "\""; + } + } + return null; + } + + @Override + public String toInstantiationType(Schema p) { + if (ModelUtils.isMapSchema(p)) { + return getSchemaType(ModelUtils.getAdditionalProperties(p)); + } else if (ModelUtils.isArraySchema(p)) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + return "[" + inner + "]"; + } + return null; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DefaultAPI"; + } + return camelize(name) + "API"; + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace("/", File.separator); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace("/", File.separator); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + @Override + public String toOperationId(String operationId) { + operationId = camelize(sanitizeName(operationId), true); + + // Throw exception if method name is empty. + // This should not happen but keep the check just in case + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method name (operationId) not allowed"); + } + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + String newOperationId = camelize(("call_" + operationId), true); + LOGGER.warn(operationId + " (reserved word) cannot be used as method name." + + " Renamed to " + newOperationId); + return newOperationId; + } + + // operationId starts with a number + if (operationId.matches("^\\d.*")) { + LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId), true)); + operationId = camelize(sanitizeName("call_" + operationId), true); + } + + + return operationId; + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // camelize the variable name + // pet_id => petId + name = camelize(name, true); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String toParamName(String name) { + // sanitize name + name = sanitizeName(name); + + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // camelize(lower) the variable name + // pet_id => petId + name = camelize(name, true); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public CodegenModel fromModel(String name, Schema model) { + Map allDefinitions = ModelUtils.getSchemas(this.openAPI); + CodegenModel codegenModel = super.fromModel(name, model); + if (codegenModel.description != null) { + codegenModel.imports.add("ApiModel"); + } + if (allDefinitions != null) { + String parentSchema = codegenModel.parentSchema; + + // multilevel inheritance: reconcile properties of all the parents + while (parentSchema != null) { + final Schema parentModel = allDefinitions.get(parentSchema); + final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, + parentModel); + codegenModel = Swift5Codegen.reconcileProperties(codegenModel, parentCodegenModel); + + // get the next parent + parentSchema = parentCodegenModel.parentSchema; + } + } + + return codegenModel; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public void setUnwrapRequired(boolean unwrapRequired) { + this.unwrapRequired = unwrapRequired; + } + + public void setLenientTypeCast(boolean lenientTypeCast) { + this.lenientTypeCast = lenientTypeCast; + } + + public void setResponseAs(String[] responseAs) { + this.responseAs = responseAs; + } + + public void setSwiftUseApiNamespace(boolean swiftUseApiNamespace) { + this.swiftUseApiNamespace = swiftUseApiNamespace; + } + + @Override + public String toEnumValue(String value, String datatype) { + // for string, array of string + if ("String".equals(datatype) || "[String]".equals(datatype) || "[String:String]".equals(datatype)) { + return "\"" + String.valueOf(value) + "\""; + } else { + return String.valueOf(value); + } + } + + @Override + public String toEnumDefaultValue(String value, String datatype) { + return datatype + "_" + value; + } + + @Override + public String toEnumVarName(String name, String datatype) { + if (name.length() == 0) { + return "empty"; + } + + Pattern startWithNumberPattern = Pattern.compile("^\\d+"); + Matcher startWithNumberMatcher = startWithNumberPattern.matcher(name); + if (startWithNumberMatcher.find()) { + String startingNumbers = startWithNumberMatcher.group(0); + String nameWithoutStartingNumbers = name.substring(startingNumbers.length()); + + return "_" + startingNumbers + camelize(nameWithoutStartingNumbers, true); + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return camelize(WordUtils.capitalizeFully(getSymbolName(name).toUpperCase(Locale.ROOT)), true); + } + + // Camelize only when we have a structure defined below + Boolean camelized = false; + if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) { + name = camelize(name, true); + camelized = true; + } + + // Reserved Name + String nameLowercase = StringUtils.lowerCase(name); + if (isReservedWord(nameLowercase)) { + return escapeReservedWord(nameLowercase); + } + + // Check for numerical conversions + if ("Int".equals(datatype) || "Int32".equals(datatype) || "Int64".equals(datatype) + || "Float".equals(datatype) || "Double".equals(datatype)) { + String varName = "number" + camelize(name); + varName = varName.replaceAll("-", "minus"); + varName = varName.replaceAll("\\+", "plus"); + varName = varName.replaceAll("\\.", "dot"); + return varName; + } + + // If we have already camelized the word, don't progress + // any further + if (camelized) { + return name; + } + + char[] separators = {'-', '_', ' ', ':', '(', ')'}; + return camelize(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators) + .replaceAll("[-_ :\\(\\)]", ""), + true); + } + + @Override + public String toEnumName(CodegenProperty property) { + String enumName = toModelName(property.name); + + // Ensure that the enum type doesn't match a reserved word or + // the variable name doesn't match the generated enum type or the + // Swift compiler will generate an error + if (isReservedWord(property.datatypeWithEnum) + || toVarName(property.name).equals(property.datatypeWithEnum)) { + enumName = property.datatypeWithEnum + "Enum"; + } + + // TODO: toModelName already does something for names starting with number, + // so this code is probably never called + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public Map postProcessModels(Map objs) { + Map 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 models = (List) postProcessedModelsEnum.get("models"); + for (Object _mo : models) { + Map mo = (Map) _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 + 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 + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @Override + public void postProcessFile(File file, String fileType) { + if (file == null) { + return; + } + String swiftPostProcessFile = System.getenv("SWIFT_POST_PROCESS_FILE"); + if (StringUtils.isEmpty(swiftPostProcessFile)) { + return; // skip if SWIFT_POST_PROCESS_FILE env variable is not defined + } + // only process files with swift extension + if ("swift".equals(FilenameUtils.getExtension(file.toString()))) { + String command = swiftPostProcessFile + " " + file.toString(); + try { + Process p = Runtime.getRuntime().exec(command); + int exitValue = p.waitFor(); + if (exitValue != 0) { + LOGGER.error("Error running the command ({}). Exit value: {}", command, exitValue); + } else { + LOGGER.info("Successfully executed: " + command); + } + } catch (Exception e) { + LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage()); + } + } + } + + @Override + public Map postProcessOperationsWithModels(Map objs, List allModels) { + Map objectMap = (Map) objs.get("operations"); + + HashMap modelMaps = new HashMap(); + for (Object o : allModels) { + HashMap h = (HashMap) o; + CodegenModel m = (CodegenModel) h.get("model"); + modelMaps.put(m.classname, m); + } + + List operations = (List) objectMap.get("operation"); + for (CodegenOperation operation : operations) { + for (CodegenParameter cp : operation.allParams) { + cp.vendorExtensions.put("x-swift-example", constructExampleCode(cp, modelMaps)); + } + } + return objs; + } + + public String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps) { + if (codegenParameter.isListContainer) { // array + return "[" + constructExampleCode(codegenParameter.items, modelMaps) + "]"; + } else if (codegenParameter.isMapContainer) { // TODO: map, file type + return "\"TODO\""; + } else if (languageSpecificPrimitives.contains(codegenParameter.dataType)) { // primitive type + if ("String".equals(codegenParameter.dataType) || "Character".equals(codegenParameter.dataType)) { + if (StringUtils.isEmpty(codegenParameter.example)) { + return "\"" + codegenParameter.example + "\""; + } else { + return "\"" + codegenParameter.paramName + "_example\""; + } + } else if ("Bool".equals(codegenParameter.dataType)) { // boolean + if (Boolean.TRUE.equals(codegenParameter.example)) { + return "true"; + } else { + return "false"; + } + } else if ("URL".equals(codegenParameter.dataType)) { // URL + return "URL(string: \"https://example.com\")!"; + } else if ("Date".equals(codegenParameter.dataType)) { // date + return "Date()"; + } else { // numeric + if (StringUtils.isEmpty(codegenParameter.example)) { + return codegenParameter.example; + } else { + return "987"; + } + } + } else { // model + // look up the model + if (modelMaps.containsKey(codegenParameter.dataType)) { + return constructExampleCode(modelMaps.get(codegenParameter.dataType), modelMaps); + } else { + //LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType); + return "TODO"; + } + } + } + + public String constructExampleCode(CodegenProperty codegenProperty, HashMap modelMaps) { + if (codegenProperty.isListContainer) { // array + return "[" + constructExampleCode(codegenProperty.items, modelMaps) + "]"; + } else if (codegenProperty.isMapContainer) { // TODO: map, file type + return "\"TODO\""; + } else if (languageSpecificPrimitives.contains(codegenProperty.dataType)) { // primitive type + if ("String".equals(codegenProperty.dataType) || "Character".equals(codegenProperty.dataType)) { + if (StringUtils.isEmpty(codegenProperty.example)) { + return "\"" + codegenProperty.example + "\""; + } else { + return "\"" + codegenProperty.name + "_example\""; + } + } else if ("Bool".equals(codegenProperty.dataType)) { // boolean + if (Boolean.TRUE.equals(codegenProperty.example)) { + return "true"; + } else { + return "false"; + } + } else if ("URL".equals(codegenProperty.dataType)) { // URL + return "URL(string: \"https://example.com\")!"; + } else if ("Date".equals(codegenProperty.dataType)) { // date + return "Date()"; + } else { // numeric + if (StringUtils.isEmpty(codegenProperty.example)) { + return codegenProperty.example; + } else { + return "123"; + } + } + } else { + // look up the model + if (modelMaps.containsKey(codegenProperty.dataType)) { + return constructExampleCode(modelMaps.get(codegenProperty.dataType), modelMaps); + } else { + //LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenProperty.dataType); + return "\"TODO\""; + } + } + } + + public String constructExampleCode(CodegenModel codegenModel, HashMap modelMaps) { + String example; + example = codegenModel.name + "("; + List propertyExamples = new ArrayList<>(); + for (CodegenProperty codegenProperty : codegenModel.vars) { + propertyExamples.add(codegenProperty.name + ": " + constructExampleCode(codegenProperty, modelMaps)); + } + example += StringUtils.join(propertyExamples, ", "); + example += ")"; + return example; + } +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index ac693c68c87..add93a5d0fd 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -99,6 +99,7 @@ org.openapitools.codegen.languages.StaticHtml2Generator org.openapitools.codegen.languages.SwiftClientCodegen org.openapitools.codegen.languages.Swift3Codegen org.openapitools.codegen.languages.Swift4Codegen +org.openapitools.codegen.languages.Swift5Codegen org.openapitools.codegen.languages.TypeScriptAngularClientCodegen org.openapitools.codegen.languages.TypeScriptAngularJsClientCodegen org.openapitools.codegen.languages.TypeScriptAureliaClientCodegen diff --git a/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache new file mode 100644 index 00000000000..d94614b34fc --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache @@ -0,0 +1,71 @@ +// APIHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +public struct APIHelper { + public static func rejectNil(_ source: [String:Any?]) -> [String:Any]? { + let destination = source.reduce(into: [String: Any]()) { (result, item) in + if let value = item.value { + result[item.key] = value + } + } + + if destination.isEmpty { + return nil + } + return destination + } + + public static func rejectNilHeaders(_ source: [String:Any?]) -> [String:String] { + return source.reduce(into: [String: String]()) { (result, item) in + if let collection = item.value as? Array { + result[item.key] = collection.filter({ $0 != nil }).map{ "\($0!)" }.joined(separator: ",") + } else if let value: Any = item.value { + result[item.key] = "\(value)" + } + } + } + + public static func convertBoolToString(_ source: [String: Any]?) -> [String:Any]? { + guard let source = source else { + return nil + } + + return source.reduce(into: [String: Any](), { (result, item) in + switch item.value { + case let x as Bool: + result[item.key] = x.description + default: + result[item.key] = item.value + } + }) + } + + public static func mapValueToPathItem(_ source: Any) -> Any { + if let collection = source as? Array { + return collection.filter({ $0 != nil }).map({"\($0!)"}).joined(separator: ",") + } + return source + } + + public static func mapValuesToQueryItems(_ source: [String:Any?]) -> [URLQueryItem]? { + let destination = source.filter({ $0.value != nil}).reduce(into: [URLQueryItem]()) { (result, item) in + if let collection = item.value as? Array { + let value = collection.filter({ $0 != nil }).map({"\($0!)"}).joined(separator: ",") + result.append(URLQueryItem(name: item.key, value: value)) + } else if let value = item.value { + result.append(URLQueryItem(name: item.key, value: "\(value)")) + } + } + + if destination.isEmpty { + return nil + } + return destination + } +} + diff --git a/modules/openapi-generator/src/main/resources/swift5/APIs.mustache b/modules/openapi-generator/src/main/resources/swift5/APIs.mustache new file mode 100644 index 00000000000..6ed1abe5694 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/APIs.mustache @@ -0,0 +1,61 @@ +// APIs.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +open class {{projectName}}API { + public static var basePath = "{{{basePath}}}" + public static var credential: URLCredential? + public static var customHeaders: [String:String] = [:] + public static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory() +} + +open class RequestBuilder { + var credential: URLCredential? + var headers: [String:String] + public let parameters: [String:Any]? + public let isBody: Bool + public let method: String + public let URLString: String + + /// Optional block to obtain a reference to the request's progress instance when available. + public var onProgressReady: ((Progress) -> ())? + + required public init(method: String, URLString: String, parameters: [String:Any]?, isBody: Bool, headers: [String:String] = [:]) { + self.method = method + self.URLString = URLString + self.parameters = parameters + self.isBody = isBody + self.headers = headers + + addHeaders({{projectName}}API.customHeaders) + } + + open func addHeaders(_ aHeaders:[String:String]) { + for (header, value) in aHeaders { + headers[header] = value + } + } + + open func execute(_ completion: @escaping (_ response: Response?, _ error: Error?) -> Void) { } + + public func addHeader(name: String, value: String) -> Self { + if !value.isEmpty { + headers[name] = value + } + return self + } + + open func addCredential() -> Self { + self.credential = {{projectName}}API.credential + return self + } +} + +public protocol RequestBuilderFactory { + func getNonDecodableBuilder() -> RequestBuilder.Type + func getBuilder() -> RequestBuilder.Type +} diff --git a/modules/openapi-generator/src/main/resources/swift5/AlamofireImplementations.mustache b/modules/openapi-generator/src/main/resources/swift5/AlamofireImplementations.mustache new file mode 100644 index 00000000000..dac40e9a31c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/AlamofireImplementations.mustache @@ -0,0 +1,451 @@ +// AlamofireImplementations.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire + +class AlamofireRequestBuilderFactory: RequestBuilderFactory { + func getNonDecodableBuilder() -> RequestBuilder.Type { + return AlamofireRequestBuilder.self + } + + func getBuilder() -> RequestBuilder.Type { + return AlamofireDecodableRequestBuilder.self + } +} + +private struct SynchronizedDictionary { + + private var dictionary = [K: V]() + private let queue = DispatchQueue( + label: "SynchronizedDictionary", + qos: DispatchQoS.userInitiated, + attributes: [DispatchQueue.Attributes.concurrent], + autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, + target: nil + ) + + public subscript(key: K) -> V? { + get { + var value: V? + + queue.sync { + value = self.dictionary[key] + } + + return value + } + set { + queue.sync(flags: DispatchWorkItemFlags.barrier) { + self.dictionary[key] = newValue + } + } + } + } + +// Store manager to retain its reference +private var managerStore = SynchronizedDictionary() + +open class AlamofireRequestBuilder: RequestBuilder { + required public init(method: String, URLString: String, parameters: [String : Any]?, isBody: Bool, headers: [String : String] = [:]) { + super.init(method: method, URLString: URLString, parameters: parameters, isBody: isBody, headers: headers) + } + + /** + May be overridden by a subclass if you want to control the session + configuration. + */ + open func createSessionManager() -> Alamofire.SessionManager { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = buildHeaders() + return Alamofire.SessionManager(configuration: configuration) + } + + /** + May be overridden by a subclass if you want to custom request constructor. + */ + open func createURLRequest() -> URLRequest? { + let encoding: ParameterEncoding = isBody ? JSONDataEncoding() : URLEncoding() + guard let originalRequest = try? URLRequest(url: URLString, method: HTTPMethod(rawValue: method)!, headers: buildHeaders()) else { return nil } + return try? encoding.encode(originalRequest, with: parameters) + } + + /** + May be overridden by a subclass if you want to control the Content-Type + that is given to an uploaded form part. + + Return nil to use the default behavior (inferring the Content-Type from + the file extension). Return the desired Content-Type otherwise. + */ + open func contentTypeForFormPart(fileURL: URL) -> String? { + return nil + } + + /** + May be overridden by a subclass if you want to control the request + configuration (e.g. to override the cache policy). + */ + open func makeRequest(manager: SessionManager, method: HTTPMethod, encoding: ParameterEncoding, headers: [String:String]) -> DataRequest { + return manager.request(URLString, method: method, parameters: parameters, encoding: encoding, headers: headers) + } + + override open func execute(_ completion: @escaping (_ response: Response?, _ error: Error?) -> Void) { + let managerId:String = UUID().uuidString + // Create a new manager for each request to customize its request header + let manager = createSessionManager() + managerStore[managerId] = manager + + let encoding:ParameterEncoding = isBody ? JSONDataEncoding() : URLEncoding() + + let xMethod = Alamofire.HTTPMethod(rawValue: method) + let fileKeys = parameters == nil ? [] : parameters!.filter { $1 is NSURL } + .map { $0.0 } + + if fileKeys.count > 0 { + manager.upload(multipartFormData: { mpForm in + for (k, v) in self.parameters! { + switch v { + case let fileURL as URL: + if let mimeType = self.contentTypeForFormPart(fileURL: fileURL) { + mpForm.append(fileURL, withName: k, fileName: fileURL.lastPathComponent, mimeType: mimeType) + } + else { + mpForm.append(fileURL, withName: k) + } + case let string as String: + mpForm.append(string.data(using: String.Encoding.utf8)!, withName: k) + case let number as NSNumber: + mpForm.append(number.stringValue.data(using: String.Encoding.utf8)!, withName: k) + default: + fatalError("Unprocessable value \(v) with key \(k)") + } + } + }, to: URLString, method: xMethod!, headers: nil, encodingCompletion: { encodingResult in + switch encodingResult { + case .success(let upload, _, _): + if let onProgressReady = self.onProgressReady { + onProgressReady(upload.uploadProgress) + } + self.processRequest(request: upload, managerId, completion) + case .failure(let encodingError): + completion(nil, ErrorResponse.error(415, nil, encodingError)) + } + }) + } else { + let request = makeRequest(manager: manager, method: xMethod!, encoding: encoding, headers: headers) + if let onProgressReady = self.onProgressReady { + onProgressReady(request.progress) + } + processRequest(request: request, managerId, completion) + } + + } + + fileprivate func processRequest(request: DataRequest, _ managerId: String, _ completion: @escaping (_ response: Response?, _ error: Error?) -> Void) { + if let credential = self.credential { + request.authenticate(usingCredential: credential) + } + + let cleanupRequest = { + managerStore[managerId] = nil + } + + let validatedRequest = request.validate() + + switch T.self { + case is String.Type: + validatedRequest.responseString(completionHandler: { (stringResponse) in + cleanupRequest() + + if stringResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error!) + ) + return + } + + completion( + Response( + response: stringResponse.response!, + body: ((stringResponse.result.value ?? "") as! T) + ), + nil + ) + }) + case is URL.Type: + validatedRequest.responseData(completionHandler: { (dataResponse) in + cleanupRequest() + + do { + + guard !dataResponse.result.isFailure else { + throw DownloadException.responseFailed + } + + guard let data = dataResponse.data else { + throw DownloadException.responseDataMissing + } + + guard let request = request.request else { + throw DownloadException.requestMissing + } + + let fileManager = FileManager.default + let urlRequest = try request.asURLRequest() + let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + let requestURL = try self.getURL(from: urlRequest) + + var requestPath = try self.getPath(from: requestURL) + + if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) { + requestPath = requestPath.appending("/\(headerFileName)") + } + + let filePath = documentsDirectory.appendingPathComponent(requestPath) + let directoryPath = filePath.deletingLastPathComponent().path + + try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) + try data.write(to: filePath, options: .atomic) + + completion( + Response( + response: dataResponse.response!, + body: (filePath as! T) + ), + nil + ) + + } catch let requestParserError as DownloadException { + completion(nil, ErrorResponse.error(400, dataResponse.data, requestParserError)) + } catch let error { + completion(nil, ErrorResponse.error(400, dataResponse.data, error)) + } + return + }) + case is Void.Type: + validatedRequest.responseData(completionHandler: { (voidResponse) in + cleanupRequest() + + if voidResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.result.error!) + ) + return + } + + completion( + Response( + response: voidResponse.response!, + body: nil), + nil + ) + }) + default: + validatedRequest.responseData(completionHandler: { (dataResponse) in + cleanupRequest() + + if dataResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.result.error!) + ) + return + } + + completion( + Response( + response: dataResponse.response!, + body: (dataResponse.data as! T) + ), + nil + ) + }) + } + } + + open func buildHeaders() -> [String: String] { + var httpHeaders = SessionManager.defaultHTTPHeaders + for (key, value) in self.headers { + httpHeaders[key] = value + } + return httpHeaders + } + + fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? { + + guard let contentDisposition = contentDisposition else { + return nil + } + + let items = contentDisposition.components(separatedBy: ";") + + var filename : String? = nil + + for contentItem in items { + + let filenameKey = "filename=" + guard let range = contentItem.range(of: filenameKey) else { + break + } + + filename = contentItem + return filename? + .replacingCharacters(in: range, with:"") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + return filename + + } + + fileprivate func getPath(from url : URL) throws -> String { + + guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { + throw DownloadException.requestMissingPath + } + + if path.hasPrefix("/") { + path.remove(at: path.startIndex) + } + + return path + + } + + fileprivate func getURL(from urlRequest : URLRequest) throws -> URL { + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + return url + } + +} + +fileprivate enum DownloadException : Error { + case responseDataMissing + case responseFailed + case requestMissing + case requestMissingPath + case requestMissingURL +} + +public enum AlamofireDecodableRequestBuilderError: Error { + case emptyDataResponse + case nilHTTPResponse + case jsonDecoding(DecodingError) + case generalError(Error) +} + +open class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder { + + override fileprivate func processRequest(request: DataRequest, _ managerId: String, _ completion: @escaping (_ response: Response?, _ error: Error?) -> Void) { + if let credential = self.credential { + request.authenticate(usingCredential: credential) + } + + let cleanupRequest = { + managerStore[managerId] = nil + } + + let validatedRequest = request.validate() + + switch T.self { + case is String.Type: + validatedRequest.responseString(completionHandler: { (stringResponse) in + cleanupRequest() + + if stringResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error!) + ) + return + } + + completion( + Response( + response: stringResponse.response!, + body: ((stringResponse.result.value ?? "") as! T) + ), + nil + ) + }) + case is Void.Type: + validatedRequest.responseData(completionHandler: { (voidResponse) in + cleanupRequest() + + if voidResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.result.error!) + ) + return + } + + completion( + Response( + response: voidResponse.response!, + body: nil), + nil + ) + }) + case is Data.Type: + validatedRequest.responseData(completionHandler: { (dataResponse) in + cleanupRequest() + + if dataResponse.result.isFailure { + completion( + nil, + ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.result.error!) + ) + return + } + + completion( + Response( + response: dataResponse.response!, + body: (dataResponse.data as! T) + ), + nil + ) + }) + default: + validatedRequest.responseData(completionHandler: { (dataResponse: DataResponse) in + cleanupRequest() + + guard dataResponse.result.isSuccess else { + completion(nil, ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.result.error!)) + return + } + + guard let data = dataResponse.data, !data.isEmpty else { + completion(nil, ErrorResponse.error(-1, nil, AlamofireDecodableRequestBuilderError.emptyDataResponse)) + return + } + + guard let httpResponse = dataResponse.response else { + completion(nil, ErrorResponse.error(-2, nil, AlamofireDecodableRequestBuilderError.nilHTTPResponse)) + return + } + + var responseObj: Response? = nil + + let decodeResult: (decodableObj: T?, error: Error?) = CodableHelper.decode(T.self, from: data) + if decodeResult.error == nil { + responseObj = Response(response: httpResponse, body: decodeResult.decodableObj) + } + + completion(responseObj, decodeResult.error) + }) + } + } + +} diff --git a/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache b/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache new file mode 100644 index 00000000000..b8548fc548c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache @@ -0,0 +1,3 @@ +github "Alamofire/Alamofire" ~> 4.5.0{{#usePromiseKit}} +github "mxcl/PromiseKit" ~> 4.4{{/usePromiseKit}}{{#useRxSwift}} +github "ReactiveX/RxSwift" ~> 4.0{{/useRxSwift}} diff --git a/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache new file mode 100644 index 00000000000..584de8c3d57 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache @@ -0,0 +1,71 @@ +// +// CodableHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +public typealias EncodeResult = (data: Data?, error: Error?) + +open class CodableHelper { + + public static var dateformatter: DateFormatter? + + open class func decode(_ type: T.Type, from data: Data) -> (decodableObj: T?, error: Error?) where T : Decodable { + var returnedDecodable: T? = nil + var returnedError: Error? = nil + + let decoder = JSONDecoder() + if let df = self.dateformatter { + decoder.dateDecodingStrategy = .formatted(df) + } else { + decoder.dataDecodingStrategy = .base64 + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = Configuration.dateFormat + decoder.dateDecodingStrategy = .formatted(formatter) + } + + do { + returnedDecodable = try decoder.decode(type, from: data) + } catch { + returnedError = error + } + + return (returnedDecodable, returnedError) + } + + open class func encode(_ value: T, prettyPrint: Bool = false) -> EncodeResult where T : Encodable { + var returnedData: Data? + var returnedError: Error? = nil + + let encoder = JSONEncoder() + if prettyPrint { + encoder.outputFormatting = .prettyPrinted + } + if let df = self.dateformatter { + encoder.dateEncodingStrategy = .formatted(df) + } else { + encoder.dataEncodingStrategy = .base64 + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = Configuration.dateFormat + encoder.dateEncodingStrategy = .formatted(formatter) + } + + do { + returnedData = try encoder.encode(value) + } catch { + returnedError = error + } + + return (returnedData, returnedError) + } + +} diff --git a/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache b/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache new file mode 100644 index 00000000000..516590da5d9 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache @@ -0,0 +1,15 @@ +// Configuration.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +open class Configuration { + + // This value is used to configure the date formatter that is used to serialize dates into JSON format. + // You must set it prior to encoding any dates, and it will only be read once. + public static var dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache b/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache new file mode 100644 index 00000000000..61d55e8afec --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache @@ -0,0 +1,192 @@ +// Extensions.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire{{#usePromiseKit}} +import PromiseKit{{/usePromiseKit}} + +extension Bool: JSONEncodable { + func encodeToJSON() -> Any { return self as Any } +} + +extension Float: JSONEncodable { + func encodeToJSON() -> Any { return self as Any } +} + +extension Int: JSONEncodable { + func encodeToJSON() -> Any { return self as Any } +} + +extension Int32: JSONEncodable { + func encodeToJSON() -> Any { return NSNumber(value: self as Int32) } +} + +extension Int64: JSONEncodable { + func encodeToJSON() -> Any { return NSNumber(value: self as Int64) } +} + +extension Double: JSONEncodable { + func encodeToJSON() -> Any { return self as Any } +} + +extension String: JSONEncodable { + func encodeToJSON() -> Any { return self as Any } +} + +private func encodeIfPossible(_ object: T) -> Any { + if let encodableObject = object as? JSONEncodable { + return encodableObject.encodeToJSON() + } else { + return object as Any + } +} + +extension Array: JSONEncodable { + func encodeToJSON() -> Any { + return self.map(encodeIfPossible) + } +} + +extension Dictionary: JSONEncodable { + func encodeToJSON() -> Any { + var dictionary = [AnyHashable: Any]() + for (key, value) in self { + dictionary[key] = encodeIfPossible(value) + } + return dictionary as Any + } +} + +extension Data: JSONEncodable { + func encodeToJSON() -> Any { + return self.base64EncodedString(options: Data.Base64EncodingOptions()) + } +} + +private let dateFormatter: DateFormatter = { + if let formatter = CodableHelper.dateformatter { + return formatter + } else { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = Configuration.dateFormat + return formatter + } +}() + +extension Date: JSONEncodable { + func encodeToJSON() -> Any { + return dateFormatter.string(from: self) as Any + } +} + +extension UUID: JSONEncodable { + func encodeToJSON() -> Any { + return self.uuidString + } +} + +extension String: CodingKey { + + public var stringValue: String { + return self + } + + public init?(stringValue: String) { + self.init(stringLiteral: stringValue) + } + + public var intValue: Int? { + return nil + } + + public init?(intValue: Int) { + return nil + } + +} + +extension KeyedEncodingContainerProtocol { + + public mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T : Encodable { + var arrayContainer = nestedUnkeyedContainer(forKey: key) + try arrayContainer.encode(contentsOf: values) + } + + public mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T : Encodable { + if let values = values { + try encodeArray(values, forKey: key) + } + } + + public mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T : Encodable { + for (key, value) in pairs { + try encode(value, forKey: key) + } + } + + public mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T : Encodable { + if let pairs = pairs { + try encodeMap(pairs) + } + } + +} + +extension KeyedDecodingContainerProtocol { + + public func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable { + var tmpArray = [T]() + + var nestedContainer = try nestedUnkeyedContainer(forKey: key) + while !nestedContainer.isAtEnd { + let arrayValue = try nestedContainer.decode(T.self) + tmpArray.append(arrayValue) + } + + return tmpArray + } + + public func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T : Decodable { + var tmpArray: [T]? = nil + + if contains(key) { + tmpArray = try decodeArray(T.self, forKey: key) + } + + return tmpArray + } + + public func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T : Decodable { + var map: [Self.Key : T] = [:] + + for key in allKeys { + if !excludedKeys.contains(key) { + let value = try decode(T.self, forKey: key) + map[key] = value + } + } + + return map + } + +} + +{{#usePromiseKit}}extension RequestBuilder { + public func execute() -> Promise> { + let deferred = Promise>.pending() + self.execute { (response: Response?, error: Error?) in + if let response = response { + deferred.fulfill(response) + } else { + deferred.reject(error!) + } + } + return deferred.promise + } +}{{/usePromiseKit}} diff --git a/modules/openapi-generator/src/main/resources/swift5/JSONEncodableEncoding.mustache b/modules/openapi-generator/src/main/resources/swift5/JSONEncodableEncoding.mustache new file mode 100644 index 00000000000..ca05906d420 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/JSONEncodableEncoding.mustache @@ -0,0 +1,54 @@ +// +// JSONDataEncoding.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire + +public struct JSONDataEncoding: ParameterEncoding { + + // MARK: Properties + + private static let jsonDataKey = "jsonData" + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. This should have a single key/value + /// pair with "jsonData" as the key and a Data object as the value. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let jsonData = parameters?[JSONDataEncoding.jsonDataKey] as? Data, !jsonData.isEmpty else { + return urlRequest + } + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = jsonData + + return urlRequest + } + + public static func encodingParameters(jsonData: Data?) -> Parameters? { + var returnedParams: Parameters? = nil + if let jsonData = jsonData, !jsonData.isEmpty { + var params = Parameters() + params[jsonDataKey] = jsonData + returnedParams = params + } + return returnedParams + } + +} diff --git a/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache new file mode 100644 index 00000000000..70449515842 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache @@ -0,0 +1,43 @@ +// +// JSONEncodingHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire + +open class JSONEncodingHelper { + + open class func encodingParameters(forEncodableObject encodableObj: T?) -> Parameters? { + var params: Parameters? = nil + + // Encode the Encodable object + if let encodableObj = encodableObj { + let encodeResult = CodableHelper.encode(encodableObj, prettyPrint: true) + if encodeResult.error == nil { + params = JSONDataEncoding.encodingParameters(jsonData: encodeResult.data) + } + } + + return params + } + + open class func encodingParameters(forEncodableObject encodableObj: Any?) -> Parameters? { + var params: Parameters? = nil + + if let encodableObj = encodableObj { + do { + let data = try JSONSerialization.data(withJSONObject: encodableObj, options: .prettyPrinted) + params = JSONDataEncoding.encodingParameters(jsonData: data) + } catch { + print(error) + return nil + } + } + + return params + } + +} diff --git a/modules/openapi-generator/src/main/resources/swift5/Models.mustache b/modules/openapi-generator/src/main/resources/swift5/Models.mustache new file mode 100644 index 00000000000..40856389035 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/Models.mustache @@ -0,0 +1,36 @@ +// Models.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +protocol JSONEncodable { + func encodeToJSON() -> Any +} + +public enum ErrorResponse : Error { + case error(Int, Data?, Error) +} + +open class Response { + public let statusCode: Int + public let header: [String: String] + public let body: T? + + public init(statusCode: Int, header: [String: String], body: T?) { + self.statusCode = statusCode + self.header = header + self.body = body + } + + public convenience init(response: HTTPURLResponse, body: T?) { + let rawHeader = response.allHeaderFields + var header = [String:String]() + for case let (key, value) as (String, String) in rawHeader { + header[key] = value + } + self.init(statusCode: response.statusCode, header: header, body: body) + } +} diff --git a/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache b/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache new file mode 100644 index 00000000000..ad2548a2461 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache @@ -0,0 +1,38 @@ +Pod::Spec.new do |s| + s.name = '{{projectName}}'{{#projectDescription}} + s.summary = '{{projectDescription}}'{{/projectDescription}} + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '9.0' + s.version = '{{#podVersion}}{{podVersion}}{{/podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}}' + s.source = {{#podSource}}{{& podSource}}{{/podSource}}{{^podSource}}{ :git => 'git@github.com:OpenAPITools/openapi-generator.git', :tag => 'v{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}' }{{/podSource}} + {{#podAuthors}} + s.authors = '{{podAuthors}}' + {{/podAuthors}} + {{#podSocialMediaURL}} + s.social_media_url = '{{podSocialMediaURL}}' + {{/podSocialMediaURL}} + {{#podDocsetURL}} + s.docset_url = '{{podDocsetURL}}' + {{/podDocsetURL}} + s.license = {{#podLicense}}{{& podLicense}}{{/podLicense}}{{^podLicense}}'Proprietary'{{/podLicense}} + s.homepage = '{{podHomepage}}{{^podHomepage}}https://github.com/OpenAPITools/openapi-generator{{/podHomepage}}' + s.summary = '{{podSummary}}{{^podSummary}}{{projectName}} Swift SDK{{/podSummary}}' + {{#podDescription}} + s.description = '{{podDescription}}' + {{/podDescription}} + {{#podScreenshots}} + s.screenshots = {{& podScreenshots}} + {{/podScreenshots}} + {{#podDocumentationURL}} + s.documentation_url = '{{podDocumentationURL}}' + {{/podDocumentationURL}} + s.source_files = '{{projectName}}/Classes/**/*.swift' + {{^onlyPlainInterface}}{{#usePromiseKit}} + s.dependency 'PromiseKit/CorePromise', '~> 4.4.0' + {{/usePromiseKit}} + {{#useRxSwift}} + s.dependency 'RxSwift', '~> 4.0' + {{/useRxSwift}} + s.dependency 'Alamofire', '~> 4.7.0'{{/onlyPlainInterface}} +end diff --git a/modules/openapi-generator/src/main/resources/swift5/README.mustache b/modules/openapi-generator/src/main/resources/swift5/README.mustache new file mode 100644 index 00000000000..8fe60510aa8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/README.mustache @@ -0,0 +1,69 @@ +# Swift4 API client for {{{projectName}}} + +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +## Overview +This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec from a remote server, you can easily generate an API client. + +- API version: {{appVersion}} +- Package version: {{packageVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Build package: {{generatorClass}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Installation + +### Carthage + +Run `carthage update` + +### CocoaPods + +Run `pod install` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + +## Documentation For Authorization + +{{^authMethods}} All endpoints do not require authorization. +{{/authMethods}}{{#authMethods}}{{#last}} Authentication schemes defined for the API:{{/last}}{{/authMethods}} +{{#authMethods}}## {{{name}}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache b/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache new file mode 100644 index 00000000000..872c7b0f572 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache @@ -0,0 +1,12 @@ +name: {{projectName}} +targets: + {{projectName}}: + type: framework + platform: iOS + deploymentTarget: "10.0" + sources: [{{projectName}}] + info: + path: ./Info.plist + settings: + APPLICATION_EXTENSION_API_ONLY: true + SWIFT_VERSION: "5.0" \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/_param.mustache b/modules/openapi-generator/src/main/resources/swift5/_param.mustache new file mode 100644 index 00000000000..5caacbc6005 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/_param.mustache @@ -0,0 +1 @@ +"{{baseName}}": {{paramName}}{{^isEnum}}{{#isInteger}}{{^required}}?{{/required}}.encodeToJSON(){{/isInteger}}{{#isLong}}{{^required}}?{{/required}}.encodeToJSON(){{/isLong}}{{/isEnum}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}.rawValue{{/isContainer}}{{/isEnum}}{{#isDate}}{{^required}}?{{/required}}.encodeToJSON(){{/isDate}}{{#isDateTime}}{{^required}}?{{/required}}.encodeToJSON(){{/isDateTime}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/api.mustache b/modules/openapi-generator/src/main/resources/swift5/api.mustache new file mode 100644 index 00000000000..c6eeab2d97e --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/api.mustache @@ -0,0 +1,190 @@ +{{#operations}}// +// {{classname}}.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire{{#usePromiseKit}} +import PromiseKit{{/usePromiseKit}}{{#useRxSwift}} +import RxSwift{{/useRxSwift}} + +{{#swiftUseApiNamespace}} +extension {{projectName}}API { +{{/swiftUseApiNamespace}} + +{{#description}} +/** {{description}} */{{/description}} +open class {{classname}} { +{{#operation}} + {{#allParams}} + {{#isEnum}} + /** + * enum for parameter {{paramName}} + */ + public enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}} { + {{#allowableValues}} + {{#enumVars}} + case {{name}} = {{{value}}} + {{/enumVars}} + {{/allowableValues}} + } + + {{/isEnum}} + {{/allParams}} +{{^usePromiseKit}} +{{^useRxSwift}} + /** + {{#summary}} + {{{summary}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - parameter completion: completion handler to receive the data and the error objects + */ + open class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}completion: @escaping ((_ data: {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}?,_ error: Error?) -> Void)) { + {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}).execute { (response, error) -> Void in + {{#returnType}} + completion(response?.body, error) + {{/returnType}} + {{^returnType}} + if error == nil { + completion((), error) + } else { + completion(nil, error) + } + {{/returnType}} + } + } +{{/useRxSwift}} +{{/usePromiseKit}} +{{#usePromiseKit}} + /** + {{#summary}} + {{{summary}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - returns: Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + */ + open class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + let deferred = Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.pending() + {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}).execute { (response, error) -> Void in + if let error = error { + deferred.reject(error) +{{#returnType}} + } else if let response = response { + deferred.fulfill(response.body!) + } else { + fatalError() +{{/returnType}} +{{^returnType}} + } else { + deferred.fulfill(()) +{{/returnType}} + } + } + return deferred.promise + } +{{/usePromiseKit}} +{{#useRxSwift}} + /** + {{#summary}} + {{{summary}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - returns: Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + */ + open class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + return Observable.create { observer -> Disposable in + {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}).execute { (response, error) -> Void in + if let error = error { + observer.onError(error) +{{#returnType}} + } else if let response = response { + observer.onNext(response.body!) + } else { + fatalError() +{{/returnType}} +{{^returnType}} + } else { + observer.onNext(()) +{{/returnType}} + } + observer.onCompleted() + } + return Disposables.create() + } + } +{{/useRxSwift}} + + /** + {{#summary}} + {{{summary}}} + {{/summary}} + - {{httpMethod}} {{{path}}}{{#notes}} + - {{{notes}}}{{/notes}}{{#subresourceOperation}} + - subresourceOperation: {{subresourceOperation}}{{/subresourceOperation}}{{#defaultResponse}} + - defaultResponse: {{defaultResponse}}{{/defaultResponse}} + {{#authMethods}} + - {{#isBasic}}BASIC{{/isBasic}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: + - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeaer}}(HEADER){{/isKeyInHeaer}}{{/keyParamName}} + - name: {{name}} + {{/authMethods}} + {{#hasResponseHeaders}} + - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] + {{/hasResponseHeaders}} + {{#externalDocs}} + - externalDocs: {{externalDocs}} + {{/externalDocs}} + {{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + - returns: RequestBuilder<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{description}} + */ + open class func {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> RequestBuilder<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{^secondaryParam}}var{{/secondaryParam}}{{/pathParams}} path = "{{{path}}}"{{#pathParams}} + let {{paramName}}PreEscape = "\({{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}APIHelper.mapValueToPathItem({{paramName}}){{/isEnum}})" + let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + path = path.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} + let URLString = {{projectName}}API.basePath + path + {{#bodyParam}} + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: {{paramName}}) + {{/bodyParam}} + {{^bodyParam}} + {{#hasFormParams}} + let formParams: [String:Any?] = [ + {{#formParams}} + {{> _param}}{{#hasMore}},{{/hasMore}} + {{/formParams}} + ] + + let nonNullParameters = APIHelper.rejectNil(formParams) + let parameters = APIHelper.convertBoolToString(nonNullParameters) + {{/hasFormParams}} + {{^hasFormParams}} + let parameters: [String:Any]? = nil + {{/hasFormParams}} + {{/bodyParam}}{{#hasQueryParams}} + var url = URLComponents(string: URLString) + url?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}} + {{#queryParams}} + {{> _param}}{{#hasMore}}, {{/hasMore}} + {{/queryParams}} + ]){{/hasQueryParams}}{{^hasQueryParams}} + let url = URLComponents(string: URLString){{/hasQueryParams}}{{#headerParams}}{{^secondaryParam}} + let nillableHeaders: [String: Any?] = [{{/secondaryParam}} + {{> _param}}{{#hasMore}},{{/hasMore}}{{^hasMore}} + ] + let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders){{/hasMore}}{{/headerParams}} + + let requestBuilder: RequestBuilder<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}API.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} + + return requestBuilder.init(method: "{{httpMethod}}", URLString: (url?.string ?? URLString), parameters: parameters, isBody: {{hasBodyParam}}{{#headerParams}}{{^secondaryParam}}, headers: headerParameters{{/secondaryParam}}{{/headerParams}}) + } + +{{/operation}} +} +{{#swiftUseApiNamespace}} +} +{{/swiftUseApiNamespace}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/swift5/api_doc.mustache b/modules/openapi-generator/src/main/resources/swift5/api_doc.mustache new file mode 100644 index 00000000000..9ea5ae16149 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/api_doc.mustache @@ -0,0 +1,97 @@ +# {{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{{basePath}}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +# **{{{operationId}}}** +```swift +{{^usePromiseKit}} +{{^useRxSwift}} + open class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}completion: @escaping (_ data: {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void) +{{/useRxSwift}} +{{/usePromiseKit}} +{{#usePromiseKit}} + open class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> +{{/usePromiseKit}} +{{#useRxSwift}} + open class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> +{{/useRxSwift}} +``` + +{{{summary}}}{{#notes}} + +{{{notes}}}{{/notes}} + +### Example +```swift +// The following code samples are still beta. For any issue, please report via http://github.com/OpenAPITools/openapi-generator/issues/new +import {{{projectName}}} + +{{#allParams}}let {{paramName}} = {{{vendorExtensions.x-swift-example}}} // {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/allParams}} + +{{^usePromiseKit}} +{{^useRxSwift}} +{{#summary}} +// {{{.}}} +{{/summary}} +{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) { (response, error) in + guard error == nil else { + print(error) + return + } + + if (response) { + dump(response) + } +} +{{/useRxSwift}} +{{/usePromiseKit}} +{{#usePromiseKit}} +{{#summary}} +// {{{.}}} +{{/summary}} +{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}).then { + // when the promise is fulfilled + }.always { + // regardless of whether the promise is fulfilled, or rejected + }.catch { errorType in + // when the promise is rejected +} +{{/usePromiseKit}} +{{#useRxSwift}} +// TODO RxSwift sample code not yet implemented. To contribute, please open a ticket via http://github.com/OpenAPITools/openapi-generator/issues/new +{{/useRxSwift}} +``` + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}Void (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/swift5/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/swift5/git_push.sh.mustache new file mode 100755 index 00000000000..8a32e53995d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/git_push.sh.mustache @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/modules/openapi-generator/src/main/resources/swift5/gitignore.mustache b/modules/openapi-generator/src/main/resources/swift5/gitignore.mustache new file mode 100644 index 00000000000..5e5d5cebcf4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/gitignore.mustache @@ -0,0 +1,63 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md + +fastlane/report.xml +fastlane/screenshots diff --git a/modules/openapi-generator/src/main/resources/swift5/model.mustache b/modules/openapi-generator/src/main/resources/swift5/model.mustache new file mode 100644 index 00000000000..806cc969b54 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/model.mustache @@ -0,0 +1,25 @@ +{{#models}}{{#model}}// +// {{classname}}.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +{{#description}} + +/** {{description}} */{{/description}} +{{#isArrayModel}} +{{> modelArray}} +{{/isArrayModel}} +{{^isArrayModel}} +{{#isEnum}} +{{> modelEnum}} +{{/isEnum}} +{{^isEnum}} +{{> modelObject}} +{{/isEnum}} +{{/isArrayModel}} +{{/model}} +{{/models}} diff --git a/modules/openapi-generator/src/main/resources/swift5/modelArray.mustache b/modules/openapi-generator/src/main/resources/swift5/modelArray.mustache new file mode 100644 index 00000000000..843626158cd --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/modelArray.mustache @@ -0,0 +1 @@ +public typealias {{classname}} = {{parent}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/modelEnum.mustache b/modules/openapi-generator/src/main/resources/swift5/modelEnum.mustache new file mode 100644 index 00000000000..91c2efd54af --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/modelEnum.mustache @@ -0,0 +1,7 @@ +public enum {{classname}}: {{dataType}}, Codable { +{{#allowableValues}} +{{#enumVars}} + case {{name}} = {{{value}}} +{{/enumVars}} +{{/allowableValues}} +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/modelInlineEnumDeclaration.mustache b/modules/openapi-generator/src/main/resources/swift5/modelInlineEnumDeclaration.mustache new file mode 100644 index 00000000000..e2a3d08e912 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/modelInlineEnumDeclaration.mustache @@ -0,0 +1,7 @@ + public enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Codable { + {{#allowableValues}} + {{#enumVars}} + case {{name}} = {{{value}}} + {{/enumVars}} + {{/allowableValues}} + } \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache new file mode 100644 index 00000000000..d477ac7880d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache @@ -0,0 +1,82 @@ + +public struct {{classname}}: Codable { + +{{#allVars}} +{{#isEnum}} +{{> modelInlineEnumDeclaration}} +{{/isEnum}} +{{/allVars}} +{{#allVars}} +{{#isEnum}} + {{#description}}/** {{description}} */ + {{/description}}public var {{name}}: {{{datatypeWithEnum}}}{{#unwrapRequired}}?{{/unwrapRequired}}{{^unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}} +{{/isEnum}} +{{^isEnum}} + {{#description}}/** {{description}} */ + {{/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}} +{{/allVars}} + +{{#hasVars}} + public init({{#allVars}}{{name}}: {{{datatypeWithEnum}}}{{#unwrapRequired}}?{{/unwrapRequired}}{{^unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#hasMore}}, {{/hasMore}}{{/allVars}}) { + {{#allVars}} + self.{{name}} = {{name}} + {{/allVars}} + } +{{/hasVars}} +{{#additionalPropertiesType}} + public var additionalProperties: [String:{{{additionalPropertiesType}}}] = [:] + + public subscript(key: String) -> {{{additionalPropertiesType}}}? { + get { + if let value = additionalProperties[key] { + return value + } + return nil + } + + set { + additionalProperties[key] = newValue + } + } + + // Encodable protocol methods + + public func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: String.self) + + {{#allVars}} + try container.encode{{#unwrapRequired}}IfPresent{{/unwrapRequired}}{{^unwrapRequired}}{{^required}}IfPresent{{/required}}{{/unwrapRequired}}({{{name}}}, forKey: "{{{baseName}}}") + {{/allVars}} + try container.encodeMap(additionalProperties) + } + + // Decodable protocol methods + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: String.self) + + {{#allVars}} + {{name}} = try container.decode{{#unwrapRequired}}IfPresent{{/unwrapRequired}}{{^unwrapRequired}}{{^required}}IfPresent{{/required}}{{/unwrapRequired}}({{{datatypeWithEnum}}}.self, forKey: "{{{baseName}}}") + {{/allVars}} + var nonAdditionalPropertyKeys = Set() + {{#allVars}} + nonAdditionalPropertyKeys.insert("{{{baseName}}}") + {{/allVars}} + additionalProperties = try container.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys) + } + +{{/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}} + +} diff --git a/modules/openapi-generator/src/main/resources/swift5/model_doc.mustache b/modules/openapi-generator/src/main/resources/swift5/model_doc.mustache new file mode 100644 index 00000000000..d912845d33f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/swift5/model_doc.mustache @@ -0,0 +1,11 @@ +{{#models}}{{#model}}# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isContainer}}[**{{dataType}}**]({{complexType}}.md){{/isContainer}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/AnotherFakeAPI.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/AnotherFakeAPI.swift index ffecb66c48c..a044abe9b92 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/AnotherFakeAPI.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/AnotherFakeAPI.swift @@ -14,11 +14,11 @@ open class AnotherFakeAPI { /** To test special tags - - parameter body: (body) client model + - parameter client: (body) client model - parameter completion: completion handler to receive the data and the error objects */ - open class func call123testSpecialTags(body: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { - call123testSpecialTagsWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func call123testSpecialTags(client: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { + call123testSpecialTagsWithRequestBuilder(client: client).execute { (response, error) -> Void in completion(response?.body, error) } } @@ -27,13 +27,13 @@ open class AnotherFakeAPI { To test special tags - PATCH /another-fake/dummy - To test special tags and operation ID starting with number - - parameter body: (body) client model + - parameter client: (body) client model - returns: RequestBuilder */ - open class func call123testSpecialTagsWithRequestBuilder(body: Client) -> RequestBuilder { + open class func call123testSpecialTagsWithRequestBuilder(client: Client) -> RequestBuilder { let path = "/another-fake/dummy" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: client) let url = URLComponents(string: URLString) diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift index 2bfd5a21d7a..7ad414efc0e 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift @@ -77,11 +77,11 @@ open class FakeAPI { /** - - parameter body: (body) Input composite as post body (optional) + - parameter outerComposite: (body) Input composite as post body (optional) - parameter completion: completion handler to receive the data and the error objects */ - open class func fakeOuterCompositeSerialize(body: OuterComposite? = nil, completion: @escaping ((_ data: OuterComposite?,_ error: Error?) -> Void)) { - fakeOuterCompositeSerializeWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func fakeOuterCompositeSerialize(outerComposite: OuterComposite? = nil, completion: @escaping ((_ data: OuterComposite?,_ error: Error?) -> Void)) { + fakeOuterCompositeSerializeWithRequestBuilder(outerComposite: outerComposite).execute { (response, error) -> Void in completion(response?.body, error) } } @@ -89,13 +89,13 @@ open class FakeAPI { /** - POST /fake/outer/composite - Test serialization of object with outer number type - - parameter body: (body) Input composite as post body (optional) + - parameter outerComposite: (body) Input composite as post body (optional) - returns: RequestBuilder */ - open class func fakeOuterCompositeSerializeWithRequestBuilder(body: OuterComposite? = nil) -> RequestBuilder { + open class func fakeOuterCompositeSerializeWithRequestBuilder(outerComposite: OuterComposite? = nil) -> RequestBuilder { let path = "/fake/outer/composite" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: outerComposite) let url = URLComponents(string: URLString) @@ -164,11 +164,11 @@ open class FakeAPI { /** - - parameter body: (body) + - parameter fileSchemaTestClass: (body) - parameter completion: completion handler to receive the data and the error objects */ - open class func testBodyWithFileSchema(body: FileSchemaTestClass, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - testBodyWithFileSchemaWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func testBodyWithFileSchema(fileSchemaTestClass: FileSchemaTestClass, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + testBodyWithFileSchemaWithRequestBuilder(fileSchemaTestClass: fileSchemaTestClass).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -180,13 +180,13 @@ open class FakeAPI { /** - PUT /fake/body-with-file-schema - For this test, the body for this request much reference a schema named `File`. - - parameter body: (body) + - parameter fileSchemaTestClass: (body) - returns: RequestBuilder */ - open class func testBodyWithFileSchemaWithRequestBuilder(body: FileSchemaTestClass) -> RequestBuilder { + open class func testBodyWithFileSchemaWithRequestBuilder(fileSchemaTestClass: FileSchemaTestClass) -> RequestBuilder { let path = "/fake/body-with-file-schema" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: fileSchemaTestClass) let url = URLComponents(string: URLString) @@ -198,11 +198,11 @@ open class FakeAPI { /** - parameter query: (query) - - parameter body: (body) + - parameter user: (body) - parameter completion: completion handler to receive the data and the error objects */ - open class func testBodyWithQueryParams(query: String, body: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - testBodyWithQueryParamsWithRequestBuilder(query: query, body: body).execute { (response, error) -> Void in + open class func testBodyWithQueryParams(query: String, user: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + testBodyWithQueryParamsWithRequestBuilder(query: query, user: user).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -214,13 +214,13 @@ open class FakeAPI { /** - PUT /fake/body-with-query-params - parameter query: (query) - - parameter body: (body) + - parameter user: (body) - returns: RequestBuilder */ - open class func testBodyWithQueryParamsWithRequestBuilder(query: String, body: User) -> RequestBuilder { + open class func testBodyWithQueryParamsWithRequestBuilder(query: String, user: User) -> RequestBuilder { let path = "/fake/body-with-query-params" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: user) var url = URLComponents(string: URLString) url?.queryItems = APIHelper.mapValuesToQueryItems([ @@ -235,11 +235,11 @@ open class FakeAPI { /** To test \"client\" model - - parameter body: (body) client model + - parameter client: (body) client model - parameter completion: completion handler to receive the data and the error objects */ - open class func testClientModel(body: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { - testClientModelWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func testClientModel(client: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { + testClientModelWithRequestBuilder(client: client).execute { (response, error) -> Void in completion(response?.body, error) } } @@ -248,13 +248,13 @@ open class FakeAPI { To test \"client\" model - PATCH /fake - To test \"client\" model - - parameter body: (body) client model + - parameter client: (body) client model - returns: RequestBuilder */ - open class func testClientModelWithRequestBuilder(body: Client) -> RequestBuilder { + open class func testClientModelWithRequestBuilder(client: Client) -> RequestBuilder { let path = "/fake" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: client) let url = URLComponents(string: URLString) @@ -537,11 +537,11 @@ open class FakeAPI { /** test inline additionalProperties - - parameter param: (body) request body + - parameter requestBody: (body) request body - parameter completion: completion handler to receive the data and the error objects */ - open class func testInlineAdditionalProperties(param: [String:String], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - testInlineAdditionalPropertiesWithRequestBuilder(param: param).execute { (response, error) -> Void in + open class func testInlineAdditionalProperties(requestBody: [String:String], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + testInlineAdditionalPropertiesWithRequestBuilder(requestBody: requestBody).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -553,13 +553,13 @@ open class FakeAPI { /** test inline additionalProperties - POST /fake/inline-additionalProperties - - parameter param: (body) request body + - parameter requestBody: (body) request body - returns: RequestBuilder */ - open class func testInlineAdditionalPropertiesWithRequestBuilder(param: [String:String]) -> RequestBuilder { + open class func testInlineAdditionalPropertiesWithRequestBuilder(requestBody: [String:String]) -> RequestBuilder { let path = "/fake/inline-additionalProperties" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: param) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: requestBody) let url = URLComponents(string: URLString) diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeClassnameTags123API.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeClassnameTags123API.swift index ddfeae23edd..1b4e3e1bfe3 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeClassnameTags123API.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeClassnameTags123API.swift @@ -14,11 +14,11 @@ open class FakeClassnameTags123API { /** To test class name in snake case - - parameter body: (body) client model + - parameter client: (body) client model - parameter completion: completion handler to receive the data and the error objects */ - open class func testClassname(body: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { - testClassnameWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func testClassname(client: Client, completion: @escaping ((_ data: Client?,_ error: Error?) -> Void)) { + testClassnameWithRequestBuilder(client: client).execute { (response, error) -> Void in completion(response?.body, error) } } @@ -30,13 +30,13 @@ open class FakeClassnameTags123API { - API Key: - type: apiKey api_key_query (QUERY) - name: api_key_query - - parameter body: (body) client model + - parameter client: (body) client model - returns: RequestBuilder */ - open class func testClassnameWithRequestBuilder(body: Client) -> RequestBuilder { + open class func testClassnameWithRequestBuilder(client: Client) -> RequestBuilder { let path = "/fake_classname_test" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: client) let url = URLComponents(string: URLString) diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/PetAPI.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/PetAPI.swift index 0a5dfeb39b6..e73c6e59e6a 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/PetAPI.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/PetAPI.swift @@ -14,11 +14,11 @@ open class PetAPI { /** Add a new pet to the store - - parameter body: (body) Pet object that needs to be added to the store + - parameter pet: (body) Pet object that needs to be added to the store - parameter completion: completion handler to receive the data and the error objects */ - open class func addPet(body: Pet, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - addPetWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func addPet(pet: Pet, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + addPetWithRequestBuilder(pet: pet).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -33,13 +33,13 @@ open class PetAPI { - OAuth: - type: oauth2 - name: petstore_auth - - parameter body: (body) Pet object that needs to be added to the store + - parameter pet: (body) Pet object that needs to be added to the store - returns: RequestBuilder */ - open class func addPetWithRequestBuilder(body: Pet) -> RequestBuilder { + open class func addPetWithRequestBuilder(pet: Pet) -> RequestBuilder { let path = "/pet" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: pet) let url = URLComponents(string: URLString) @@ -217,11 +217,11 @@ open class PetAPI { /** Update an existing pet - - parameter body: (body) Pet object that needs to be added to the store + - parameter pet: (body) Pet object that needs to be added to the store - parameter completion: completion handler to receive the data and the error objects */ - open class func updatePet(body: Pet, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - updatePetWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func updatePet(pet: Pet, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + updatePetWithRequestBuilder(pet: pet).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -236,13 +236,13 @@ open class PetAPI { - OAuth: - type: oauth2 - name: petstore_auth - - parameter body: (body) Pet object that needs to be added to the store + - parameter pet: (body) Pet object that needs to be added to the store - returns: RequestBuilder */ - open class func updatePetWithRequestBuilder(body: Pet) -> RequestBuilder { + open class func updatePetWithRequestBuilder(pet: Pet) -> RequestBuilder { let path = "/pet" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: pet) let url = URLComponents(string: URLString) diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/StoreAPI.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/StoreAPI.swift index a2bced598f0..c1ef8f607c9 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/StoreAPI.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/StoreAPI.swift @@ -118,11 +118,11 @@ open class StoreAPI { /** Place an order for a pet - - parameter body: (body) order placed for purchasing the pet + - parameter order: (body) order placed for purchasing the pet - parameter completion: completion handler to receive the data and the error objects */ - open class func placeOrder(body: Order, completion: @escaping ((_ data: Order?,_ error: Error?) -> Void)) { - placeOrderWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func placeOrder(order: Order, completion: @escaping ((_ data: Order?,_ error: Error?) -> Void)) { + placeOrderWithRequestBuilder(order: order).execute { (response, error) -> Void in completion(response?.body, error) } } @@ -130,13 +130,13 @@ open class StoreAPI { /** Place an order for a pet - POST /store/order - - parameter body: (body) order placed for purchasing the pet + - parameter order: (body) order placed for purchasing the pet - returns: RequestBuilder */ - open class func placeOrderWithRequestBuilder(body: Order) -> RequestBuilder { + open class func placeOrderWithRequestBuilder(order: Order) -> RequestBuilder { let path = "/store/order" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: order) let url = URLComponents(string: URLString) diff --git a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/UserAPI.swift b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/UserAPI.swift index d8cafaf2c96..70997939533 100644 --- a/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/UserAPI.swift +++ b/samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/UserAPI.swift @@ -14,11 +14,11 @@ open class UserAPI { /** Create user - - parameter body: (body) Created user object + - parameter user: (body) Created user object - parameter completion: completion handler to receive the data and the error objects */ - open class func createUser(body: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - createUserWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func createUser(user: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + createUserWithRequestBuilder(user: user).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -31,13 +31,13 @@ open class UserAPI { Create user - POST /user - This can only be done by the logged in user. - - parameter body: (body) Created user object + - parameter user: (body) Created user object - returns: RequestBuilder */ - open class func createUserWithRequestBuilder(body: User) -> RequestBuilder { + open class func createUserWithRequestBuilder(user: User) -> RequestBuilder { let path = "/user" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: user) let url = URLComponents(string: URLString) @@ -49,11 +49,11 @@ open class UserAPI { /** Creates list of users with given input array - - parameter body: (body) List of user object + - parameter user: (body) List of user object - parameter completion: completion handler to receive the data and the error objects */ - open class func createUsersWithArrayInput(body: [User], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - createUsersWithArrayInputWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func createUsersWithArrayInput(user: [User], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + createUsersWithArrayInputWithRequestBuilder(user: user).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -65,13 +65,13 @@ open class UserAPI { /** Creates list of users with given input array - POST /user/createWithArray - - parameter body: (body) List of user object + - parameter user: (body) List of user object - returns: RequestBuilder */ - open class func createUsersWithArrayInputWithRequestBuilder(body: [User]) -> RequestBuilder { + open class func createUsersWithArrayInputWithRequestBuilder(user: [User]) -> RequestBuilder { let path = "/user/createWithArray" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: user) let url = URLComponents(string: URLString) @@ -83,11 +83,11 @@ open class UserAPI { /** Creates list of users with given input array - - parameter body: (body) List of user object + - parameter user: (body) List of user object - parameter completion: completion handler to receive the data and the error objects */ - open class func createUsersWithListInput(body: [User], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - createUsersWithListInputWithRequestBuilder(body: body).execute { (response, error) -> Void in + open class func createUsersWithListInput(user: [User], completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + createUsersWithListInputWithRequestBuilder(user: user).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -99,13 +99,13 @@ open class UserAPI { /** Creates list of users with given input array - POST /user/createWithList - - parameter body: (body) List of user object + - parameter user: (body) List of user object - returns: RequestBuilder */ - open class func createUsersWithListInputWithRequestBuilder(body: [User]) -> RequestBuilder { + open class func createUsersWithListInputWithRequestBuilder(user: [User]) -> RequestBuilder { let path = "/user/createWithList" let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: user) let url = URLComponents(string: URLString) @@ -258,11 +258,11 @@ open class UserAPI { Updated user - parameter username: (path) name that need to be deleted - - parameter body: (body) Updated user object + - parameter user: (body) Updated user object - parameter completion: completion handler to receive the data and the error objects */ - open class func updateUser(username: String, body: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { - updateUserWithRequestBuilder(username: username, body: body).execute { (response, error) -> Void in + open class func updateUser(username: String, user: User, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) { + updateUserWithRequestBuilder(username: username, user: user).execute { (response, error) -> Void in if error == nil { completion((), error) } else { @@ -276,16 +276,16 @@ open class UserAPI { - PUT /user/{username} - This can only be done by the logged in user. - parameter username: (path) name that need to be deleted - - parameter body: (body) Updated user object + - parameter user: (body) Updated user object - returns: RequestBuilder */ - open class func updateUserWithRequestBuilder(username: String, body: User) -> RequestBuilder { + open class func updateUserWithRequestBuilder(username: String, user: User) -> RequestBuilder { var path = "/user/{username}" let usernamePreEscape = "\(APIHelper.mapValueToPathItem(username))" let usernamePostEscape = usernamePreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" path = path.replacingOccurrences(of: "{username}", with: usernamePostEscape, options: .literal, range: nil) let URLString = PetstoreClientAPI.basePath + path - let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: body) + let parameters = JSONEncodingHelper.encodingParameters(forEncodableObject: user) let url = URLComponents(string: URLString) diff --git a/samples/client/test/swift4/default/TestClient/Classes/OpenAPIs/APIHelper.swift b/samples/client/test/swift4/default/TestClient/Classes/OpenAPIs/APIHelper.swift index 3c7b53f8149..d94614b34fc 100644 --- a/samples/client/test/swift4/default/TestClient/Classes/OpenAPIs/APIHelper.swift +++ b/samples/client/test/swift4/default/TestClient/Classes/OpenAPIs/APIHelper.swift @@ -45,6 +45,12 @@ public struct APIHelper { }) } + public static func mapValueToPathItem(_ source: Any) -> Any { + if let collection = source as? Array { + return collection.filter({ $0 != nil }).map({"\($0!)"}).joined(separator: ",") + } + return source + } public static func mapValuesToQueryItems(_ source: [String:Any?]) -> [URLQueryItem]? { let destination = source.filter({ $0.value != nil}).reduce(into: [URLQueryItem]()) { (result, item) in diff --git a/website/i18n/en.json b/website/i18n/en.json index a42620c4a4d..06e46d33d92 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -370,6 +370,9 @@ "generators/swift4": { "title": "generators/swift4" }, + "generators/swift5": { + "title": "generators/swift5" + }, "generators/typescript-angular": { "title": "generators/typescript-angular" },