Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

16 changed files with 1422 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
#idea-gitignore
.gradle
.idea/
!.idea/icon*.png
*.iml
build/
src/main/gen
src/main/resources/templates.list
src/main/resources/gitignore/*

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Touch Instinct
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.

View File

@ -1,2 +1,3 @@
# TINetworkingCodegen # TINetworkingCodegen
Swagger code generation for TINetworking

129
pom.xml Normal file
View File

@ -0,0 +1,129 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.swagger</groupId>
<artifactId>TINetworking-swagger-codegen</artifactId>
<packaging>jar</packaging>
<name>TINetworking-swagger-codegen</name>
<version>1.2.4</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M1</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>2.2.0</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<systemProperties>
<property>
<name>loggerPath</name>
<value>conf/log4j.properties</value>
</property>
</systemProperties>
<argLine>-Xms512m -Xmx1500m</argLine>
<parallel>methods</parallel>
<forkMode>pertest</forkMode>
</configuration>
</plugin>
<!-- attach test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin}</version>
<executions>
<execution>
<id>add_sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add_test_sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen</artifactId>
<version>${swagger-codegen-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-generators</artifactId>
<version>${swagger-codegen-generators-version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<swagger-codegen-version>3.0.33</swagger-codegen-version>
<swagger-codegen-generators-version>1.0.32</swagger-codegen-generators-version>
<maven-plugin-version>1.0.0</maven-plugin-version>
<junit-version>4.13.1</junit-version>
<build-helper-maven-plugin>3.0.0</build-helper-maven-plugin>
</properties>
</project>

View File

@ -0,0 +1,789 @@
package ru.touchin.codegen;
import io.swagger.codegen.v3.*;
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.RequestBody;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
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 TINetworkingCodegen extends DefaultCodegenConfig {
protected static final Logger LOGGER = LoggerFactory.getLogger(TINetworkingCodegen.class);
public static final String PROJECT_NAME = "projectName";
public static final String RESPONSE_AS = "responseAs";
protected String projectName = "SwaggerAPI";
private String[] responseAs = new String[0];
protected String sourceFolder = "Classes" + File.separator + "Swaggers";
private Map<String, String> allCustomDateFormats = new HashMap<>();
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public String getName() {
return "TINetworking";
}
@Override
public String getHelp() {
return "Generates a TINetworking client library.";
}
@Override
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel,
Schema swaggerModel) {
final Object additionalProperties = swaggerModel.getAdditionalProperties();
if (additionalProperties != null && additionalProperties instanceof Schema) {
codegenModel.additionalPropertiesType = getSchemaType((Schema) additionalProperties);
}
}
/**
* Constructor for the TINetworking language codegen module.
*/
public TINetworkingCodegen() {
super();
outputFolder = "generated-code" + File.separator + "swift";
modelTemplateFiles.put("model.mustache", ".swift");
apiTemplateFiles.put("api.mustache", ".swift");
apiPackage = File.separator + "APIs";
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(
//
// 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", "Date", "TimeInterval", "Codable", "Encodable", "Decodable",
// The following are other words we want to reserve
"Void", "AnyObject", "Class", "dynamicType", "COLUMN", "FILE", "FUNCTION", "LINE"
)
);
typeMapping = new HashMap<>();
typeMapping.put("array", "Array");
typeMapping.put("List", "Array");
typeMapping.put("map", "Dictionary");
typeMapping.put("date", "Date");
typeMapping.put("Date", "Date");
typeMapping.put("DateTime", "Date");
typeMapping.put("boolean", "Bool");
typeMapping.put("string", "String");
typeMapping.put("char", "Character");
typeMapping.put("short", "Int");
typeMapping.put("int", "Int");
typeMapping.put("long", "Int64");
typeMapping.put("integer", "Int");
typeMapping.put("Integer", "Int");
typeMapping.put("float", "Float");
typeMapping.put("number", "Double");
typeMapping.put("double", "Double");
typeMapping.put("object", "Any");
typeMapping.put("Object", "Any");
typeMapping.put("file", "URL");
typeMapping.put("binary", "Data");
typeMapping.put("ByteArray", "Data");
typeMapping.put("UUID", "UUID");
typeMapping.put("URI", "String");
typeMapping.put("BigDecimal", "Decimal");
importMapping = new HashMap<>();
cliOptions.add(new CliOption(PROJECT_NAME, "Project name in Xcode"));
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP,
CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC)
.defaultValue(Boolean.TRUE.toString()));
}
@Override
public void processOpts() {
super.processOpts();
/*
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
if (StringUtils.isBlank(templateDir)) {
embeddedTemplateDir = templateDir = getTemplateDir();
}
// 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(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);
supportingFiles.add(new SupportingFile("Servers.mustache",
sourceFolder,
projectName + "+Servers.swift"));
supportingFiles.add(new SupportingFile("OpenAPI.mustache",
sourceFolder,
projectName + "+OpenAPI.swift"));
copyFistAllOfProperties = true;
}
@Override
public List<SupportingFile> supportingFiles() {
List<SupportingFile> supportingFiles = super.supportingFiles();
if (!allCustomDateFormats.isEmpty()) {
supportingFiles.add(new SupportingFile("APIDateFormat.mustache",
sourceFolder,
"APIDateFormat.swift"));
}
return supportingFiles;
}
@Override
protected boolean isReservedWord(String word) {
return word != null && reservedWords.contains(word); //don't lowercase as super does
}
@Override
public String getDefaultTemplateDir() {
return "TINetworking";
}
@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 prop) {
if (prop instanceof ArraySchema) {
ArraySchema arraySchema = (ArraySchema) prop;
Schema inner = arraySchema.getItems();
return "[" + getTypeDeclaration(inner) + "]";
} else if (prop instanceof MapSchema) {
MapSchema mp = (MapSchema) prop;
Object inner = mp.getAdditionalProperties();
if (inner instanceof Schema) {
return "[String:" + getTypeDeclaration((Schema) inner) + "]";
}
}
return super.getTypeDeclaration(prop);
}
@Override
public String getSchemaType(Schema prop) {
String schemaType = super.getSchemaType(prop);
String type;
if (typeMapping.containsKey(schemaType)) {
type = typeMapping.get(schemaType);
if (languageSpecificPrimitives.contains(type) || defaultIncludes.contains(type)) {
return type;
}
} else {
type = schemaType;
}
return toModelName(type);
}
@Override
public CodegenParameter fromRequestBody(RequestBody body, String name, Schema schema, Map<String, Schema> schemas, Set<String> imports) {
CodegenParameter codegenParameter = super.fromRequestBody(body, name, schema, schemas, imports);
codegenParameter.description = codegenParameter.getDescription() == null
? schema.getDescription()
: codegenParameter.getDescription();
return codegenParameter;
}
@Override
public boolean isDataTypeFile(String dataType) {
return dataType != null && dataType.equals("URL");
}
@Override
public boolean isDataTypeBinary(final String dataType) {
return dataType != null && dataType.equals("Data");
}
/**
* Output the proper model name (capitalized).
*
* @param name the name of the model
* @return capitalized model name
*/
@Override
public String toModelName(String name) {
// FIXME parameter should not be assigned. Also declare it as "final"
name = sanitizeName(name);
if (!StringUtils.isEmpty(modelNameSuffix)) { // set model suffix
name = name + "_" + modelNameSuffix;
}
if (!StringUtils.isEmpty(modelNamePrefix)) { // set model prefix
name = modelNamePrefix + "_" + name;
}
// camelize the model name
// phone_number => PhoneNumber
name = camelize(name);
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(name)) {
String modelName = escapeReservedTypeDeclaration(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 prop) {
// nil
return null;
}
@Override
public String toInstantiationType(Schema prop) {
if (prop instanceof MapSchema) {
MapSchema mapSchema = (MapSchema) prop;
if (mapSchema.getAdditionalProperties() != null && mapSchema.getAdditionalProperties() instanceof Schema) {
return getSchemaType((Schema) mapSchema.getAdditionalProperties());
}
} else if (prop instanceof ArraySchema) {
ArraySchema ap = (ArraySchema) prop;
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 = camelize(sanitizeName(operationId), true);
// Throw exception if method name is empty.
// This should not happen but keep the check just in case
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
String newOperationId = camelize(("call_" + operationId), true);
LOGGER.warn(operationId + " (reserved word) cannot be used as method name."
+ " Renamed to " + newOperationId);
return newOperationId;
}
return operationId;
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name);
// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}
// camelize the variable name
// pet_id => petId
name = camelize(name, true);
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toParamName(String name) {
// sanitize name
name = sanitizeName(name);
// replace - with _ e.g. created-at => created_at
name = name.replaceAll("-", "_");
// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}
// camelize(lower) the variable name
// pet_id => petId
name = camelize(name, true);
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public CodegenModel fromModel(String name, Schema model, Map<String, Schema> 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);
reconcileProperties(codegenModel, parentCodegenModel);
// get the next parent
parentSchema = parentCodegenModel.parentSchema;
}
}
return codegenModel;
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Map<String, Object> supportingFileData = super.postProcessSupportingFileData(objs);
supportingFileData.put("apiDateFormats", allCustomDateFormats);
return supportingFileData;
}
protected void updateCodegenModelEnumVars(CodegenModel codegenModel) {
super.updateCodegenModelEnumVars(codegenModel);
for (CodegenProperty var : codegenModel.allVars) {
updateCodegenPropertyEnum(var);
}
}
@Override
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map<String, Schema> definitions, OpenAPI openAPI) {
CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, definitions, openAPI);
if (codegenOperation.returnType != null && codegenOperation.returnType.equals("Any")) {
codegenOperation.returnType = null;
}
if (operation.getResponses() != null && !operation.getResponses().isEmpty()) {
for (CodegenContent content : codegenOperation.getContents()) {
ArrayList<String> contentStatusCodes = new ArrayList<>();
for (CodegenResponse codegenResponse : codegenOperation.responses) {
contentStatusCodes.add(codegenResponse.code);
}
content.getContentExtensions()
.put("x-codegen-acceptable-status-codes", String.join(", ", contentStatusCodes));
}
}
return codegenOperation;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void setResponseAs(String[] responseAs) {
this.responseAs = responseAs;
}
@Override
public String toEnumValue(String value, String datatype) {
return String.valueOf(value);
}
@Override
public String toEnumDefaultValue(String value, String datatype) {
return datatype + "_" + value;
}
@Override
public String toEnumVarName(String name, String datatype) {
if (name.length() == 0) {
return "empty";
}
Pattern startWithNumberPattern = Pattern.compile("^\\d+");
Matcher startWithNumberMatcher = startWithNumberPattern.matcher(name);
if (startWithNumberMatcher.find()) {
String startingNumbers = startWithNumberMatcher.group(0);
String nameWithoutStartingNumbers = name.substring(startingNumbers.length());
return "_" + startingNumbers + camelize(nameWithoutStartingNumbers, true);
}
// for symbol, e.g. $, #
if (getSymbolName(name) != null) {
return camelize(WordUtils.capitalizeFully(getSymbolName(name).toUpperCase()), true);
}
// Camelize only when we have a structure defined below
boolean camelized = false;
if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) {
name = camelize(name, true);
camelized = true;
} else if (name.matches("[a-z]+((\\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?")) {
// already camelized: https://stackoverflow.com/a/47591707
camelized = true;
}
// Reserved Name
String nameLowercase = StringUtils.lowerCase(name);
if (isReservedWord(nameLowercase)) {
return escapeReservedWord(nameLowercase);
}
// Check for numerical conversions
if ("Int".equals(datatype) || "Int32".equals(datatype) || "Int64".equals(datatype)
|| "Float".equals(datatype) || "Double".equals(datatype)) {
String varName = "number" + camelize(name);
varName = varName.replaceAll("-", "minus");
varName = varName.replaceAll("\\+", "plus");
varName = varName.replaceAll("\\.", "dot");
return varName;
}
// If we have already camelized the word, don't progress
// any further
if (camelized) {
return name;
}
char[] separators = {'-', '_', ' ', ':', '(', ')'};
return camelize(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators)
.replaceAll("[-_ :()]", ""),
true);
}
@Override
public String toEnumName(CodegenProperty property) {
String enumName = toModelName(property.name);
// Ensure that the enum type doesn't match a reserved word or
// the variable name doesn't match the generated enum type or the
// Swift compiler will generate an error
if (isReservedWord(property.datatypeWithEnum)
|| toVarName(property.name).equals(property.datatypeWithEnum)) {
enumName = property.datatypeWithEnum + "Enum";
}
// TODO: toModelName already does something for names starting with number,
// so this code is probably never called
if (enumName.matches("\\d.*")) { // starts with number
return "_" + enumName;
} else {
return enumName;
}
}
@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
Map<String, Object> postProcessedModelsEnum = postProcessModelsEnum(objs);
// We iterate through the list of models, and also iterate through each of the
// properties for each model. For each property, if:
//
// CodegenProperty.name != CodegenProperty.baseName
//
// then we set
//
// CodegenProperty.vendorExtensions["x-codegen-escaped-property-name"] = true
//
// Also, if any property in the model has x-codegen-escaped-property-name=true, then we mark:
//
// CodegenModel.vendorExtensions["x-codegen-has-escaped-property-names"] = true
//
List<Object> models = (List<Object>) postProcessedModelsEnum.get("models");
for (Object _mo : models) {
Map<String, Object> mo = (Map<String, Object>) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
boolean modelHasPropertyWithEscapedName = false;
for (CodegenProperty prop : cm.allVars) {
if (!prop.name.equals(prop.baseName)) {
prop.vendorExtensions.put("x-codegen-escaped-property-name", true);
modelHasPropertyWithEscapedName = true;
}
}
if (modelHasPropertyWithEscapedName) {
cm.vendorExtensions.put("x-codegen-has-escaped-property-names", true);
}
}
return postProcessedModelsEnum;
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
if (typeAliases.containsKey(property.baseType)) {
Schema resolvedPropertySchema = getOpenAPI().getComponents().getSchemas().get(property.datatype);
postProcessAliasProperty(property, resolvedPropertySchema);
}
updateVendorExtensionsForProperty(property);
if (property.getIsListContainer()) {
updateVendorExtensionsForProperty(property.items);
}
if (property.getIsObject() && isReservedWord(property.getDatatype())) {
property.datatype = escapeReservedTypeDeclaration(property.getDatatype());
property.datatypeWithEnum = property.datatype;
}
}
private void updateVendorExtensionsForProperty(CodegenProperty property) {
Map<String, Object> vendorExtensions = property.getVendorExtensions();
if (isISO8601DateProperty(property)) {
vendorExtensions.put(TINetworkingCodegenConstants.IS_ISO8601_DATE, true);
} else if (isCustomDateFormatProperty(property)) {
String customDateFormat = (String) vendorExtensions.get(TINetworkingCodegenConstants.DATE_FORMAT);
String dateFormatName = customDateFormat.replace(".", "_")
.replaceAll("[^A-Za-z0-9_]", "");
vendorExtensions.put(TINetworkingCodegenConstants.DATE_FORMAT_NAME, dateFormatName);
allCustomDateFormats.put(dateFormatName, customDateFormat);
}
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
private static void 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<CodegenProperty> parentModelCodegenProperties = parentCodegenModel.vars;
List<CodegenProperty> 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<CodegenProperty> 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.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, count < numVars);
}
codegenModel.vars = codegenProperties;
}
}
private String escapeReservedTypeDeclaration(String name) {
return "Model" + name;
}
private void postProcessAliasProperty(CodegenProperty codegenProperty, Schema resolvedPropertySchema) {
Map<String, Object> propertyExtensions = codegenProperty.getVendorExtensions();
propertyExtensions.put(CodegenConstants.IS_ALIAS_EXT_NAME, Boolean.TRUE);
codegenProperty.setDescription(codegenProperty.getDescription() == null
? resolvedPropertySchema.getDescription()
: codegenProperty.getDescription());
if (resolvedPropertySchema.getExtensions() != null) {
propertyExtensions.putAll(resolvedPropertySchema.getExtensions());
}
if (resolvedPropertySchema instanceof DateSchema) {
propertyExtensions.put(CodegenConstants.IS_DATE_EXT_NAME, Boolean.TRUE);
}
if (resolvedPropertySchema instanceof DateTimeSchema) {
propertyExtensions.put(CodegenConstants.IS_DATE_TIME_EXT_NAME, Boolean.TRUE);
}
}
private boolean isISO8601DateProperty(CodegenProperty property) {
return property.getIsDate()
|| property.getIsDateTime()
&& !isCustomDateFormatProperty(property);
}
private boolean isCustomDateFormatProperty(CodegenProperty property) {
return property.getVendorExtensions().containsKey(TINetworkingCodegenConstants.DATE_FORMAT);
}
}

View File

@ -0,0 +1,7 @@
package ru.touchin.codegen;
public class TINetworkingCodegenConstants {
public static final String DATE_FORMAT = "x-custom-date-format";
public static final String DATE_FORMAT_NAME = "x-codegen-date-format-name";
public static final String IS_ISO8601_DATE = "x-codegen-is-iso8601-date";
}

View File

@ -0,0 +1 @@
ru.touchin.codegen.TINetworkingCodegen

View File

@ -0,0 +1,8 @@
import TIFoundationUtils
import Foundation
public enum APIDateFormat: String, DateFormat {
{{#each apiDateFormats as |value key|}}
case {{key}} = "{{{value}}}"
{{/each}}
}

View File

@ -0,0 +1,17 @@
import TINetworking
public extension OpenAPI {
enum SecurityNames: String, Hashable {
{{#authMethods}}
case {{name}}
{{/authMethods}}
}
static var {{projectName}}: Self {
.init(defaultServer: .default, security: [
{{#authMethods}}
"{{name}}": {{#isApiKey}}.apiKey({{#isKeyInHeader}}.header{{/isKeyInHeader}}{{#isKeyInQuery}}.query{{/isKeyInQuery}}, parameterName: "{{keyParamName}}"){{/isApiKey}}{{#isBearer}}.http(.bearer){{/isBearer}}{{#isBasic}}.http(.basic){{/isBasic}}{{#hasMore}},{{/hasMore}}
{{/authMethods}}
])
}
}

View File

@ -0,0 +1,7 @@
import TINetworking
public extension Server {
static var `default`: Server {
.init(baseUrl: "{{basePath}}")
}
}

View File

@ -0,0 +1,76 @@
{{#operations}}
import Alamofire
import TINetworking
{{#description}}
/**
{{description}}
*/
{{/description}}
{{#operation}}
{{#contents}}
{{#parameters}}
{{#isEnum}}
/**
* enum for parameter {{paramName}}
*/
public enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}} { {{#allowableValues}}{{#enumVars}}
case {{name}} = {{#isContainer}}"{{/isContainer}}{{#isString}}"{{/isString}}{{{value}}}{{#isString}}"{{/isString}}{{#isContainer}}"{{/isContainer}}{{/enumVars}}{{/allowableValues}}
}
{{/isEnum}}
{{/parameters}}
{{/contents}}
{{/operation}}
public extension EndpointRequest {
{{#operation}}
{{#contents}}
/**
{{#summary}}
{{{summary}}}
{{/summary}}
{{#parameters}}
- parameter {{paramName}}: {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/parameters}}
*/
static func {{operationId}}({{#parameters}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/parameters}}{{#hasParams}}, {{/hasParams}}server: Server? = nil, security: [[String]]? = nil) -> EndpointRequest<{{#parameters}}{{#isBodyParam}}{{{dataType}}}{{/isBodyParam}}{{/parameters}}{{^hasBodyParam}}Nothing{{/hasBodyParam}}, {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Nothing{{/returnType}}> {
.init(templatePath: "{{{path}}}",
method: .init(rawValue: "{{httpMethod}}"),
body: {{#hasBodyParam}}body{{/hasBodyParam}}{{^hasBodyParam}}nil{{/hasBodyParam}},
{{#if queryParams}}
queryParameters: [
{{#each queryParams}}
"{{{this.baseName}}}": .init(value: {{{this.paramName}}}, allowEmptyValue: {{#this.required}}false{{/this.required}}{{^this.required}}true{{/this.required}}),
{{/each}}],
{{/if}}
{{#hasPathParams}}
pathParameters: [
{{#each pathParams}}
"{{{this.baseName}}}": .init(value: {{{this.paramName}}}, allowEmptyValue: {{#this.required}}false{{/this.required}}{{^this.required}}true{{/this.required}}),
{{/each}}],
{{/hasPathParams}}
{{#hasHeaderParams}}
headerParameters: [
{{#each headerParams}}
"{{{this.baseName}}}": .init(value: {{{this.paramName}}}, allowEmptyValue: {{#this.required}}false{{/this.required}}{{^this.required}}true{{/this.required}}),
{{/each}}],
{{/hasHeaderParams}}
{{^hasHeaderParams}}
headerParameters: nil,
{{/hasHeaderParams}}
{{#hasCookieParams}}
cookieParameters: [
{{#each cookieParams}}
"{{{this.baseName}}}": .init(value: {{{this.paramName}}}, allowEmptyValue: {{#this.required}}false{{/this.required}}{{^this.required}}true{{/this.required}}),
{{/each}}],
{{/hasCookieParams}}
acceptableStatusCodes: [{{contentExtensions.x-codegen-acceptable-status-codes}}],
security: security ?? [{{#if authMethods}}[{{/if}}{{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}}{{#if authMethods}}]{{/if}}], // note: OR requirement is not supported by swagger-codegen
server: server)
}
{{/contents}}
{{/operation}}
}
{{/operations}}

View File

@ -0,0 +1,26 @@
{{#models}}{{#model}}
import Foundation
import TIFoundationUtils
{{#description}}
/** {{description}} */{{/description}}
{{#isArrayModel}}
{{> modelArray}}
{{/isArrayModel}}
{{^isArrayModel}}
{{#isEnum}}
{{> modelEnum}}
{{/isEnum}}
{{^isEnum}}
{{#isAlias}}
public typealias {{classname}} = {{dataType}}
{{/isAlias}}
{{^isAlias}}
{{> modelObject}}
{{/isAlias}}
{{/isEnum}}
{{/isArrayModel}}
{{/model}}
{{/models}}

View File

@ -0,0 +1 @@
public typealias {{classname}} = {{parent}}

View File

@ -0,0 +1,4 @@
public enum {{classname}}: {{dataType}}, Codable, CaseIterable, Equatable {
{{#allowableValues}}{{#enumVars}} case {{name}} = {{#isString}}"{{/isString}}{{{value}}}{{#isString}}"{{/isString}}
{{/enumVars}}{{/allowableValues}}
}

View File

@ -0,0 +1,3 @@
public enum {{enumName}}: {{^isContainer}}{{datatype}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Codable, CaseIterable, Equatable { {{#allowableValues}}{{#enumVars}}
case {{name}} = {{#isContainer}}"{{/isContainer}}{{#isString}}"{{/isString}}{{{value}}}{{#isString}}"{{/isString}}{{#isContainer}}"{{/isContainer}}{{/enumVars}}{{/allowableValues}}
}

View File

@ -0,0 +1,130 @@
public {{#useModelClasses}}class{{/useModelClasses}}{{^useModelClasses}}struct{{/useModelClasses}} {{classname}}: Codable, Equatable {
{{#allVars}}
{{#isEnum}}
{{> modelInlineEnumDeclaration}}
{{/isEnum}}
{{/allVars}}
{{#allVars}}
{{#isEnum}}
{{#description}}/** {{description}} */
{{/description}}public var {{name}}: {{{datatypeWithEnum}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
{{/isEnum}}
{{^isEnum}}
{{#description}}
/** {{description}} */
{{/description}}
public var {{name}}: {{{datatype}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
{{/isEnum}}
{{/allVars}}
{{#hasVars}}
private enum CodingKeys: String, CodingKey {
{{#allVars}}
case {{name}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}}
{{/allVars}}
}
public init({{#allVars}}{{name}}: {{{datatypeWithEnum}}}{{^required}}? = nil{{/required}}{{#hasMore}}, {{/hasMore}}{{/allVars}}) {
{{#allVars}}
self.{{name}} = {{name}}
{{/allVars}}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
{{#allVars}}
{{#if isListContainer}}
{{#if items.vendorExtensions.x-codegen-is-iso8601-date}}
{{name}} = try container.decodeDates(forKey: .{{name}}, userInfo: decoder.userInfo, options: .{{#items.isDate}}withFullDate{{/items.isDate}}{{#items.isDateTime}}withInternetDateTime{{/items.isDateTime}}{{^required}}, required: false{{/required}})
{{else if items.vendorExtensions.x-custom-date-format}}
{{name}} = try container.decodeDates(forKey: .{{name}}, userInfo: decoder.userInfo, dateFormat: APIDateFormat.{{items.vendorExtensions.x-codegen-date-format-name}}{{^required}}, required: false{{/required}})
{{else}}
{{name}} = try container.decode({{{datatypeWithEnum}}}{{^required}}?{{/required}}.self, forKey: .{{name}}{{^required}}, required: false{{/required}})
{{/if}}
{{else}}
{{#if vendorExtensions.x-codegen-is-iso8601-date}}
{{name}} = try container.decodeDate(forKey: .{{name}}, userInfo: decoder.userInfo, options: .{{#isDate}}withFullDate{{/isDate}}{{#isDateTime}}withInternetDateTime{{/isDateTime}}{{^required}}, required: false{{/required}})
{{else if vendorExtensions.x-custom-date-format}}
{{name}} = try container.decodeDate(forKey: .{{name}}, userInfo: decoder.userInfo, dateFormat: APIDateFormat.{{vendorExtensions.x-codegen-date-format-name}}{{^required}}, required: false{{/required}})
{{else}}
{{name}} = try container.decode({{{datatypeWithEnum}}}{{^required}}?{{/required}}.self, forKey: .{{name}}{{^required}}, required: false{{/required}})
{{/if}}
{{/if}}
{{/allVars}}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
{{#allVars}}
{{#if isListContainer}}
{{#if items.vendorExtensions.x-codegen-is-iso8601-date}}
try container.encode(dates: {{name}}, forKey: .{{name}}, userInfo: encoder.userInfo, options: .{{#items.isDate}}withFullDate{{/items.isDate}}{{#items.isDateTime}}withInternetDateTime{{/items.isDateTime}}{{^required}}, required: false{{/required}})
{{else if items.vendorExtensions.x-custom-date-format}}
try container.encode(dates: {{name}}, forKey: .{{name}}, userInfo: encoder.userInfo, dateFormat: APIDateFormat.{{items.vendorExtensions.x-codegen-date-format-name}}{{^required}}, required: false{{/required}})
{{else}}
try container.encode({{name}}, forKey: .{{name}}{{^required}}, required: false{{/required}})
{{/if}}
{{else}}
{{#if vendorExtensions.x-codegen-is-iso8601-date}}
try container.encode(date: {{name}}, forKey: .{{name}}, userInfo: encoder.userInfo, options: .{{#isDate}}withFullDate{{/isDate}}{{#isDateTime}}withInternetDateTime{{/isDateTime}}{{^required}}, required: false{{/required}})
{{else if vendorExtensions.x-custom-date-format}}
try container.encode(date: {{name}}, forKey: .{{name}}, userInfo: encoder.userInfo, dateFormat: APIDateFormat.{{vendorExtensions.x-codegen-date-format-name}}{{^required}}, required: false{{/required}})
{{else}}
try container.encode({{name}}, forKey: .{{name}}{{^required}}, required: false{{/required}})
{{/if}}
{{/if}}
{{/allVars}}
}
{{/hasVars}}
{{^hasVars}}
public init() {}
{{/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{{^required}}IfPresent{{/required}}({{{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{{^required}}IfPresent{{/required}}({{{datatypeWithEnum}}}.self, forKey: "{{{baseName}}}")
{{/allVars}}
var nonAdditionalPropertyKeys = Set<String>()
{{#allVars}}
nonAdditionalPropertyKeys.insert("{{{baseName}}}")
{{/allVars}}
additionalProperties = try container.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys)
}
{{/additionalPropertiesType}}
}