diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fc2667 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# IntelliJ +.idea/ +out/ + +# Editor-based Rest Client +.idea/httpRequests +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a91300a --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# OpenAPI Generator for the minderaswift4generator library + +## Overview +This is a boiler-plate project to generate your own project derived from an OpenAPI specification. +Its goal is to get you started with the basic plumbing so you can put in your own logic. +It won't work without your changes applied. + +## What's OpenAPI +The goal of OpenAPI is to define a standard, language-agnostic interface to REST APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. +When properly described with OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. +Similar to what interfaces have done for lower-level programming, OpenAPI removes the guesswork in calling the service. + +Check out [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) for additional information about the OpenAPI project, including additional libraries with support for other languages and more. + +## How do I use this? +At this point, you've likely generated a client setup. It will include something along these lines: + +``` +. +|- README.md // this file +|- pom.xml // build script +|-- src +|--- main +|---- java +|----- com/github/Mindera.MinderaSwift4GeneratorGenerator.java // generator file +|---- resources +|----- minderaswift4generator // template files +|----- META-INF +|------ services +|------- org.openapitools.codegen.CodegenConfig +``` + +You _will_ need to make changes in at least the following: + +`MinderaSwift4GeneratorGenerator.java` + +Templates in this folder: + +`src/main/resources/minderaswift4generator` + +Once modified, you can run this: + +``` +mvn package +``` + +In your generator project. A single jar file will be produced in `target`. You can now use that with [OpenAPI Generator](https://openapi-generator.tech): + +For mac/linux: +``` +java -cp /path/to/openapi-generator-cli.jar:/path/to/your.jar org.openapitools.codegen.OpenAPIGenerator generate -g minderaswift4generator -i /path/to/openapi.yaml -o ./test +``` +(Do not forget to replace the values `/path/to/openapi-generator-cli.jar`, `/path/to/your.jar` and `/path/to/openapi.yaml` in the previous command) + +For Windows users, you will need to use `;` instead of `:` in the classpath, e.g. +``` +java -cp /path/to/openapi-generator-cli.jar;/path/to/your.jar org.openapitools.codegen.OpenAPIGenerator generate -g minderaswift4generator -i /path/to/openapi.yaml -o ./test +``` + +Now your templates are available to the client generator and you can write output values + +## But how do I modify this? +The `MinderaSwift4GeneratorGenerator.java` has comments in it--lots of comments. There is no good substitute +for reading the code more, though. See how the `MinderaSwift4GeneratorGenerator` implements `CodegenConfig`. +That class has the signature of all values that can be overridden. + +For the templates themselves, you have a number of values available to you for generation. +You can execute the `java` command from above while passing different debug flags to show +the object you have available during client generation: + +``` +# The following additional debug options are available for all codegen targets: +# -DdebugOpenAPI prints the OpenAPI Specification as interpreted by the codegen +# -DdebugModels prints models passed to the template engine +# -DdebugOperations prints operations passed to the template engine +# -DdebugSupportingFiles prints additional data passed to the template engine + +java -DdebugOperations -cp /path/to/openapi-generator-cli.jar:/path/to/your.jar org.openapitools.codegen.OpenAPIGenerator generate -g minderaswift4generator -i /path/to/openapi.yaml -o ./test +``` + +Will, for example, output the debug info for operations. +You can use this info in the `api.mustache` file. \ No newline at end of file diff --git a/mindera-openapi-generator.iml b/mindera-openapi-generator.iml new file mode 100644 index 0000000..ff1b2a8 --- /dev/null +++ b/mindera-openapi-generator.iml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ef3816d --- /dev/null +++ b/pom.xml @@ -0,0 +1,144 @@ + + 4.0.0 + com.github.mindera + mindera-openapi-generator + jar + mindera-openapi-generator + 1.0.0 + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M1 + + + enforce-maven + + enforce + + + + + 2.2.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add_sources + generate-sources + + add-source + + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + + + + + + + org.openapitools + openapi-generator + ${openapi-generator-version} + provided + + + org.openapitools + openapi-generator + ${openapi-generator-version} + tests + test + + + org.testng + testng + ${testng-version} + test + + + org.jmockit + jmockit + ${jmockit-version} + test + + + + 1.43 + 6.9.6 + UTF-8 + 3.3.4 + 1.0.0 + 4.8.1 + + diff --git a/src/main/java/com/mindera/openapi/generator/Swift4Generator.java b/src/main/java/com/mindera/openapi/generator/Swift4Generator.java new file mode 100644 index 0000000..65d9c2f --- /dev/null +++ b/src/main/java/com/mindera/openapi/generator/Swift4Generator.java @@ -0,0 +1,870 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * 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 com.mindera.openapi.generator; + +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; + + +public class Swift4Generator extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(Swift4Generator.class); + + public static final String PROJECT_NAME = "projectName"; + public static final String RESPONSE_AS = "responseAs"; + public static final String UNWRAP_REQUIRED = "unwrapRequired"; + public static final String OBJC_COMPATIBLE = "objcCompatible"; + public static final String POD_SOURCE = "podSource"; + public static final String POD_AUTHORS = "podAuthors"; + public static final String POD_SOCIAL_MEDIA_URL = "podSocialMediaURL"; + 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 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 unwrapRequired; + protected boolean objcCompatible = false; + protected boolean lenientTypeCast = false; + protected boolean swiftUseApiNamespace; + protected String[] responseAs = new String[0]; + protected String sourceFolder = "Classes" + File.separator + "OpenAPIs"; + + /** + * Constructor for the swift4 language codegen module. + */ + public Swift4Generator() { + super(); + outputFolder = "generated-code" + File.separator + "swift"; + modelTemplateFiles.put("model.mustache", ".swift"); + apiTemplateFiles.put("api.mustache", ".swift"); + embeddedTemplateDir = templateDir = "MinderaSwift4"; + apiPackage = File.separator + "APIs"; + modelPackage = File.separator + "Models"; + + languageSpecificPrimitives = new HashSet<>( + Arrays.asList( + "Int", + "Int32", + "Int64", + "Float", + "Double", + "Bool", + "Void", + "String", + "Character", + "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", + + // Added for Objective-C compatibility + "NSArray", "NSURL", "CGFloat", "NSSet", "NSString", "NSInteger", "NSUInteger", + "NSError", "NSDictionary", + + // + // 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", "DateWithoutTime"); + typeMapping.put("Date", "DateWithoutTime"); + 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"); + + 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(UNWRAP_REQUIRED, + "Treat 'required' properties in response as non-optional " + + "(which would crash the app if api returns null as opposed " + + "to required option specified in json schema")); + cliOptions.add(new CliOption(OBJC_COMPATIBLE, + "Add additional properties and methods for Objective-C " + + "compatibility (default: false)")); + cliOptions.add(new CliOption(POD_SOURCE, "Source information used for Podspec")); + cliOptions.add(new CliOption(CodegenConstants.POD_VERSION, "Version used for Podspec")); + cliOptions.add(new CliOption(POD_AUTHORS, "Authors used for Podspec")); + 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 "mindera-swift4"; + } + + @Override + public String getHelp() { + return "Generates a Swift 4.x client library for Minders."; + } + + @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)"); + } + + // 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 objcCompatible option, which adds additional properties + // and methods for Objective-C compatibility + if (additionalProperties.containsKey(OBJC_COMPATIBLE)) { + setObjcCompatible(convertPropertyToBooleanAndWriteBack(OBJC_COMPATIBLE)); + } + additionalProperties.put(OBJC_COMPATIBLE, objcCompatible); + + // Setup unwrapRequired option, which makes all the properties with "required" non-optional + if (additionalProperties.containsKey(RESPONSE_AS)) { + Object responseAsObject = additionalProperties.get(RESPONSE_AS); + if (responseAsObject instanceof String) { + setResponseAs(((String) responseAsObject).split(",")); + } else { + setResponseAs((String[]) responseAsObject); + } + } + additionalProperties.put(RESPONSE_AS, responseAs); + 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)); + + supportingFiles.add(new SupportingFile("Podspec.mustache", + "", + projectName + ".podspec")); + 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("Configuration.mustache", + sourceFolder, + "Configuration.swift")); + supportingFiles.add(new SupportingFile("Extensions.mustache", + sourceFolder, + "Extensions.swift")); + supportingFiles.add(new SupportingFile("Models.mustache", + sourceFolder, + "Models.swift")); + supportingFiles.add(new SupportingFile("APIs.mustache", + sourceFolder, + "APIs.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("git_push.sh.mustache", + "", + "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", + "", + ".gitignore")); + + } + + @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 getSchemaType(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 = org.openapitools.codegen.utils.StringUtils.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 initialCaps(name) + "API"; + } + + @Override + public String toOperationId(String operationId) { + operationId = org.openapitools.codegen.utils.StringUtils.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 = org.openapitools.codegen.utils.StringUtils.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 " + org.openapitools.codegen.utils.StringUtils.camelize(sanitizeName("call_" + operationId), true)); + operationId = org.openapitools.codegen.utils.StringUtils.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 = org.openapitools.codegen.utils.StringUtils.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; + } + + // org.openapitools.codegen.utils.StringUtils.camelize(lower) the variable name + // pet_id => petId + name = org.openapitools.codegen.utils.StringUtils.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) { + CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); + 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, + allDefinitions); + codegenModel = Swift4Generator.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 setObjcCompatible(boolean objcCompatible) { + this.objcCompatible = objcCompatible; + } + + 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 + org.openapitools.codegen.utils.StringUtils.camelize(nameWithoutStartingNumbers, true); + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return org.openapitools.codegen.utils.StringUtils.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 = org.openapitools.codegen.utils.StringUtils.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" + org.openapitools.codegen.utils.StringUtils.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 org.openapitools.codegen.utils.StringUtils.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()); + } + } + } +} diff --git a/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig new file mode 100644 index 0000000..5bafaee --- /dev/null +++ b/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -0,0 +1 @@ +com.mindera.openapi.generator.Swift4Generator \ No newline at end of file diff --git a/src/main/resources/MinderaSwift4/APIHelper.mustache b/src/main/resources/MinderaSwift4/APIHelper.mustache new file mode 100644 index 0000000..3c7b53f --- /dev/null +++ b/src/main/resources/MinderaSwift4/APIHelper.mustache @@ -0,0 +1,65 @@ +// 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 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/src/main/resources/MinderaSwift4/APIs.mustache b/src/main/resources/MinderaSwift4/APIs.mustache new file mode 100644 index 0000000..fb2a779 --- /dev/null +++ b/src/main/resources/MinderaSwift4/APIs.mustache @@ -0,0 +1,61 @@ +// APIs.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +open class {{projectName}}API { + open static var basePath = "{{{basePath}}}" + open static var credential: URLCredential? + open static var customHeaders: [String:String] = [:] + open 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/src/main/resources/MinderaSwift4/AlamofireImplementations.mustache b/src/main/resources/MinderaSwift4/AlamofireImplementations.mustache new file mode 100644 index 0000000..03b353e --- /dev/null +++ b/src/main/resources/MinderaSwift4/AlamofireImplementations.mustache @@ -0,0 +1,442 @@ +// 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 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.removeValue(forKey: managerId) + } + + 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 as 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 as 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/src/main/resources/MinderaSwift4/Cartfile.mustache b/src/main/resources/MinderaSwift4/Cartfile.mustache new file mode 100644 index 0000000..b8548fc --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/CodableHelper.mustache b/src/main/resources/MinderaSwift4/CodableHelper.mustache new file mode 100644 index 0000000..b4e2538 --- /dev/null +++ b/src/main/resources/MinderaSwift4/CodableHelper.mustache @@ -0,0 +1,51 @@ +// +// CodableHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +public typealias EncodeResult = (data: Data?, error: Error?) + +open class CodableHelper { + + 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() + decoder.dataDecodingStrategy = .base64 + decoder.dateDecodingStrategy = .formatted(Configuration.iso8601DateTimeFormatter) + + 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 + } + encoder.dataEncodingStrategy = .base64 + encoder.dateEncodingStrategy = .formatted(Configuration.iso8601DateTimeFormatter) + + do { + returnedData = try encoder.encode(value) + } catch { + returnedError = error + } + + return (returnedData, returnedError) + } + +} diff --git a/src/main/resources/MinderaSwift4/Configuration.mustache b/src/main/resources/MinderaSwift4/Configuration.mustache new file mode 100644 index 0000000..efeea5d --- /dev/null +++ b/src/main/resources/MinderaSwift4/Configuration.mustache @@ -0,0 +1,33 @@ +// Configuration.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +open class Configuration { + + // These values are used to configure date formatters that are used to serialize dates into JSON format. + // You must set it prior to encoding any dates, and it will only be read once. + open static var dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + open static var dateFormat = "yyyy-MM-dd" + + static var iso8601DateTimeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = Configuration.dateTimeFormat + formatter.calendar = Calendar(identifier: .iso8601) + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + }() + + static var iso8601DateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = Configuration.dateFormat + formatter.calendar = Calendar(identifier: .iso8601) + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + }() +} \ No newline at end of file diff --git a/src/main/resources/MinderaSwift4/Extensions.mustache b/src/main/resources/MinderaSwift4/Extensions.mustache new file mode 100644 index 0000000..46f016f --- /dev/null +++ b/src/main/resources/MinderaSwift4/Extensions.mustache @@ -0,0 +1,193 @@ +// 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()) + } +} + +extension DateWithoutTime: JSONEncodable { + func encodeToJSON() -> Any { + return date.formatAsDate() as Any + } +} + +extension Date: JSONEncodable { + func encodeToJSON() -> Any { + return formatAsDate() as Any + } + + func formatAsDateTime() -> String { + return Configuration.iso8601DateTimeFormatter.string(from: self) + } + + func formatAsDate() -> String { + return Configuration.iso8601DateFormatter.string(from: self) + } +} + +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/src/main/resources/MinderaSwift4/JSONEncodableEncoding.mustache b/src/main/resources/MinderaSwift4/JSONEncodableEncoding.mustache new file mode 100644 index 0000000..ca05906 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/JSONEncodingHelper.mustache b/src/main/resources/MinderaSwift4/JSONEncodingHelper.mustache new file mode 100644 index 0000000..7044951 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/Models.mustache b/src/main/resources/MinderaSwift4/Models.mustache new file mode 100644 index 0000000..f36ae35 --- /dev/null +++ b/src/main/resources/MinderaSwift4/Models.mustache @@ -0,0 +1,59 @@ +// Models.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +public struct DateWithoutTime: Codable { + public var value: Date + + public init(date: Date) { + self.value = date + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + let raw = try values.decode(String.self, forKey: .date) + value = Configuration.iso8601DateFormatter.date(from: raw)! + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value.formatAsDate(), forKey: .date) + } + + public enum CodingKeys: String, CodingKey { + case date + } +} + +protocol JSONEncodable { + func encodeToJSON() -> Any +} + +public enum ErrorResponse : Error { + case error(Int, Data?, Error) +} + +open class Response { + open let statusCode: Int + open let header: [String: String] + open 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/src/main/resources/MinderaSwift4/Podspec.mustache b/src/main/resources/MinderaSwift4/Podspec.mustache new file mode 100644 index 0000000..24ffd62 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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}}0.0.1{{/podVersion}}' + s.source = {{#podSource}}{{& podSource}}{{/podSource}}{{^podSource}}{ :git => 'git@github.com:OpenAPITools/openapi-generator.git', :tag => 'v1.0.0' }{{/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' + {{#usePromiseKit}} + s.dependency 'PromiseKit/CorePromise', '~> 4.4.0' + {{/usePromiseKit}} + {{#useRxSwift}} + s.dependency 'RxSwift', '~> 4.0' + {{/useRxSwift}} + s.dependency 'Alamofire', '~> 4.5.0' +end diff --git a/src/main/resources/MinderaSwift4/README.mustache b/src/main/resources/MinderaSwift4/README.mustache new file mode 100644 index 0000000..99f1e0b --- /dev/null +++ b/src/main/resources/MinderaSwift4/README.mustache @@ -0,0 +1,65 @@ +# Swift4 API client for {{packageName}} + +{{#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 +Put the package under your project folder and add the following in import: +``` + "./{{packageName}}" +``` + +## 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/src/main/resources/MinderaSwift4/_param.mustache b/src/main/resources/MinderaSwift4/_param.mustache new file mode 100644 index 0000000..5caacbc --- /dev/null +++ b/src/main/resources/MinderaSwift4/_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/src/main/resources/MinderaSwift4/api.mustache b/src/main/resources/MinderaSwift4/api.mustache new file mode 100644 index 0000000..ed4afe1 --- /dev/null +++ b/src/main/resources/MinderaSwift4/api.mustache @@ -0,0 +1,171 @@ +{{#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}} + /** + {{#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}} + } + } + +{{#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}}({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) { data, error in + if let error = error { + deferred.reject(error) + } else { + deferred.fulfill(data!) + } + } + 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}}({{#allParams}}{{paramName}}: {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) { data, error in + if let error = error { + observer.on(.error(error)) + } else { + observer.on(.next(data!)) + } + observer.on(.completed) + } + 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 = "\({{paramName}}{{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/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}} + {{> _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/src/main/resources/MinderaSwift4/git_push.sh.mustache b/src/main/resources/MinderaSwift4/git_push.sh.mustache new file mode 100755 index 0000000..8a32e53 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/gitignore.mustache b/src/main/resources/MinderaSwift4/gitignore.mustache new file mode 100644 index 0000000..5e5d5ce --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/model.mustache b/src/main/resources/MinderaSwift4/model.mustache new file mode 100644 index 0000000..806cc96 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/modelArray.mustache b/src/main/resources/MinderaSwift4/modelArray.mustache new file mode 100644 index 0000000..8436261 --- /dev/null +++ b/src/main/resources/MinderaSwift4/modelArray.mustache @@ -0,0 +1 @@ +public typealias {{classname}} = {{parent}} \ No newline at end of file diff --git a/src/main/resources/MinderaSwift4/modelEnum.mustache b/src/main/resources/MinderaSwift4/modelEnum.mustache new file mode 100644 index 0000000..91c2efd --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/modelInlineEnumDeclaration.mustache b/src/main/resources/MinderaSwift4/modelInlineEnumDeclaration.mustache new file mode 100644 index 0000000..e2a3d08 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/main/resources/MinderaSwift4/modelObject.mustache b/src/main/resources/MinderaSwift4/modelObject.mustache new file mode 100644 index 0000000..d477ac7 --- /dev/null +++ b/src/main/resources/MinderaSwift4/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/src/test/java/com/mindera/openapi/generator/swift4/Swift4GeneratorTest.java b/src/test/java/com/mindera/openapi/generator/swift4/Swift4GeneratorTest.java new file mode 100644 index 0000000..6bfed15 --- /dev/null +++ b/src/test/java/com/mindera/openapi/generator/swift4/Swift4GeneratorTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * 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 com.mindera.openapi.generator.swift4; + +import com.mindera.openapi.generator.Swift4Generator; +import io.swagger.parser.OpenAPIParser; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.parser.core.models.ParseOptions; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.DefaultCodegen; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class Swift4GeneratorTest { + + Swift4Generator swiftCodegen = new Swift4Generator(); + + @Test(enabled = true) + public void testCapitalizedReservedWord() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("AS", null), "_as"); + } + + @Test(enabled = true) + public void testReservedWord() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("Public", null), "_public"); + } + + @Test(enabled = true) + public void shouldNotBreakNonReservedWord() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("Error", null), "error"); + } + + @Test(enabled = true) + public void shouldNotBreakCorrectName() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("EntryName", null), "entryName"); + } + + @Test(enabled = true) + public void testSingleWordAllCaps() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("VALUE", null), "value"); + } + + @Test(enabled = true) + public void testSingleWordLowercase() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("value", null), "value"); + } + + @Test(enabled = true) + public void testCapitalsWithUnderscore() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY_NAME", null), "entryName"); + } + + @Test(enabled = true) + public void testCapitalsWithDash() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY-NAME", null), "entryName"); + } + + @Test(enabled = true) + public void testCapitalsWithSpace() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("ENTRY NAME", null), "entryName"); + } + + @Test(enabled = true) + public void testLowercaseWithUnderscore() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("entry_name", null), "entryName"); + } + + @Test(enabled = true) + public void testStartingWithNumber() throws Exception { + Assert.assertEquals(swiftCodegen.toEnumVarName("123EntryName", null), "_123entryName"); + Assert.assertEquals(swiftCodegen.toEnumVarName("123Entry_name", null), "_123entryName"); + Assert.assertEquals(swiftCodegen.toEnumVarName("123EntryName123", null), "_123entryName123"); + } + + @Test(description = "returns Data when response format is binary", enabled = true) + public void binaryDataTest() { + // TODO update json file + + final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/binaryDataTest.json", null, new ParseOptions()).getOpenAPI(); + final DefaultCodegen codegen = new Swift4Generator(); + final String path = "/tests/binaryResponse"; + final Operation p = openAPI.getPaths().get(path).getPost(); + final CodegenOperation op = codegen.fromOperation(path, "post", p, openAPI.getComponents().getSchemas()); + + Assert.assertEquals(op.returnType, "URL"); + Assert.assertEquals(op.bodyParam.dataType, "URL"); + Assert.assertTrue(op.bodyParam.isBinary); + Assert.assertTrue(op.responses.get(0).isBinary); + } + + @Test(description = "returns Date when response format is date", enabled = true) + public void dateTest() { + final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/datePropertyTest.json", null, new ParseOptions()).getOpenAPI(); + final DefaultCodegen codegen = new Swift4Generator(); + final String path = "/tests/dateResponse"; + final Operation p = openAPI.getPaths().get(path).getPost(); + final CodegenOperation op = codegen.fromOperation(path, "post", p, openAPI.getComponents().getSchemas()); + + Assert.assertEquals(op.returnType, "DateWithoutTime"); + Assert.assertEquals(op.bodyParam.dataType, "DateWithoutTime"); + } + + @Test(enabled = true) + public void testDefaultPodAuthors() throws Exception { + // Given + + // When + swiftCodegen.processOpts(); + + // Then + final String podAuthors = (String) swiftCodegen.additionalProperties().get(Swift4Generator.POD_AUTHORS); + Assert.assertEquals(podAuthors, Swift4Generator.DEFAULT_POD_AUTHORS); + } + + @Test(enabled = true) + public void testPodAuthors() throws Exception { + // Given + final String openAPIDevs = "OpenAPI Devs"; + swiftCodegen.additionalProperties().put(Swift4Generator.POD_AUTHORS, openAPIDevs); + + // When + swiftCodegen.processOpts(); + + // Then + final String podAuthors = (String) swiftCodegen.additionalProperties().get(Swift4Generator.POD_AUTHORS); + Assert.assertEquals(podAuthors, openAPIDevs); + } + +} diff --git a/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelEnumTest.java b/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelEnumTest.java new file mode 100644 index 0000000..d096994 --- /dev/null +++ b/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelEnumTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * 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 com.mindera.openapi.generator.swift4; + +import com.mindera.openapi.generator.Swift4Generator; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.DefaultCodegen; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; + +@SuppressWarnings("static-method") +public class Swift4ModelEnumTest { + @Test(description = "convert a java model with an enum and a default value") + public void converterTest() { + final StringSchema enumSchema = new StringSchema(); + enumSchema.setEnum(Arrays.asList("VALUE1", "VALUE2", "VALUE3")); + enumSchema.setDefault("VALUE2"); + final Schema model = new Schema().type("object").addProperties("name", enumSchema); + + final DefaultCodegen codegen = new Swift4Generator(); + final CodegenModel cm = codegen.fromModel("sample", model, Collections.singletonMap("sample", model)); + + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty enumVar = cm.vars.get(0); + Assert.assertEquals(enumVar.baseName, "name"); + Assert.assertEquals(enumVar.dataType, "String"); + Assert.assertEquals(enumVar.datatypeWithEnum, "Name"); + Assert.assertEquals(enumVar.name, "name"); + Assert.assertEquals(enumVar.defaultValue, ".VALUE2"); + Assert.assertEquals(enumVar.baseType, "String"); + Assert.assertTrue(enumVar.isEnum); + } +} diff --git a/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelTest.java b/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelTest.java new file mode 100644 index 0000000..c18b537 --- /dev/null +++ b/src/test/java/com/mindera/openapi/generator/swift4/Swift4ModelTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * 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 com.mindera.openapi.generator.swift4; + +import com.mindera.openapi.generator.Swift4Generator; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.DefaultCodegen; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.Collections; + +@SuppressWarnings("static-method") +public class Swift4ModelTest { + + @Test(description = "convert a simple java model", enabled = true) + public void simpleModelTest() { + final Schema schema = new Schema() + .description("a sample model") + .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("name", new StringSchema()) + .addProperties("createdAt", new DateTimeSchema()) + .addProperties("binary", new BinarySchema()) + .addProperties("byte", new ByteArraySchema()) + .addProperties("uuid", new UUIDSchema()) + .addProperties("dateOfBirth", new DateSchema()) + .addRequiredItem("id") + .addRequiredItem("name") + .discriminator(new Discriminator().propertyName("test")); + final DefaultCodegen codegen = new Swift4Generator(); + final CodegenModel cm = codegen.fromModel("sample", schema, Collections.singletonMap("sample", schema)); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 7); + Assert.assertEquals(cm.getDiscriminatorName(),"test"); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.dataType, "Int64"); + Assert.assertEquals(property1.name, "id"); + Assert.assertNull(property1.defaultValue); + Assert.assertEquals(property1.baseType, "Int64"); + Assert.assertTrue(property1.hasMore); + Assert.assertTrue(property1.required); + Assert.assertTrue(property1.isPrimitiveType); + Assert.assertTrue(property1.isNotContainer); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.dataType, "String"); + Assert.assertEquals(property2.name, "name"); + Assert.assertNull(property2.defaultValue); + Assert.assertEquals(property2.baseType, "String"); + Assert.assertTrue(property2.hasMore); + Assert.assertTrue(property2.required); + Assert.assertTrue(property2.isPrimitiveType); + Assert.assertTrue(property2.isNotContainer); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.dataType, "Date"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertNull(property3.defaultValue); + Assert.assertEquals(property3.baseType, "Date"); + Assert.assertTrue(property3.hasMore); + Assert.assertFalse(property3.required); + Assert.assertTrue(property3.isNotContainer); + + final CodegenProperty property4 = cm.vars.get(3); + Assert.assertEquals(property4.baseName, "binary"); + Assert.assertEquals(property4.dataType, "URL"); + Assert.assertEquals(property4.name, "binary"); + Assert.assertNull(property4.defaultValue); + Assert.assertEquals(property4.baseType, "URL"); + Assert.assertTrue(property4.hasMore); + Assert.assertFalse(property4.required); + Assert.assertTrue(property4.isNotContainer); + + final CodegenProperty property5 = cm.vars.get(4); + Assert.assertEquals(property5.baseName, "byte"); + Assert.assertEquals(property5.dataType, "Data"); + Assert.assertEquals(property5.name, "byte"); + Assert.assertNull(property5.defaultValue); + Assert.assertEquals(property5.baseType, "Data"); + Assert.assertTrue(property5.hasMore); + Assert.assertFalse(property5.required); + Assert.assertTrue(property5.isNotContainer); + + final CodegenProperty property6 = cm.vars.get(5); + Assert.assertEquals(property6.baseName, "uuid"); + Assert.assertEquals(property6.dataType, "UUID"); + Assert.assertEquals(property6.name, "uuid"); + Assert.assertNull(property6.defaultValue); + Assert.assertEquals(property6.baseType, "UUID"); + Assert.assertTrue(property6.hasMore); + Assert.assertFalse(property6.required); + Assert.assertTrue(property6.isNotContainer); + + final CodegenProperty property7 = cm.vars.get(6); + Assert.assertEquals(property7.baseName, "dateOfBirth"); + Assert.assertEquals(property7.dataType, "DateWithoutTime"); + Assert.assertEquals(property7.name, "dateOfBirth"); + Assert.assertNull(property7.defaultValue); + Assert.assertEquals(property7.baseType, "DateWithoutTime"); + Assert.assertFalse(property7.hasMore); + Assert.assertFalse(property7.required); + Assert.assertTrue(property7.isNotContainer); + } + +} diff --git a/src/test/java/com/mindera/openapi/generator/swift4/options/MinderaSwift4OptionsProvider.java b/src/test/java/com/mindera/openapi/generator/swift4/options/MinderaSwift4OptionsProvider.java new file mode 100644 index 0000000..004e5d6 --- /dev/null +++ b/src/test/java/com/mindera/openapi/generator/swift4/options/MinderaSwift4OptionsProvider.java @@ -0,0 +1,69 @@ +package com.mindera.openapi.generator.swift4.options; + +import com.google.common.collect.ImmutableMap; +import com.mindera.openapi.generator.Swift4Generator; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.options.OptionsProvider; + +import java.util.Map; + +public class MinderaSwift4OptionsProvider implements OptionsProvider { + public static final String SORT_PARAMS_VALUE = "false"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String PROJECT_NAME_VALUE = "Swagger"; + public static final String RESPONSE_AS_VALUE = "test"; + public static final String UNWRAP_REQUIRED_VALUE = "true"; + public static final String OBJC_COMPATIBLE_VALUE = "false"; + public static final String LENIENT_TYPE_CAST_VALUE = "false"; + public static final String POD_SOURCE_VALUE = "{ :git => 'git@github.com:swagger-api/swagger-mustache.git'," + + " :tag => 'v1.0.0-SNAPSHOT' }"; + public static final String POD_VERSION_VALUE = "v1.0.0-SNAPSHOT"; + public static final String POD_AUTHORS_VALUE = "podAuthors"; + public static final String POD_SOCIAL_MEDIA_URL_VALUE = "podSocialMediaURL"; + public static final String POD_DOCSET_URL_VALUE = "podDocsetURL"; + public static final String POD_LICENSE_VALUE = "'Apache License, Version 2.0'"; + public static final String POD_HOMEPAGE_VALUE = "podHomepage"; + public static final String POD_SUMMARY_VALUE = "podSummary"; + public static final String POD_DESCRIPTION_VALUE = "podDescription"; + public static final String POD_SCREENSHOTS_VALUE = "podScreenshots"; + public static final String POD_DOCUMENTATION_URL_VALUE = "podDocumentationURL"; + public static final String SWIFT_USE_API_NAMESPACE_VALUE = "swiftUseApiNamespace"; + public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false"; + + @Override + public String getLanguage() { + return "swift4"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE) + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE) + .put(Swift4Generator.PROJECT_NAME, PROJECT_NAME_VALUE) + .put(Swift4Generator.RESPONSE_AS, RESPONSE_AS_VALUE) + .put(Swift4Generator.UNWRAP_REQUIRED, UNWRAP_REQUIRED_VALUE) + .put(Swift4Generator.OBJC_COMPATIBLE, OBJC_COMPATIBLE_VALUE) + .put(Swift4Generator.LENIENT_TYPE_CAST, LENIENT_TYPE_CAST_VALUE) + .put(Swift4Generator.POD_SOURCE, POD_SOURCE_VALUE) + .put(CodegenConstants.POD_VERSION, POD_VERSION_VALUE) + .put(Swift4Generator.POD_AUTHORS, POD_AUTHORS_VALUE) + .put(Swift4Generator.POD_SOCIAL_MEDIA_URL, POD_SOCIAL_MEDIA_URL_VALUE) + .put(Swift4Generator.POD_DOCSET_URL, POD_DOCSET_URL_VALUE) + .put(Swift4Generator.POD_LICENSE, POD_LICENSE_VALUE) + .put(Swift4Generator.POD_HOMEPAGE, POD_HOMEPAGE_VALUE) + .put(Swift4Generator.POD_SUMMARY, POD_SUMMARY_VALUE) + .put(Swift4Generator.POD_DESCRIPTION, POD_DESCRIPTION_VALUE) + .put(Swift4Generator.POD_SCREENSHOTS, POD_SCREENSHOTS_VALUE) + .put(Swift4Generator.POD_DOCUMENTATION_URL, POD_DOCUMENTATION_URL_VALUE) + .put(Swift4Generator.SWIFT_USE_API_NAMESPACE, SWIFT_USE_API_NAMESPACE_VALUE) + .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/src/test/resources/binaryDataTest.json b/src/test/resources/binaryDataTest.json new file mode 100644 index 0000000..7b0f888 --- /dev/null +++ b/src/test/resources/binaryDataTest.json @@ -0,0 +1,51 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at http://swagger.io or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://helloreverb.com/terms/", + "license": { + "name": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath": "/v2", + "schemes": [ + "http" + ], + "paths": { + "/tests/binaryResponse": { + "post": { + "summary": "Echo test", + "operationId": "echotest", + "consumes": [ + "application/octet-stream" + ], + "produces": [ + "application/octet-stream" + ], + "parameters": [ + { + "name": "InputBinaryData", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": { + "200": { + "description": "OutputBinaryData", + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } +} diff --git a/src/test/resources/datePropertyTest.json b/src/test/resources/datePropertyTest.json new file mode 100644 index 0000000..b5e0417 --- /dev/null +++ b/src/test/resources/datePropertyTest.json @@ -0,0 +1,45 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at http://swagger.io or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://helloreverb.com/terms/", + "license": { + "name": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath": "/v2", + "schemes": [ + "http" + ], + "paths": { + "/tests/dateResponse": { + "post": { + "summary": "Echo test", + "operationId": "echotest", + "parameters": [ + { + "name": "InputDate", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "OutputDate", + "schema": { + "type": "string", + "format": "date" + } + } + } + } + } + } +} diff --git a/src/test/resources/petstore.json b/src/test/resources/petstore.json new file mode 100644 index 0000000..4395934 --- /dev/null +++ b/src/test/resources/petstore.json @@ -0,0 +1,977 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at http://swagger.io or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "schemes": [ + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": false, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": false, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Validation exception" + }, + "404": { + "description": "Pet not found" + }, + "400": { + "description": "Invalid ID supplied" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma seperated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "default": "available" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a pet when ID < 10. ID > 10 or nonintegers will simulate API error conditions", + "operationId": "getPetById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "404": { + "description": "Pet not found" + }, + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "string" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "default": { + "description": "successful operation" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json", + "application/xml" + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": false, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "Order not found" + }, + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "Order not found" + }, + "400": { + "description": "Invalid ID supplied" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": false, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/json", + "application/xml" + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + }, + "examples": { + "application/json": { + "id": 1, + "username": "johnp", + "firstName": "John", + "lastName": "Public", + "email": "johnp@swagger.io", + "password": "-secret-", + "phone": "0123456789", + "userStatus": 0 + } + } + }, + "400": { + "description": "Invalid username supplied" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": false, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "400": { + "description": "Invalid user supplied" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "400": { + "description": "Invalid username supplied" + } + } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + }, + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + }, + "definitions": { + "User": { + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Category": { + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "Tag": { + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Order": { + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + } + } +} \ No newline at end of file