forked from loafle/openapi-generator-original
[kotlin-server] --library=ktor (barebones implementation) (#7412)
* [tools] Make sed in new.sh more cross-platform The -r option passed to sed is a GNU sed option for extended regex evaluation. The -E option evaluates the same option, and is part of the POSIX standard, meaning this option is available in GNU sed as well as Apple's BSD variant. This commit removes the need for users to install gnu-sed on Mac. * [ktor] Initial ktor (kotlin-server) This adds a very barebones implementation for a ktor server generator. This supports metrics and typed locations. All endpoins are stubbed to return HTTP/1.1 501 Not Implemented. * [ktor] Initial sample * [ktor] Adding options for select feature installs Options available: * featureAutoHead * featureConditionalHeaders * featureHSTS * featureCORS * featureCompression * [ktor] Start of auth functionality * [ktor] API key auth placeholder * Add basic support for oauth2 configurations ktor doesn't seem to explicitly accept oauth flow properties in its configuration object. This may be a blocker for 'implicit' flow definitions. * Added example response objects * [ktor] Route for apis with bodies, some cleanup ktor locations are only supported for routes with path/query parameters. Routes with body or file parameters must be declared with traditional route api. This commit also includes lambdas for simplifying processing in library-based server generator code. As an example, ktor requires lowercase http methods while spring (a potential future generator) would require an uppercase such as HttpMethod.GET. It doesn't make sense to modify these in the operations post-process method because that format wouldn't be universally desirable. The lambdas included in the KotlinServerCodegen: * lowercase: converts all text to lowercase * uppercase: converts all text to UPPERCASE * titlecase: converts words (with configurable delim) to Title Case * indented|indented_8|indented_12|indented_16: these helpers apply the same desired indent to all lines of an included fragment's text. * Fix some javadoc issues in lambda classes * Update kotlin-server-petstore.bat Change `kotlin` to `kotlin-server` * Fix javadoc error messages in CI
This commit is contained in:
parent
a2410b210c
commit
7cad47dd39
31
bin/kotlin-server-petstore.sh
Executable file
31
bin/kotlin-server-petstore.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
|
||||
SCRIPT="$0"
|
||||
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=$(ls -ld "$SCRIPT")
|
||||
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=$(dirname "$SCRIPT")/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "${APP_DIR}" ]; then
|
||||
APP_DIR=$(dirname "$SCRIPT")/..
|
||||
APP_DIR=$(cd "${APP_DIR}"; pwd)
|
||||
fi
|
||||
|
||||
executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar"
|
||||
|
||||
if [ ! -f "$executable" ]
|
||||
then
|
||||
mvn clean package
|
||||
fi
|
||||
|
||||
# if you've executed sbt assembly previously it will use that instead.
|
||||
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties -DdebugSupportingFiles=true"
|
||||
ags="generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -t modules/swagger-codegen/src/main/resources/kotlin-server -l kotlin-server --library=ktor -o samples/server/petstore/kotlin-server/ktor $@"
|
||||
|
||||
java ${JAVA_OPTS} -jar ${executable} ${ags}
|
10
bin/windows/kotlin-server-petstore.bat
Normal file
10
bin/windows/kotlin-server-petstore.bat
Normal file
@ -0,0 +1,10 @@
|
||||
set executable=.\modules\swagger-codegen-cli\target\swagger-codegen-cli.jar
|
||||
|
||||
If Not Exist %executable% (
|
||||
mvn clean package
|
||||
)
|
||||
|
||||
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
|
||||
set ags=generate --artifact-id "kotlin-petstore-server" -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -l kotlin-server --library=ktor -o samples\server\petstore\kotlin
|
||||
|
||||
java %JAVA_OPTS% -jar %executable% %ags%
|
@ -194,12 +194,15 @@ public class CodegenConstants {
|
||||
public static final String EXCLUDE_TESTS_DESC = "Specifies that no tests are to be generated.";
|
||||
|
||||
// Not user-configurable. System provided for use in templates.
|
||||
|
||||
public static final String GENERATE_APIS = "generateApis";
|
||||
public static final String GENERATE_API_DOCS = "generateApiDocs";
|
||||
|
||||
public static final String GENERATE_API_TESTS = "generateApiTests";
|
||||
public static final String GENERATE_API_TESTS_DESC = "Specifies that api tests are to be generated.";
|
||||
|
||||
// Not user-configurable. System provided for use in templates.
|
||||
public static final String GENERATE_MODELS = "generateModels";
|
||||
public static final String GENERATE_MODEL_DOCS = "generateModelDocs";
|
||||
|
||||
public static final String GENERATE_MODEL_TESTS = "generateModelTests";
|
||||
|
@ -157,6 +157,9 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
|
||||
config.additionalProperties().put(CodegenConstants.GENERATE_API_DOCS, generateApiDocumentation);
|
||||
config.additionalProperties().put(CodegenConstants.GENERATE_MODEL_DOCS, generateModelDocumentation);
|
||||
|
||||
config.additionalProperties().put(CodegenConstants.GENERATE_APIS, generateApis);
|
||||
config.additionalProperties().put(CodegenConstants.GENERATE_MODELS, generateModels);
|
||||
|
||||
if (!generateApiTests && !generateModelTests) {
|
||||
config.additionalProperties().put(CodegenConstants.EXCLUDE_TESTS, true);
|
||||
}
|
||||
|
@ -434,7 +434,7 @@ public class InlineModelResolver {
|
||||
*
|
||||
* @param ref new property name
|
||||
* @param property Property
|
||||
* @return
|
||||
* @return {@link Property} A constructed Swagger property
|
||||
*/
|
||||
public Property makeRefProperty(String ref, Property property) {
|
||||
RefProperty newProperty = new RefProperty(ref);
|
||||
|
@ -0,0 +1,548 @@
|
||||
package io.swagger.codegen.languages;
|
||||
|
||||
import io.swagger.codegen.CliOption;
|
||||
import io.swagger.codegen.CodegenConfig;
|
||||
import io.swagger.codegen.CodegenConstants;
|
||||
import io.swagger.codegen.DefaultCodegen;
|
||||
import io.swagger.models.properties.ArrayProperty;
|
||||
import io.swagger.models.properties.MapProperty;
|
||||
import io.swagger.models.properties.Property;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractKotlinCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class);
|
||||
|
||||
protected String artifactId;
|
||||
protected String artifactVersion = "1.0.0";
|
||||
protected String groupId = "io.swagger";
|
||||
protected String packageName;
|
||||
|
||||
protected String sourceFolder = "src/main/kotlin";
|
||||
|
||||
protected String apiDocPath = "docs/";
|
||||
protected String modelDocPath = "docs/";
|
||||
|
||||
protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase;
|
||||
|
||||
public AbstractKotlinCodegen() {
|
||||
super();
|
||||
supportsInheritance = true;
|
||||
|
||||
languageSpecificPrimitives = new HashSet<String>(Arrays.asList(
|
||||
"kotlin.Byte",
|
||||
"kotlin.Short",
|
||||
"kotlin.Int",
|
||||
"kotlin.Long",
|
||||
"kotlin.Float",
|
||||
"kotlin.Double",
|
||||
"kotlin.Boolean",
|
||||
"kotlin.Char",
|
||||
"kotlin.String",
|
||||
"kotlin.Array",
|
||||
"kotlin.collections.List",
|
||||
"kotlin.collections.Map",
|
||||
"kotlin.collections.Set"
|
||||
));
|
||||
|
||||
// this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java
|
||||
// as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html
|
||||
reservedWords = new HashSet<String>(Arrays.asList(
|
||||
"abstract",
|
||||
"annotation",
|
||||
"as",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"companion",
|
||||
"const",
|
||||
"constructor",
|
||||
"continue",
|
||||
"crossinline",
|
||||
"data",
|
||||
"delegate",
|
||||
"do",
|
||||
"else",
|
||||
"enum",
|
||||
"external",
|
||||
"false",
|
||||
"final",
|
||||
"finally",
|
||||
"for",
|
||||
"fun",
|
||||
"if",
|
||||
"in",
|
||||
"infix",
|
||||
"init",
|
||||
"inline",
|
||||
"inner",
|
||||
"interface",
|
||||
"internal",
|
||||
"is",
|
||||
"it",
|
||||
"lateinit",
|
||||
"lazy",
|
||||
"noinline",
|
||||
"null",
|
||||
"object",
|
||||
"open",
|
||||
"operator",
|
||||
"out",
|
||||
"override",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"reified",
|
||||
"return",
|
||||
"sealed",
|
||||
"super",
|
||||
"suspend",
|
||||
"tailrec",
|
||||
"this",
|
||||
"throw",
|
||||
"true",
|
||||
"try",
|
||||
"typealias",
|
||||
"typeof",
|
||||
"val",
|
||||
"var",
|
||||
"vararg",
|
||||
"when",
|
||||
"while"
|
||||
));
|
||||
|
||||
defaultIncludes = new HashSet<String>(Arrays.asList(
|
||||
"kotlin.Byte",
|
||||
"kotlin.Short",
|
||||
"kotlin.Int",
|
||||
"kotlin.Long",
|
||||
"kotlin.Float",
|
||||
"kotlin.Double",
|
||||
"kotlin.Boolean",
|
||||
"kotlin.Char",
|
||||
"kotlin.Array",
|
||||
"kotlin.collections.List",
|
||||
"kotlin.collections.Set",
|
||||
"kotlin.collections.Map"
|
||||
));
|
||||
|
||||
typeMapping = new HashMap<String, String>();
|
||||
typeMapping.put("string", "kotlin.String");
|
||||
typeMapping.put("boolean", "kotlin.Boolean");
|
||||
typeMapping.put("integer", "kotlin.Int");
|
||||
typeMapping.put("float", "kotlin.Float");
|
||||
typeMapping.put("long", "kotlin.Long");
|
||||
typeMapping.put("double", "kotlin.Double");
|
||||
typeMapping.put("number", "java.math.BigDecimal");
|
||||
typeMapping.put("date-time", "java.time.LocalDateTime");
|
||||
typeMapping.put("date", "java.time.LocalDateTime");
|
||||
typeMapping.put("file", "java.io.File");
|
||||
typeMapping.put("array", "kotlin.Array");
|
||||
typeMapping.put("list", "kotlin.Array");
|
||||
typeMapping.put("map", "kotlin.collections.Map");
|
||||
typeMapping.put("object", "kotlin.Any");
|
||||
typeMapping.put("binary", "kotlin.Array<kotlin.Byte>");
|
||||
typeMapping.put("Date", "java.time.LocalDateTime");
|
||||
typeMapping.put("DateTime", "java.time.LocalDateTime");
|
||||
|
||||
instantiationTypes.put("array", "arrayOf");
|
||||
instantiationTypes.put("list", "arrayOf");
|
||||
instantiationTypes.put("map", "mapOf");
|
||||
|
||||
importMapping = new HashMap<String, String>();
|
||||
importMapping.put("BigDecimal", "java.math.BigDecimal");
|
||||
importMapping.put("UUID", "java.util.UUID");
|
||||
importMapping.put("File", "java.io.File");
|
||||
importMapping.put("Date", "java.util.Date");
|
||||
importMapping.put("Timestamp", "java.sql.Timestamp");
|
||||
importMapping.put("DateTime", "java.time.LocalDateTime");
|
||||
importMapping.put("LocalDateTime", "java.time.LocalDateTime");
|
||||
importMapping.put("LocalDate", "java.time.LocalDate");
|
||||
importMapping.put("LocalTime", "java.time.LocalTime");
|
||||
|
||||
specialCharReplacements.put(";", "Semicolon");
|
||||
|
||||
cliOptions.clear();
|
||||
addOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC, sourceFolder);
|
||||
addOption(CodegenConstants.PACKAGE_NAME, "Generated artifact package name (e.g. io.swagger).", packageName);
|
||||
addOption(CodegenConstants.GROUP_ID, "Generated artifact package's organization (i.e. maven groupId).", groupId);
|
||||
addOption(CodegenConstants.ARTIFACT_ID, "Generated artifact id (name of jar).", artifactId);
|
||||
addOption(CodegenConstants.ARTIFACT_VERSION, "Generated artifact's package version.", artifactVersion);
|
||||
|
||||
CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC);
|
||||
cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name()));
|
||||
}
|
||||
|
||||
protected void addOption(String key, String description) {
|
||||
addOption(key, description, null);
|
||||
}
|
||||
|
||||
protected void addOption(String key, String description, String defaultValue) {
|
||||
CliOption option = new CliOption(key, description);
|
||||
if (defaultValue != null) option.defaultValue(defaultValue);
|
||||
cliOptions.add(option);
|
||||
}
|
||||
|
||||
protected void addSwitch(String key, String description, Boolean defaultValue) {
|
||||
CliOption option = CliOption.newBoolean(key, description);
|
||||
if (defaultValue != null) option.defaultValue(defaultValue.toString());
|
||||
cliOptions.add(option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiDocFileFolder() {
|
||||
return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove " to avoid code injection
|
||||
return input.replace("\"", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
// TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping).
|
||||
return String.format("`%s`", name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("*/", "*_/").replace("/*", "/_*");
|
||||
}
|
||||
|
||||
public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() {
|
||||
return this.enumPropertyNaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the naming convention for Kotlin enum properties
|
||||
*
|
||||
* @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE}
|
||||
*/
|
||||
public void setEnumPropertyNaming(final String enumPropertyNamingType) {
|
||||
try {
|
||||
this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:");
|
||||
for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) {
|
||||
sb.append("\n ").append(t.name());
|
||||
}
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the swagger type for the property
|
||||
*
|
||||
* @param p Swagger property object
|
||||
* @return string presentation of the type
|
||||
**/
|
||||
@Override
|
||||
public String getSwaggerType(Property p) {
|
||||
String swaggerType = super.getSwaggerType(p);
|
||||
String type;
|
||||
// This maps, for example, long -> kotlin.Long based on hashes in this type's constructor
|
||||
if (typeMapping.containsKey(swaggerType)) {
|
||||
type = typeMapping.get(swaggerType);
|
||||
if (languageSpecificPrimitives.contains(type)) {
|
||||
return toModelName(type);
|
||||
}
|
||||
} else {
|
||||
type = swaggerType;
|
||||
}
|
||||
return toModelName(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the type declaration of the property
|
||||
*
|
||||
* @param p Swagger Property object
|
||||
* @return a string presentation of the property type
|
||||
*/
|
||||
@Override
|
||||
public String getTypeDeclaration(Property p) {
|
||||
if (p instanceof ArrayProperty) {
|
||||
return getArrayTypeDeclaration((ArrayProperty) p);
|
||||
} else if (p instanceof MapProperty) {
|
||||
MapProperty mp = (MapProperty) p;
|
||||
Property inner = mp.getAdditionalProperties();
|
||||
|
||||
// Maps will be keyed only by primitive Kotlin string
|
||||
return getSwaggerType(p) + "<kotlin.String, " + getTypeDeclaration(inner) + ">";
|
||||
}
|
||||
return super.getTypeDeclaration(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String modelDocFileFolder() {
|
||||
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String modelFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
|
||||
return postProcessModelsEnum(super.postProcessModels(objs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) {
|
||||
setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING));
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
|
||||
this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
|
||||
this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
|
||||
if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE))
|
||||
this.setModelPackage(packageName + ".models");
|
||||
if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE))
|
||||
this.setApiPackage(packageName + ".apis");
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) {
|
||||
this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.GROUP_ID)) {
|
||||
this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) {
|
||||
this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
|
||||
LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + ".");
|
||||
}
|
||||
|
||||
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage());
|
||||
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage());
|
||||
|
||||
additionalProperties.put("apiDocPath", apiDocPath);
|
||||
additionalProperties.put("modelDocPath", modelDocPath);
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactVersion(String artifactVersion) {
|
||||
this.artifactVersion = artifactVersion;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public void setSourceFolder(String sourceFolder) {
|
||||
this.sourceFolder = sourceFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sanitized variable name for enum
|
||||
*
|
||||
* @param value enum variable name
|
||||
* @param datatype data type
|
||||
* @return the sanitized variable name for enum
|
||||
*/
|
||||
@Override
|
||||
public String toEnumVarName(String value, String datatype) {
|
||||
String modified;
|
||||
if (value.length() == 0) {
|
||||
modified = "EMPTY";
|
||||
} else {
|
||||
modified = value;
|
||||
modified = sanitizeKotlinSpecificNames(modified);
|
||||
}
|
||||
|
||||
switch (getEnumPropertyNaming()) {
|
||||
case original:
|
||||
// NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped.
|
||||
modified = value;
|
||||
break;
|
||||
case camelCase:
|
||||
// NOTE: Removes hyphens and underscores
|
||||
modified = camelize(modified, true);
|
||||
break;
|
||||
case PascalCase:
|
||||
// NOTE: Removes hyphens and underscores
|
||||
String result = camelize(modified);
|
||||
modified = titleCase(result);
|
||||
break;
|
||||
case snake_case:
|
||||
// NOTE: Removes hyphens
|
||||
modified = underscore(modified);
|
||||
break;
|
||||
case UPPERCASE:
|
||||
modified = modified.toUpperCase();
|
||||
break;
|
||||
}
|
||||
|
||||
if (reservedWords.contains(modified)) {
|
||||
return escapeReservedWord(modified);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toInstantiationType(Property p) {
|
||||
if (p instanceof ArrayProperty) {
|
||||
return getArrayTypeDeclaration((ArrayProperty) p);
|
||||
}
|
||||
return super.toInstantiationType(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully-qualified "Model" name for import
|
||||
*
|
||||
* @param name the name of the "Model"
|
||||
* @return the fully-qualified "Model" name for import
|
||||
*/
|
||||
@Override
|
||||
public String toModelImport(String name) {
|
||||
// toModelImport is called while processing operations, but DefaultCodegen doesn't
|
||||
// define imports correctly with fully qualified primitives and models as defined in this generator.
|
||||
if (needToImport(name)) {
|
||||
return super.toModelImport(name);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the proper model name (capitalized).
|
||||
* In case the name belongs to the TypeSystem it won't be renamed.
|
||||
*
|
||||
* @param name the name of the model
|
||||
* @return capitalized model name
|
||||
*/
|
||||
@Override
|
||||
public String toModelName(final String name) {
|
||||
// Allow for explicitly configured kotlin.* and java.* types
|
||||
if (name.startsWith("kotlin.") || name.startsWith("java.")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// If importMapping contains name, assume this is a legitimate model name.
|
||||
if (importMapping.containsKey(name)) {
|
||||
return importMapping.get(name);
|
||||
}
|
||||
|
||||
String modifiedName = name.replaceAll("\\.", "");
|
||||
modifiedName = sanitizeKotlinSpecificNames(modifiedName);
|
||||
|
||||
if (reservedWords.contains(modifiedName)) {
|
||||
modifiedName = escapeReservedWord(modifiedName);
|
||||
}
|
||||
|
||||
return titleCase(modifiedName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
|
||||
*
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
private String getArrayTypeDeclaration(ArrayProperty arr) {
|
||||
// TODO: collection type here should be fully qualified namespace to avoid model conflicts
|
||||
// This supports arrays of arrays.
|
||||
String arrayType = typeMapping.get("array");
|
||||
StringBuilder instantiationType = new StringBuilder(arrayType);
|
||||
Property items = arr.getItems();
|
||||
String nestedType = getTypeDeclaration(items);
|
||||
// TODO: We may want to differentiate here between generics and primitive arrays.
|
||||
instantiationType.append("<").append(nestedType).append(">");
|
||||
return instantiationType.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}.
|
||||
*
|
||||
* @param name string to be sanitize
|
||||
* @return sanitized string
|
||||
*/
|
||||
private String sanitizeKotlinSpecificNames(final String name) {
|
||||
String word = name;
|
||||
for (Map.Entry<String, String> specialCharacters : specialCharReplacements.entrySet()) {
|
||||
// Underscore is the only special character we'll allow
|
||||
if (!specialCharacters.getKey().equals("_")) {
|
||||
word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback, replace unknowns with underscore.
|
||||
word = word.replaceAll("\\W+", "_");
|
||||
if (word.matches("\\d.*")) {
|
||||
word = "_" + word;
|
||||
}
|
||||
|
||||
// _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count.
|
||||
if (word.matches("^_*$")) {
|
||||
word = word.replaceAll("\\Q_\\E", "Underscore");
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
private String titleCase(final String input) {
|
||||
return input.substring(0, 1).toUpperCase() + input.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReservedWord(String word) {
|
||||
// We want case-sensitive escaping, to avoid unnecessary backtick-escaping.
|
||||
return reservedWords.contains(word);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the type to see if it needs import the library/module/package
|
||||
*
|
||||
* @param type name of the type
|
||||
* @return true if the library/module/package of the corresponding type needs to be imported
|
||||
*/
|
||||
@Override
|
||||
protected boolean needToImport(String type) {
|
||||
// provides extra protection against improperly trying to import language primitives and java types
|
||||
boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type);
|
||||
return imports;
|
||||
}
|
||||
}
|
@ -1,36 +1,25 @@
|
||||
package io.swagger.codegen.languages;
|
||||
|
||||
import io.swagger.codegen.*;
|
||||
import io.swagger.models.properties.ArrayProperty;
|
||||
import io.swagger.models.properties.MapProperty;
|
||||
import io.swagger.models.properties.Property;
|
||||
import io.swagger.codegen.CodegenType;
|
||||
import io.swagger.codegen.SupportingFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
public class KotlinClientCodegen extends AbstractKotlinCodegen {
|
||||
|
||||
static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class);
|
||||
|
||||
protected String groupId = "io.swagger";
|
||||
protected String artifactId = "kotlin-client";
|
||||
protected String artifactVersion = "1.0.0";
|
||||
protected String sourceFolder = "src/main/kotlin";
|
||||
protected String packageName = "io.swagger.client";
|
||||
protected String apiDocPath = "docs/";
|
||||
protected String modelDocPath = "docs/";
|
||||
protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase;
|
||||
|
||||
/**
|
||||
* Constructs an instance of `KotlinClientCodegen`.
|
||||
*/
|
||||
public KotlinClientCodegen() {
|
||||
super();
|
||||
|
||||
artifactId = "kotlin-client";
|
||||
packageName = "io.swagger.client";
|
||||
|
||||
outputFolder = "generated-code" + File.separator + "kotlin-client";
|
||||
modelTemplateFiles.put("model.mustache", ".kt");
|
||||
apiTemplateFiles.put("api.mustache", ".kt");
|
||||
@ -39,151 +28,6 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
|
||||
embeddedTemplateDir = templateDir = "kotlin-client";
|
||||
apiPackage = packageName + ".apis";
|
||||
modelPackage = packageName + ".models";
|
||||
|
||||
languageSpecificPrimitives = new HashSet<String>(Arrays.asList(
|
||||
"kotlin.Byte",
|
||||
"kotlin.Short",
|
||||
"kotlin.Int",
|
||||
"kotlin.Long",
|
||||
"kotlin.Float",
|
||||
"kotlin.Double",
|
||||
"kotlin.Boolean",
|
||||
"kotlin.Char",
|
||||
"kotlin.String",
|
||||
"kotlin.Array",
|
||||
"kotlin.collections.List",
|
||||
"kotlin.collections.Map",
|
||||
"kotlin.collections.Set"
|
||||
));
|
||||
|
||||
// this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java
|
||||
// as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html
|
||||
reservedWords = new HashSet<String>(Arrays.asList(
|
||||
"abstract",
|
||||
"annotation",
|
||||
"as",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"companion",
|
||||
"const",
|
||||
"constructor",
|
||||
"continue",
|
||||
"crossinline",
|
||||
"data",
|
||||
"delegate",
|
||||
"do",
|
||||
"else",
|
||||
"enum",
|
||||
"external",
|
||||
"false",
|
||||
"final",
|
||||
"finally",
|
||||
"for",
|
||||
"fun",
|
||||
"if",
|
||||
"in",
|
||||
"infix",
|
||||
"init",
|
||||
"inline",
|
||||
"inner",
|
||||
"interface",
|
||||
"internal",
|
||||
"is",
|
||||
"it",
|
||||
"lateinit",
|
||||
"lazy",
|
||||
"noinline",
|
||||
"null",
|
||||
"object",
|
||||
"open",
|
||||
"operator",
|
||||
"out",
|
||||
"override",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"reified",
|
||||
"return",
|
||||
"sealed",
|
||||
"super",
|
||||
"suspend",
|
||||
"tailrec",
|
||||
"this",
|
||||
"throw",
|
||||
"true",
|
||||
"try",
|
||||
"typealias",
|
||||
"typeof",
|
||||
"val",
|
||||
"var",
|
||||
"vararg",
|
||||
"when",
|
||||
"while"
|
||||
));
|
||||
|
||||
defaultIncludes = new HashSet<String>(Arrays.asList(
|
||||
"kotlin.Byte",
|
||||
"kotlin.Short",
|
||||
"kotlin.Int",
|
||||
"kotlin.Long",
|
||||
"kotlin.Float",
|
||||
"kotlin.Double",
|
||||
"kotlin.Boolean",
|
||||
"kotlin.Char",
|
||||
"kotlin.Array",
|
||||
"kotlin.collections.List",
|
||||
"kotlin.collections.Set",
|
||||
"kotlin.collections.Map"
|
||||
));
|
||||
|
||||
typeMapping = new HashMap<String, String>();
|
||||
typeMapping.put("string", "kotlin.String");
|
||||
typeMapping.put("boolean", "kotlin.Boolean");
|
||||
typeMapping.put("integer", "kotlin.Int");
|
||||
typeMapping.put("float", "kotlin.Float");
|
||||
typeMapping.put("long", "kotlin.Long");
|
||||
typeMapping.put("double", "kotlin.Double");
|
||||
typeMapping.put("number", "java.math.BigDecimal");
|
||||
typeMapping.put("date-time", "java.time.LocalDateTime");
|
||||
typeMapping.put("date", "java.time.LocalDateTime");
|
||||
typeMapping.put("file", "java.io.File");
|
||||
typeMapping.put("array", "kotlin.Array");
|
||||
typeMapping.put("list", "kotlin.Array");
|
||||
typeMapping.put("map", "kotlin.collections.Map");
|
||||
typeMapping.put("object", "kotlin.Any");
|
||||
typeMapping.put("binary", "kotlin.Array<kotlin.Byte>");
|
||||
typeMapping.put("Date", "java.time.LocalDateTime");
|
||||
typeMapping.put("DateTime", "java.time.LocalDateTime");
|
||||
|
||||
instantiationTypes.put("array", "arrayOf");
|
||||
instantiationTypes.put("list", "arrayOf");
|
||||
instantiationTypes.put("map", "mapOf");
|
||||
|
||||
importMapping = new HashMap<String, String>();
|
||||
importMapping.put("BigDecimal", "java.math.BigDecimal");
|
||||
importMapping.put("UUID", "java.util.UUID");
|
||||
importMapping.put("File", "java.io.File");
|
||||
importMapping.put("Date", "java.util.Date");
|
||||
importMapping.put("Timestamp", "java.sql.Timestamp");
|
||||
importMapping.put("DateTime", "java.time.LocalDateTime");
|
||||
importMapping.put("LocalDateTime", "java.time.LocalDateTime");
|
||||
importMapping.put("LocalDate", "java.time.LocalDate");
|
||||
importMapping.put("LocalTime", "java.time.LocalTime");
|
||||
|
||||
specialCharReplacements.put(";", "Semicolon");
|
||||
|
||||
cliOptions.clear();
|
||||
cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue(sourceFolder));
|
||||
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Client package name (e.g. io.swagger).").defaultValue(this.packageName));
|
||||
cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, "Client package's organization (i.e. maven groupId).").defaultValue(groupId));
|
||||
cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, "Client artifact id (name of generated jar).").defaultValue(artifactId));
|
||||
cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "Client package version.").defaultValue(artifactVersion));
|
||||
|
||||
CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC);
|
||||
cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name()));
|
||||
}
|
||||
|
||||
public CodegenType getTag() {
|
||||
@ -198,78 +42,10 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
|
||||
return "Generates a kotlin client.";
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactVersion(String artifactVersion) {
|
||||
this.artifactVersion = artifactVersion;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public void setSourceFolder(String sourceFolder) {
|
||||
this.sourceFolder = sourceFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) {
|
||||
setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING));
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
|
||||
this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
|
||||
this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
|
||||
if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE))
|
||||
this.setModelPackage(packageName + ".models");
|
||||
if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE))
|
||||
this.setApiPackage(packageName + ".apis");
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
|
||||
}
|
||||
|
||||
if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) {
|
||||
this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
|
||||
}
|
||||
|
||||
if(additionalProperties.containsKey(CodegenConstants.GROUP_ID)) {
|
||||
this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
|
||||
}
|
||||
|
||||
if(additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) {
|
||||
this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION));
|
||||
} else {
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
|
||||
LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + ".");
|
||||
}
|
||||
|
||||
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage());
|
||||
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage());
|
||||
|
||||
additionalProperties.put("apiDocPath", apiDocPath);
|
||||
additionalProperties.put("modelDocPath", modelDocPath);
|
||||
|
||||
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
|
||||
|
||||
supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
|
||||
@ -287,251 +63,4 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
|
||||
supportingFiles.add(new SupportingFile("infrastructure/Serializer.kt.mustache", infrastructureFolder, "Serializer.kt"));
|
||||
supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the naming convention for Kotlin enum properties
|
||||
*
|
||||
* @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE}
|
||||
*/
|
||||
public void setEnumPropertyNaming(final String enumPropertyNamingType) {
|
||||
try {
|
||||
this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:");
|
||||
for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) {
|
||||
sb.append("\n ").append(t.name());
|
||||
}
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() {
|
||||
return this.enumPropertyNaming;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("*/", "*_/").replace("/*", "/_*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove " to avoid code injection
|
||||
return input.replace("\"", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiDocFileFolder() {
|
||||
return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String modelDocFileFolder() {
|
||||
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String modelFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
// TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping).
|
||||
return String.format("`%s`", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the proper model name (capitalized).
|
||||
* In case the name belongs to the TypeSystem it won't be renamed.
|
||||
*
|
||||
* @param name the name of the model
|
||||
* @return capitalized model name
|
||||
*/
|
||||
@Override
|
||||
public String toModelName(final String name) {
|
||||
// Allow for explicitly configured kotlin.* and java.* types
|
||||
if (name.startsWith("kotlin.") || name.startsWith("java.")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// If importMapping contains name, assume this is a legitimate model name.
|
||||
if (importMapping.containsKey(name)) {
|
||||
return importMapping.get(name);
|
||||
}
|
||||
|
||||
String modifiedName = name.replaceAll("\\.", "");
|
||||
modifiedName = sanitizeKotlinSpecificNames(modifiedName);
|
||||
|
||||
if (reservedWords.contains(modifiedName)) {
|
||||
modifiedName = escapeReservedWord(modifiedName);
|
||||
}
|
||||
|
||||
return titleCase(modifiedName);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the swagger type for the property
|
||||
*
|
||||
* @param p Swagger property object
|
||||
* @return string presentation of the type
|
||||
**/
|
||||
@Override
|
||||
public String getSwaggerType(Property p) {
|
||||
String swaggerType = super.getSwaggerType(p);
|
||||
String type;
|
||||
// This maps, for example, long -> kotlin.Long based on hashes in this type's constructor
|
||||
if (typeMapping.containsKey(swaggerType)) {
|
||||
type = typeMapping.get(swaggerType);
|
||||
if (languageSpecificPrimitives.contains(type)) {
|
||||
return toModelName(type);
|
||||
}
|
||||
} else {
|
||||
type = swaggerType;
|
||||
}
|
||||
return toModelName(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the type declaration of the property
|
||||
*
|
||||
* @param p Swagger Property object
|
||||
* @return a string presentation of the property type
|
||||
*/
|
||||
@Override
|
||||
public String getTypeDeclaration(Property p) {
|
||||
if (p instanceof ArrayProperty) {
|
||||
ArrayProperty ap = (ArrayProperty) p;
|
||||
Property inner = ap.getItems();
|
||||
return getSwaggerType(p) + "<" + getTypeDeclaration(inner) + ">";
|
||||
} else if (p instanceof MapProperty) {
|
||||
MapProperty mp = (MapProperty) p;
|
||||
Property inner = mp.getAdditionalProperties();
|
||||
|
||||
// Maps will be keyed only by primitive Kotlin string
|
||||
return getSwaggerType(p) + "<kotlin.String, " + getTypeDeclaration(inner) + ">";
|
||||
}
|
||||
return super.getTypeDeclaration(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the type to see if it needs import the library/module/package
|
||||
*
|
||||
* @param type name of the type
|
||||
* @return true if the library/module/package of the corresponding type needs to be imported
|
||||
*/
|
||||
@Override
|
||||
protected boolean needToImport(String type) {
|
||||
// provides extra protection against improperly trying to import language primitives and java types
|
||||
boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type);
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully-qualified "Model" name for import
|
||||
*
|
||||
* @param name the name of the "Model"
|
||||
* @return the fully-qualified "Model" name for import
|
||||
*/
|
||||
@Override
|
||||
public String toModelImport(String name) {
|
||||
// toModelImport is called while processing operations, but DefaultCodegen doesn't
|
||||
// define imports correctly with fully qualified primitives and models as defined in this generator.
|
||||
if(needToImport(name)) {
|
||||
return super.toModelImport(name);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
|
||||
return postProcessModelsEnum(super.postProcessModels(objs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sanitized variable name for enum
|
||||
*
|
||||
* @param value enum variable name
|
||||
* @param datatype data type
|
||||
* @return the sanitized variable name for enum
|
||||
*/
|
||||
@Override
|
||||
public String toEnumVarName(String value, String datatype) {
|
||||
String modified;
|
||||
if (value.length() == 0) {
|
||||
modified = "EMPTY";
|
||||
} else {
|
||||
modified = value;
|
||||
modified = sanitizeKotlinSpecificNames(modified);
|
||||
}
|
||||
|
||||
switch (getEnumPropertyNaming()) {
|
||||
case original:
|
||||
// NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped.
|
||||
modified = value;
|
||||
break;
|
||||
case camelCase:
|
||||
// NOTE: Removes hyphens and underscores
|
||||
modified = camelize(modified, true);
|
||||
break;
|
||||
case PascalCase:
|
||||
// NOTE: Removes hyphens and underscores
|
||||
String result = camelize(modified);
|
||||
modified = titleCase(result);
|
||||
break;
|
||||
case snake_case:
|
||||
// NOTE: Removes hyphens
|
||||
modified = underscore(modified);
|
||||
break;
|
||||
case UPPERCASE:
|
||||
modified = modified.toUpperCase();
|
||||
break;
|
||||
}
|
||||
|
||||
if (reservedWords.contains(modified)) {
|
||||
return escapeReservedWord(modified);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
private String titleCase(final String input) {
|
||||
return input.substring(0, 1).toUpperCase() + input.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}.
|
||||
*
|
||||
* @param name string to be sanitize
|
||||
* @return sanitized string
|
||||
*/
|
||||
private String sanitizeKotlinSpecificNames(final String name) {
|
||||
String word = name;
|
||||
for (Map.Entry<String, String> specialCharacters : specialCharReplacements.entrySet()) {
|
||||
// Underscore is the only special character we'll allow
|
||||
if (!specialCharacters.getKey().equals("_")) {
|
||||
word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback, replace unknowns with underscore.
|
||||
word = word.replaceAll("\\W+", "_");
|
||||
if (word.matches("\\d.*")) {
|
||||
word = "_" + word;
|
||||
}
|
||||
|
||||
// _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count.
|
||||
if (word.matches("^_*$")) {
|
||||
word = word.replaceAll("\\Q_\\E", "Underscore");
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,226 @@
|
||||
package io.swagger.codegen.languages;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;
|
||||
import io.swagger.codegen.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
import io.swagger.codegen.mustache.IndentedLambda;
|
||||
import io.swagger.codegen.mustache.LowercaseLambda;
|
||||
import io.swagger.codegen.mustache.TitlecaseLambda;
|
||||
import io.swagger.codegen.mustache.UppercaseLambda;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class KotlinServerCodegen extends AbstractKotlinCodegen {
|
||||
|
||||
public static final String DEFAULT_LIBRARY = Constants.KTOR;
|
||||
static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class);
|
||||
private Boolean autoHeadFeatureEnabled = true;
|
||||
private Boolean conditionalHeadersFeatureEnabled = false;
|
||||
private Boolean hstsFeatureEnabled = true;
|
||||
private Boolean corsFeatureEnabled = false;
|
||||
private Boolean compressionFeatureEnabled = true;
|
||||
|
||||
// This is here to potentially warn the user when an option is not supoprted by the target framework.
|
||||
private Map<String, List<String>> optionsSupportedPerFramework = new ImmutableMap.Builder<String, List<String>>()
|
||||
.put(Constants.KTOR, Arrays.asList(
|
||||
Constants.AUTOMATIC_HEAD_REQUESTS,
|
||||
Constants.CONDITIONAL_HEADERS,
|
||||
Constants.HSTS,
|
||||
Constants.CORS,
|
||||
Constants.COMPRESSION
|
||||
))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Constructs an instance of `KotlinServerCodegen`.
|
||||
*/
|
||||
public KotlinServerCodegen() {
|
||||
super();
|
||||
|
||||
artifactId = "kotlin-server";
|
||||
packageName = "io.swagger.server";
|
||||
outputFolder = "generated-code" + File.separator + "kotlin-server";
|
||||
modelTemplateFiles.put("model.mustache", ".kt");
|
||||
apiTemplateFiles.put("api.mustache", ".kt");
|
||||
embeddedTemplateDir = templateDir = "kotlin-server";
|
||||
apiPackage = packageName + ".apis";
|
||||
modelPackage = packageName + ".models";
|
||||
|
||||
supportedLibraries.put("ktor", "ktor framework");
|
||||
|
||||
// TODO: Configurable server engine. Defaults to netty in build.gradle.
|
||||
CliOption library = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use");
|
||||
library.setDefault(DEFAULT_LIBRARY);
|
||||
library.setEnum(supportedLibraries);
|
||||
|
||||
cliOptions.add(library);
|
||||
|
||||
addSwitch(Constants.AUTOMATIC_HEAD_REQUESTS, Constants.AUTOMATIC_HEAD_REQUESTS_DESC, getAutoHeadFeatureEnabled());
|
||||
addSwitch(Constants.CONDITIONAL_HEADERS, Constants.CONDITIONAL_HEADERS_DESC, getConditionalHeadersFeatureEnabled());
|
||||
addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled());
|
||||
addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled());
|
||||
addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled());
|
||||
}
|
||||
|
||||
public Boolean getAutoHeadFeatureEnabled() {
|
||||
return autoHeadFeatureEnabled;
|
||||
}
|
||||
|
||||
public void setAutoHeadFeatureEnabled(Boolean autoHeadFeatureEnabled) {
|
||||
this.autoHeadFeatureEnabled = autoHeadFeatureEnabled;
|
||||
}
|
||||
|
||||
public Boolean getCompressionFeatureEnabled() {
|
||||
return compressionFeatureEnabled;
|
||||
}
|
||||
|
||||
public void setCompressionFeatureEnabled(Boolean compressionFeatureEnabled) {
|
||||
this.compressionFeatureEnabled = compressionFeatureEnabled;
|
||||
}
|
||||
|
||||
public Boolean getConditionalHeadersFeatureEnabled() {
|
||||
return conditionalHeadersFeatureEnabled;
|
||||
}
|
||||
|
||||
public void setConditionalHeadersFeatureEnabled(Boolean conditionalHeadersFeatureEnabled) {
|
||||
this.conditionalHeadersFeatureEnabled = conditionalHeadersFeatureEnabled;
|
||||
}
|
||||
|
||||
public Boolean getCorsFeatureEnabled() {
|
||||
return corsFeatureEnabled;
|
||||
}
|
||||
|
||||
public void setCorsFeatureEnabled(Boolean corsFeatureEnabled) {
|
||||
this.corsFeatureEnabled = corsFeatureEnabled;
|
||||
}
|
||||
|
||||
public String getHelp() {
|
||||
return "Generates a kotlin server.";
|
||||
}
|
||||
|
||||
public Boolean getHstsFeatureEnabled() {
|
||||
return hstsFeatureEnabled;
|
||||
}
|
||||
|
||||
public void setHstsFeatureEnabled(Boolean hstsFeatureEnabled) {
|
||||
this.hstsFeatureEnabled = hstsFeatureEnabled;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "kotlin-server";
|
||||
}
|
||||
|
||||
public CodegenType getTag() {
|
||||
return CodegenType.SERVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
|
||||
if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) {
|
||||
this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY));
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) {
|
||||
setAutoHeadFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.AUTOMATIC_HEAD_REQUESTS));
|
||||
} else {
|
||||
additionalProperties.put(Constants.AUTOMATIC_HEAD_REQUESTS, getAutoHeadFeatureEnabled());
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(Constants.CONDITIONAL_HEADERS)) {
|
||||
setConditionalHeadersFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CONDITIONAL_HEADERS));
|
||||
} else {
|
||||
additionalProperties.put(Constants.CONDITIONAL_HEADERS, getConditionalHeadersFeatureEnabled());
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(Constants.HSTS)) {
|
||||
setHstsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.HSTS));
|
||||
} else {
|
||||
additionalProperties.put(Constants.HSTS, getHstsFeatureEnabled());
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(Constants.CORS)) {
|
||||
setCorsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CORS));
|
||||
} else {
|
||||
additionalProperties.put(Constants.CORS, getCorsFeatureEnabled());
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(Constants.COMPRESSION)) {
|
||||
setCompressionFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.COMPRESSION));
|
||||
} else {
|
||||
additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled());
|
||||
}
|
||||
|
||||
Boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean)additionalProperties.get(CodegenConstants.GENERATE_APIS);
|
||||
String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator);
|
||||
String resourcesFolder = "src/main/resources"; // not sure this can be user configurable.
|
||||
|
||||
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
|
||||
supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile"));
|
||||
|
||||
supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
|
||||
supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle"));
|
||||
supportingFiles.add(new SupportingFile("gradle.properties", "", "gradle.properties"));
|
||||
|
||||
supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt"));
|
||||
supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt"));
|
||||
|
||||
if (generateApis) {
|
||||
supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt"));
|
||||
}
|
||||
|
||||
supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf"));
|
||||
supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml"));
|
||||
|
||||
final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", File.separator);
|
||||
|
||||
supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt"));
|
||||
|
||||
addMustacheLambdas(additionalProperties);
|
||||
}
|
||||
|
||||
private void addMustacheLambdas(Map<String, Object> objs) {
|
||||
|
||||
Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
|
||||
.put("lowercase", new LowercaseLambda())
|
||||
.put("uppercase", new UppercaseLambda())
|
||||
.put("titlecase", new TitlecaseLambda())
|
||||
.put("indented", new IndentedLambda())
|
||||
.put("indented_8", new IndentedLambda(8, " "))
|
||||
.put("indented_12", new IndentedLambda(12, " "))
|
||||
.put("indented_16", new IndentedLambda(16, " "))
|
||||
.build();
|
||||
|
||||
if (objs.containsKey("lambda")) {
|
||||
LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " +
|
||||
"You'll likely need to use a custom template, " +
|
||||
"see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. ");
|
||||
objs.put("_lambda", lambdas);
|
||||
} else {
|
||||
objs.put("lambda", lambdas);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Constants {
|
||||
public final static String KTOR = "ktor";
|
||||
public final static String AUTOMATIC_HEAD_REQUESTS = "featureAutoHead";
|
||||
public final static String AUTOMATIC_HEAD_REQUESTS_DESC = "Automatically provide responses to HEAD requests for existing routes that have the GET verb defined.";
|
||||
public final static String CONDITIONAL_HEADERS = "featureConditionalHeaders";
|
||||
public final static String CONDITIONAL_HEADERS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties.";
|
||||
public final static String HSTS = "featureHSTS";
|
||||
public final static String HSTS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties.";
|
||||
public final static String CORS = "featureCORS";
|
||||
public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.";
|
||||
public final static String COMPRESSION = "featureCompression";
|
||||
public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.";
|
||||
}
|
||||
}
|
@ -196,7 +196,7 @@ public class ScalaGatlingCodegen extends AbstractScalaCodegen implements Codegen
|
||||
/**
|
||||
* Modifies the swagger doc to make mustache easier to use
|
||||
*
|
||||
* @param swagger
|
||||
* @param swagger input swagger document
|
||||
*/
|
||||
@Override
|
||||
public void preprocessSwagger(Swagger swagger) {
|
||||
|
@ -74,7 +74,7 @@ public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig
|
||||
|
||||
/**
|
||||
* Convert Markdown (CommonMark) to HTML. This class also disables normal HTML
|
||||
* escaping in the Mustache engine (see {@link #processCompiler(Compiler)} above.)
|
||||
* escaping in the Mustache engine (see {@link DefaultCodegen#processCompiler(Compiler)} above.)
|
||||
*/
|
||||
@Override
|
||||
public String escapeText(String input) {
|
||||
|
@ -0,0 +1,93 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* This naively prepends indention to all lines of a fragment.
|
||||
* <p>
|
||||
* Generator authors may add helpers for explicitly adding prefixed spaces which fragments won't be aware of.
|
||||
* <p>
|
||||
* Register:
|
||||
* <pre>
|
||||
* additionalProperties.put("indent4", new IndentedLambda(4));
|
||||
* additionalProperties.put("indent8", new IndentedLambda(8));
|
||||
* </pre>
|
||||
* <p>
|
||||
* Use:
|
||||
* <pre>{@code
|
||||
* {{#indent4}}{{>template}}{{/indent4}}
|
||||
* {{#indent8}}{{>other_template}}{{/indent8}}
|
||||
* }</pre>
|
||||
*/
|
||||
public class IndentedLambda implements Mustache.Lambda {
|
||||
private final int prefixSpaceCount;
|
||||
private int spaceCode;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link IndentedLambda}, with an indent count of 4 spaces
|
||||
*/
|
||||
public IndentedLambda() {
|
||||
this(4, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link IndentedLambda}, with customized indent count and intention character
|
||||
*
|
||||
* @param prefixSpaceCount The number of indented characters to apply as a prefix to a fragment.
|
||||
* @param indentionCharacter String representation of the character used in the indent (e.g. " ", "\t", ".").
|
||||
*/
|
||||
public IndentedLambda(int prefixSpaceCount, String indentionCharacter) {
|
||||
this(prefixSpaceCount, Character.codePointAt(indentionCharacter, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link IndentedLambda}
|
||||
*
|
||||
* @param prefixSpaceCount The number of indented characters to apply as a prefix to a fragment.
|
||||
*/
|
||||
private IndentedLambda(int prefixSpaceCount, int indentionCodePoint) {
|
||||
if (prefixSpaceCount <= 0) {
|
||||
throw new IllegalArgumentException("prefixSpaceCount must be greater than 0");
|
||||
}
|
||||
|
||||
if (!Character.isValidCodePoint(indentionCodePoint)) {
|
||||
throw new IllegalArgumentException("indentionCodePoint is an invalid code point ");
|
||||
}
|
||||
|
||||
this.prefixSpaceCount = prefixSpaceCount;
|
||||
this.spaceCode = indentionCodePoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||
String text = fragment.execute();
|
||||
if (text == null || text.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String prefixedIndention = StringUtils.repeat(new String(Character.toChars(spaceCode)), prefixSpaceCount);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String[] lines = text.split(System.lineSeparator());
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
// Mustache will apply correct indentation to the first line of a template (to match declaration location).
|
||||
// So, we want to skip the first line.
|
||||
if (i > 0) {
|
||||
sb.append(prefixedIndention);
|
||||
}
|
||||
|
||||
sb.append(line);
|
||||
|
||||
// We've split on the system's line separator. We don't want to add an additional trailing line.
|
||||
if (i < lines.length - 1) {
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
}
|
||||
writer.write(sb.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Converts text in a fragment to lowercase.
|
||||
*
|
||||
* Register:
|
||||
* <pre>
|
||||
* additionalProperties.put("lowercase", new LowercaseLambda());
|
||||
* </pre>
|
||||
*
|
||||
* Use:
|
||||
* <pre>
|
||||
* {{#lowercase}}{{httpMethod}}{{/lowercase}}
|
||||
* </pre>
|
||||
*/
|
||||
public class LowercaseLambda implements Mustache.Lambda {
|
||||
@Override
|
||||
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||
String text = fragment.execute();
|
||||
writer.write(text.toLowerCase());
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Converts text in a fragment to title case.
|
||||
*
|
||||
* Register:
|
||||
* <pre>
|
||||
* additionalProperties.put("titlecase", new TitlecaseLambda());
|
||||
* </pre>
|
||||
*
|
||||
* Use:
|
||||
* <pre>
|
||||
* {{#titlecase}}{{classname}}{{/titlecase}}
|
||||
* </pre>
|
||||
*/
|
||||
public class TitlecaseLambda implements Mustache.Lambda {
|
||||
private String delimiter;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TitlecaseLambda}, which will convert all text
|
||||
* in a space delimited string to title-case.
|
||||
*/
|
||||
public TitlecaseLambda() {
|
||||
this(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TitlecaseLambda}, splitting on the specified
|
||||
* delimiter and converting each word to title-case.
|
||||
*
|
||||
* NOTE: passing {@code null} results in a title-casing the first word only.
|
||||
*
|
||||
* @param delimiter Provided to allow an override for the default space delimiter.
|
||||
*/
|
||||
public TitlecaseLambda(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
private String titleCase(final String input) {
|
||||
return input.substring(0, 1).toUpperCase() + input.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||
String text = fragment.execute();
|
||||
if (delimiter == null) {
|
||||
writer.write(titleCase(text));
|
||||
return;
|
||||
}
|
||||
|
||||
// Split accepts regex. \Q and \E wrap the delimiter to create a literal regex,
|
||||
// so things like "." and "|" aren't treated as their regex equivalents.
|
||||
String[] parts = text.split("\\Q" + delimiter + "\\E");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String part = parts[i];
|
||||
writer.write(titleCase(part));
|
||||
if (i != parts.length - 1) {
|
||||
writer.write(delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Converts text in a fragment to uppercase.
|
||||
*
|
||||
* Register:
|
||||
* <pre>
|
||||
* additionalProperties.put("uppercase", new UppercaseLambda());
|
||||
* </pre>
|
||||
*
|
||||
* Use:
|
||||
* <pre>
|
||||
* {{#uppercase}}{{summary}}{{/uppercase}}
|
||||
* </pre>
|
||||
*/
|
||||
public class UppercaseLambda implements Mustache.Lambda {
|
||||
@Override
|
||||
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||
String text = fragment.execute();
|
||||
writer.write(text.toUpperCase());
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ public class Markdown {
|
||||
/**
|
||||
* Convert input markdown text to HTML.
|
||||
* Simple text is not wrapped in <p>...</p>.
|
||||
* @param markdown text with Markdown styles. If <code>null<code>, </code>""</code> is returned.
|
||||
* @param markdown text with Markdown styles. If <code>null</code>, <code>""</code> is returned.
|
||||
* @return HTML rendering from the Markdown
|
||||
*/
|
||||
public String toHtml(String markdown) {
|
||||
|
@ -87,3 +87,4 @@ io.swagger.codegen.languages.TypeScriptJqueryClientCodegen
|
||||
io.swagger.codegen.languages.TypeScriptNodeClientCodegen
|
||||
io.swagger.codegen.languages.UndertowCodegen
|
||||
io.swagger.codegen.languages.ZendExpressivePathHandlerServerCodegen
|
||||
io.swagger.codegen.languages.KotlinServerCodegen
|
||||
|
@ -0,0 +1,84 @@
|
||||
# {{packageName}} - Kotlin Server library for {{appName}}
|
||||
|
||||
## Requires
|
||||
|
||||
* Kotlin 1.1.2
|
||||
* Gradle 3.3
|
||||
|
||||
## Build
|
||||
|
||||
First, create the gradle wrapper script:
|
||||
|
||||
```
|
||||
gradle wrapper
|
||||
```
|
||||
|
||||
Then, run:
|
||||
|
||||
```
|
||||
./gradlew check assemble
|
||||
```
|
||||
|
||||
This runs all tests and packages the library.
|
||||
|
||||
## Features/Implementation Notes
|
||||
|
||||
* Supports JSON inputs/outputs, File inputs, and Form inputs.
|
||||
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
|
||||
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions.
|
||||
|
||||
{{#generateApiDocs}}
|
||||
<a name="documentation-for-api-endpoints"></a>
|
||||
## 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}}
|
||||
{{/generateApiDocs}}
|
||||
|
||||
{{#generateModelDocs}}
|
||||
<a name="documentation-for-models"></a>
|
||||
## Documentation for Models
|
||||
|
||||
{{#modelPackage}}
|
||||
{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
|
||||
{{/model}}{{/models}}
|
||||
{{/modelPackage}}
|
||||
{{^modelPackage}}
|
||||
No model defined in this package
|
||||
{{/modelPackage}}
|
||||
{{/generateModelDocs}}
|
||||
|
||||
<a name="documentation-for-authorization"></a>{{! TODO: optional documentation for authorization? }}
|
||||
## Documentation for Authorization
|
||||
|
||||
{{^authMethods}}
|
||||
All endpoints do not require authorization.
|
||||
{{/authMethods}}
|
||||
{{#authMethods}}
|
||||
{{#last}}
|
||||
Authentication schemes defined for the API:
|
||||
{{/last}}
|
||||
{{/authMethods}}
|
||||
{{#authMethods}}
|
||||
<a name="{{name}}"></a>
|
||||
### {{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}}
|
@ -0,0 +1,65 @@
|
||||
# {{classname}}{{#description}}
|
||||
{{description}}{{/description}}
|
||||
|
||||
All URIs are relative to *{{basePath}}*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
|
||||
{{/operation}}{{/operations}}
|
||||
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
<a name="{{operationId}}"></a>
|
||||
# **{{operationId}}**
|
||||
> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
|
||||
|
||||
{{summary}}{{#notes}}
|
||||
|
||||
{{notes}}{{/notes}}
|
||||
|
||||
### Example
|
||||
```kotlin
|
||||
// Import classes:
|
||||
//import {{{packageName}}}.infrastructure.*
|
||||
//import {{{modelPackage}}}.*
|
||||
|
||||
{{! TODO: Auth method documentation examples}}
|
||||
val apiInstance = {{{classname}}}()
|
||||
{{#allParams}}
|
||||
val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}}
|
||||
{{/allParams}}
|
||||
try {
|
||||
{{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}
|
||||
println(result){{/returnType}}
|
||||
} catch (e: ClientException) {
|
||||
println("4xx response calling {{{classname}}}#{{{operationId}}}")
|
||||
e.printStackTrace()
|
||||
} catch (e: ServerException) {
|
||||
println("5xx response calling {{{classname}}}#{{{operationId}}}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
|
||||
{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}}
|
||||
{{/allParams}}
|
||||
|
||||
### Return type
|
||||
|
||||
{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}{{#generateModelDocs}}[**{{returnType}}**]({{returnBaseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{returnType}}**{{/generateModelDocs}}{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}}
|
||||
|
||||
### Authorization
|
||||
|
||||
{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}}
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
|
||||
- **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}}
|
||||
|
||||
{{/operation}}
|
||||
{{/operations}}
|
@ -0,0 +1,15 @@
|
||||
# {{classname}}
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}}
|
||||
{{/vars}}
|
||||
{{#vars}}{{#isEnum}}
|
||||
|
||||
<a name="{{{datatypeWithEnum}}}"></a>{{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}}
|
||||
## Enum: {{baseName}}
|
||||
Name | Value
|
||||
---- | -----{{#allowableValues}}
|
||||
{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}
|
||||
{{/isEnum}}{{/vars}}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* {{{description}}}
|
||||
{{#vars}}
|
||||
* @param {{name}} {{{description}}}
|
||||
{{/vars}}
|
||||
*/
|
||||
data class {{classname}} (
|
||||
{{#requiredVars}}
|
||||
{{>data_class_req_var}}{{^-last}},
|
||||
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
|
||||
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}},
|
||||
{{/-last}}{{/optionalVars}}
|
||||
) {
|
||||
{{#hasEnums}}{{#vars}}{{#isEnum}}
|
||||
/**
|
||||
* {{{description}}}
|
||||
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
|
||||
*/
|
||||
enum class {{nameInCamelCase}}(val value: {{dataType}}){
|
||||
{{#allowableValues}}{{#enumVars}}
|
||||
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
|
||||
{{/enumVars}}{{/allowableValues}}
|
||||
}
|
||||
{{/isEnum}}{{/vars}}{{/hasEnums}}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{{#description}}
|
||||
/* {{{description}}} */
|
||||
{{/description}}
|
||||
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
|
@ -0,0 +1,4 @@
|
||||
{{#description}}
|
||||
/* {{{description}}} */
|
||||
{{/description}}
|
||||
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* {{{description}}}
|
||||
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
|
||||
*/
|
||||
enum class {{classname}}(val value: {{dataType}}){
|
||||
{{#allowableValues}}{{#enumVars}}
|
||||
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
|
||||
{{/enumVars}}{{/allowableValues}}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
# {{classname}}
|
||||
|
||||
## Enum
|
||||
|
||||
{{#allowableValues}}{{#enumVars}}
|
||||
* `{{name}}` (value: `{{{value}}}`)
|
||||
{{/enumVars}}{{/allowableValues}}
|
@ -0,0 +1,57 @@
|
||||
package {{packageName}}.infrastructure
|
||||
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.request.ApplicationRequest
|
||||
import io.ktor.response.respond
|
||||
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.pipeline.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import java.util.*
|
||||
|
||||
enum class ApiKeyLocation(val location: String) {
|
||||
QUERY("query"),
|
||||
HEADER("header")
|
||||
}
|
||||
data class ApiKey(val value: String): Credential
|
||||
data class ApiPrincipal(val apiKey: ApiKey?) : Principal
|
||||
fun ApplicationCall.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? = request.apiKey(key, keyLocation)
|
||||
fun ApplicationRequest.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? {
|
||||
val value: String? = when(keyLocation) {
|
||||
ApiKeyLocation.QUERY -> this.queryParameters[key]
|
||||
ApiKeyLocation.HEADER -> this.headers[key]
|
||||
}
|
||||
when (value) {
|
||||
null -> return null
|
||||
else -> return ApiKey(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String, validate: suspend (ApiKey) -> ApiPrincipal?) {
|
||||
intercept(AuthenticationPipeline.RequestAuthentication) { context ->
|
||||
val credentials = call.request.apiKey(apiKeyName, ApiKeyLocation.values().first { it.location == authLocation })
|
||||
val principal = credentials?.let { validate(it) }
|
||||
|
||||
val cause = when {
|
||||
credentials == null -> AuthenticationFailedCause.NoCredentials
|
||||
principal == null -> AuthenticationFailedCause.InvalidCredentials
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (cause != null) {
|
||||
context.challenge(apiKeyName, cause) {
|
||||
// TODO: Verify correct response structure here.
|
||||
call.respond(UnauthorizedResponse(HttpAuthHeader.Parameterized("API_KEY", mapOf("key" to apiKeyName), HeaderValueEncoding.QUOTED_ALWAYS)))
|
||||
it.complete()
|
||||
}
|
||||
}
|
||||
if (principal != null) {
|
||||
context.principal(principal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package {{packageName}}
|
||||
|
||||
import com.codahale.metrics.*
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import io.ktor.application.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.apache.Apache
|
||||
import io.ktor.config.HoconApplicationConfig
|
||||
import io.ktor.features.*
|
||||
import io.ktor.gson.GsonConverter
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.metrics.*
|
||||
import io.ktor.routing.*
|
||||
import java.util.concurrent.*
|
||||
{{#generateApis}}
|
||||
import {{apiPackage}}.*
|
||||
{{/generateApis}}
|
||||
|
||||
{{#imports}}import {{import}}
|
||||
{{/imports}}
|
||||
|
||||
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
|
||||
|
||||
object HTTP {
|
||||
val client = HttpClient(Apache)
|
||||
}
|
||||
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(Metrics) {
|
||||
val reporter = Slf4jReporter.forRegistry(registry)
|
||||
.outputTo(log)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
reporter.start(10, TimeUnit.SECONDS)
|
||||
}
|
||||
{{#generateApis}}
|
||||
install(ContentNegotiation) {
|
||||
register(ContentType.Application.Json, GsonConverter())
|
||||
}
|
||||
{{#featureAutoHead}}
|
||||
install(AutoHeadResponse) // see http://ktor.io/features/autoheadresponse.html
|
||||
{{/featureAutoHead}}
|
||||
{{#featureConditionalHeaders}}
|
||||
install(ConditionalHeaders) // see http://ktor.io/features/conditional-headers.html
|
||||
{{/featureConditionalHeaders}}
|
||||
{{#featureHSTS}}
|
||||
install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html
|
||||
{{/featureHSTS}}
|
||||
{{#featureCORS}}
|
||||
install(CORS, ApplicationCORSConfiguration()) // see http://ktor.io/features/cors.html
|
||||
{{/featureCORS}}
|
||||
{{#featureCompression}}
|
||||
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
|
||||
{{/featureCompression}}
|
||||
install(Locations) // see http://ktor.io/features/locations.html
|
||||
install(Routing) {
|
||||
{{#apiInfo}}
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{classname}}()
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/apiInfo}}
|
||||
}
|
||||
{{/generateApis}}
|
||||
|
||||
environment.monitor.subscribe(ApplicationStopping)
|
||||
{
|
||||
HTTP.client.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package {{packageName}}
|
||||
|
||||
// Use this file to hold package-level internal functions that return receiver object passed to the `install` method.
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import {{packageName}}.settings
|
||||
|
||||
{{#featureCORS}}
|
||||
/**
|
||||
* Application block for [CORS] configuration.
|
||||
*
|
||||
* This file may be excluded in .swagger-codegen-ignore,
|
||||
* and application specific configuration can be applied in this function.
|
||||
*
|
||||
* See http://ktor.io/features/cors.html
|
||||
*/
|
||||
internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
|
||||
return {
|
||||
// method(HttpMethod.Options)
|
||||
// header(HttpHeaders.XForwardedProto)
|
||||
// anyHost()
|
||||
// host("my-host")
|
||||
// host("my-host:80")
|
||||
// host("my-host", subDomains = listOf("www"))
|
||||
// host("my-host", schemes = listOf("http", "https"))
|
||||
// allowCredentials = true
|
||||
// maxAge = Duration.ofDays(1)
|
||||
}
|
||||
}
|
||||
{{/featureCORS}}
|
||||
|
||||
{{#featureHSTS}}
|
||||
/**
|
||||
* Application block for [HSTS] configuration.
|
||||
*
|
||||
* This file may be excluded in .swagger-codegen-ignore,
|
||||
* and application specific configuration can be applied in this function.
|
||||
*
|
||||
* See http://ktor.io/features/hsts.html
|
||||
*/
|
||||
internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
|
||||
return {
|
||||
maxAge = Duration.ofDays(365)
|
||||
includeSubDomains = true
|
||||
preload = false
|
||||
|
||||
// You may also apply any custom directives supported by specific user-agent. For example:
|
||||
// customDirectives.put("redirectHttpToHttps", "false")
|
||||
}
|
||||
}
|
||||
{{/featureHSTS}}
|
||||
|
||||
{{#featureCompression}}
|
||||
/**
|
||||
* Application block for [Compression] configuration.
|
||||
*
|
||||
* This file may be excluded in .swagger-codegen-ignore,
|
||||
* and application specific configuration can be applied in this function.
|
||||
*
|
||||
* See http://ktor.io/features/compression.html
|
||||
*/
|
||||
internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() -> Unit {
|
||||
return {
|
||||
gzip {
|
||||
priority = 1.0
|
||||
}
|
||||
deflate {
|
||||
priority = 10.0
|
||||
minimumSize(1024) // condition
|
||||
}
|
||||
}
|
||||
}
|
||||
{{/featureCompression}}
|
||||
|
||||
// Defines authentication mechanisms used throughout the application.
|
||||
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
|
||||
{{#authMethods}}
|
||||
{{#isOAuth}}
|
||||
OAuthServerSettings.OAuth2ServerSettings(
|
||||
name = "{{name}}",
|
||||
authorizeUrl = "{{authorizationUrl}}",
|
||||
accessTokenUrl = "{{tokenUrl}}",
|
||||
requestMethod = HttpMethod.Get,
|
||||
{{! TODO: flow, doesn't seem to be supported yet by ktor }}
|
||||
clientId = settings.property("auth.oauth.{{name}}.clientId").getString(),
|
||||
clientSecret = settings.property("auth.oauth.{{name}}.clientSecret").getString(),
|
||||
defaultScopes = listOf({{#scopes}}"{{scope}}"{{#hasMore}}, {{/hasMore}}{{/scopes}})
|
||||
){{#hasMore}},{{/hasMore}}
|
||||
{{/isOAuth}}
|
||||
{{/authMethods}}
|
||||
// OAuthServerSettings.OAuth2ServerSettings(
|
||||
// name = "facebook",
|
||||
// authorizeUrl = "https://graph.facebook.com/oauth/authorize",
|
||||
// accessTokenUrl = "https://graph.facebook.com/oauth/access_token",
|
||||
// requestMethod = HttpMethod.Post,
|
||||
//
|
||||
// clientId = "settings.property("auth.oauth.facebook.clientId").getString()",
|
||||
// clientSecret = "settings.property("auth.oauth.facebook.clientSecret").getString()",
|
||||
// defaultScopes = listOf("public_profile")
|
||||
// )
|
||||
).associateBy { it.name }
|
||||
|
||||
// Provides an application-level fixed thread pool on which to execute coroutines (mainly)
|
||||
internal val ApplicationExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4)
|
@ -0,0 +1,7 @@
|
||||
FROM openjdk:8-jre-alpine
|
||||
|
||||
COPY ./build/libs/{{artifactId}}.jar /root/{{artifactId}}.jar
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "{{artifactId}}.jar"]
|
@ -0,0 +1,43 @@
|
||||
{{>licenseInfo}}
|
||||
package {{packageName}}
|
||||
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.pipeline.PipelineContext
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.method
|
||||
import {{modelPackage}}.*
|
||||
|
||||
{{#imports}}
|
||||
import {{import}}
|
||||
{{/imports}}
|
||||
|
||||
// NOTE: ktor-location@0.9.0 is missing extension for Route.delete. This includes it.
|
||||
inline fun <reified T : Any> Route.delete(noinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit): Route {
|
||||
return location(T::class) {
|
||||
method(HttpMethod.Delete) {
|
||||
handle(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{{#apiInfo}}
|
||||
object Paths {
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
{{^bodyAllowed}}
|
||||
/**
|
||||
* {{summary}}
|
||||
* {{#unescapedNotes}}{{.}}{{/unescapedNotes}}
|
||||
{{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
|
||||
{{/allParams}}*/
|
||||
@Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
|
||||
|
||||
{{/bodyAllowed}}
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
}
|
||||
{{/apiInfo}}
|
@ -0,0 +1,101 @@
|
||||
# {{packageName}} - Kotlin Server library for {{appName}}
|
||||
|
||||
{{#unescapedAppDescription}}
|
||||
{{.}}
|
||||
{{/unescapedAppDescription}}
|
||||
|
||||
Generated by Swagger Codegen {{generatorVersion}} ({{generatedDate}}).
|
||||
|
||||
## Requires
|
||||
|
||||
* Kotlin 1.2.10
|
||||
* Gradle 4.3
|
||||
|
||||
## Build
|
||||
|
||||
First, create the gradle wrapper script:
|
||||
|
||||
```
|
||||
gradle wrapper
|
||||
```
|
||||
|
||||
Then, run:
|
||||
|
||||
```
|
||||
./gradlew check assemble
|
||||
```
|
||||
|
||||
This runs all tests and packages the library.
|
||||
|
||||
## Running
|
||||
|
||||
The server builds as a fat jar with a main entrypoint. To start the service, run `java -jar ./build/libs/{{artifactId}}.jar`.
|
||||
|
||||
You may also run in docker:
|
||||
|
||||
```
|
||||
docker build -t {{artifactId}} .
|
||||
docker run -p 8080:8080 {{artifactId}}
|
||||
```
|
||||
|
||||
## Features/Implementation Notes
|
||||
|
||||
* Supports JSON inputs/outputs, File inputs, and Form inputs (see ktor documentation for more info).
|
||||
* ~Supports collection formats for query parameters: csv, tsv, ssv, pipes.~
|
||||
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions.
|
||||
|
||||
{{#generateApiDocs}}
|
||||
<a name="documentation-for-api-endpoints"></a>
|
||||
## 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}}
|
||||
{{/generateApiDocs}}
|
||||
|
||||
{{#generateModelDocs}}
|
||||
<a name="documentation-for-models"></a>
|
||||
## Documentation for Models
|
||||
|
||||
{{#modelPackage}}
|
||||
{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
|
||||
{{/model}}{{/models}}
|
||||
{{/modelPackage}}
|
||||
{{^modelPackage}}
|
||||
No model defined in this package
|
||||
{{/modelPackage}}
|
||||
{{/generateModelDocs}}
|
||||
|
||||
<a name="documentation-for-authorization"></a>{{! TODO: optional documentation for authorization? }}
|
||||
## Documentation for Authorization
|
||||
|
||||
{{^authMethods}}
|
||||
All endpoints do not require authorization.
|
||||
{{/authMethods}}
|
||||
{{#authMethods}}
|
||||
{{#last}}
|
||||
Authentication schemes defined for the API:
|
||||
{{/last}}
|
||||
{{/authMethods}}
|
||||
{{#authMethods}}
|
||||
<a name="{{name}}"></a>
|
||||
### {{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}}
|
@ -0,0 +1,25 @@
|
||||
{{#hasAuthMethods}}
|
||||
{{>libraries/ktor/_principal}}
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
{{#examples}}
|
||||
{{#-first}}
|
||||
{{#lambda.indented}}{{>_response}}{{/lambda.indented}}
|
||||
{{/-first}}
|
||||
{{/examples}}
|
||||
{{^examples}}
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
{{/examples}}
|
||||
}
|
||||
{{/hasAuthMethods}}
|
||||
{{^hasAuthMethods}}
|
||||
{{#examples}}
|
||||
{{#-first}}
|
||||
{{>libraries/ktor/_response}}
|
||||
{{/-first}}
|
||||
{{/examples}}
|
||||
{{^examples}}
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
{{/examples}}
|
||||
{{/hasAuthMethods}}
|
@ -0,0 +1,11 @@
|
||||
{{#authMethods}}
|
||||
{{#isBasic}}
|
||||
val principal = call.authentication.principal<UserIdPrincipal>()
|
||||
{{/isBasic}}
|
||||
{{#isApiKey}}
|
||||
val principal = call.authentication.principal<ApiPrincipal>()
|
||||
{{/isApiKey}}
|
||||
{{#isOAuth}}
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
{{/isOAuth}}
|
||||
{{/authMethods}}
|
@ -0,0 +1,8 @@
|
||||
val exampleContentType = "{{{contentType}}}"
|
||||
val exampleContentString = """{{&example}}"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
{{>licenseInfo}}
|
||||
package {{apiPackage}}
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.auth.authentication
|
||||
import io.ktor.auth.basicAuthentication
|
||||
import io.ktor.auth.oauth
|
||||
import io.ktor.auth.OAuthAccessTokenResponse
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
|
||||
import {{packageName}}.ApplicationAuthProviders
|
||||
import {{packageName}}.Paths
|
||||
import {{packageName}}.ApplicationExecutors
|
||||
import {{packageName}}.HTTP.client
|
||||
import {{packageName}}.infrastructure.ApiPrincipal
|
||||
import {{packageName}}.infrastructure.apiKeyAuth
|
||||
|
||||
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
|
||||
// see https://github.com/ktorio/ktor/issues/288
|
||||
import {{packageName}}.delete
|
||||
|
||||
{{#imports}}import {{import}}
|
||||
{{/imports}}
|
||||
|
||||
{{#operations}}
|
||||
fun Route.{{classname}}() {
|
||||
val gson = Gson()
|
||||
val empty = mutableMapOf<String, Any?>()
|
||||
{{#operation}}
|
||||
{{#bodyAllowed}}
|
||||
|
||||
route("{{path}}") {
|
||||
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} {
|
||||
{{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}}
|
||||
}
|
||||
}
|
||||
{{/bodyAllowed}}
|
||||
{{^bodyAllowed}}
|
||||
|
||||
{{! NOTE: Locations can be used on routes without body parameters.}}
|
||||
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { it: Paths.{{operationId}} ->
|
||||
{{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}}
|
||||
}
|
||||
{{/bodyAllowed}}
|
||||
{{! THis looks a little weird, but it's completely valid Kotlin code, and simplifies templated route logic above. }}
|
||||
{{#hasAuthMethods}}.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
{{#authMethods}}
|
||||
{{#isBasic}}
|
||||
basicAuthentication("{{{name}}}") { credentials ->
|
||||
// TODO: "Apply your basic authentication functionality."
|
||||
// Accessible in-method via call.principal<UserIdPrincipal>()
|
||||
if (credentials.name == "Swagger" && "Codegen" == credentials.password) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
{{/isBasic}}
|
||||
{{#isApiKey}}
|
||||
// "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'."
|
||||
apiKeyAuth("{{{keyParamName}}}", {{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}) {
|
||||
// TODO: "Verify key here , accessible as it.value"
|
||||
if (it.value == "keyboardcat") {
|
||||
ApiPrincipal(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
{{/isApiKey}}
|
||||
{{#isOAuth}}
|
||||
{{#bodyAllowed}}
|
||||
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["{{{name}}}"] }, {
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
{{/bodyAllowed}}
|
||||
{{^bodyAllowed}}
|
||||
oauthAtLocation<Paths.{{operationId}}>(client, ApplicationExecutors.asCoroutineDispatcher(),
|
||||
providerLookup = { ApplicationAuthProviders["{{{name}}}"] },
|
||||
urlProvider = { currentLocation, provider ->
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
{{/bodyAllowed}}
|
||||
{{/isOAuth}}
|
||||
{{/authMethods}}
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '{{path}}' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
{{/hasAuthMethods}}
|
||||
{{^hasAuthMethods}}
|
||||
|
||||
{{/hasAuthMethods}}
|
||||
{{/operation}}
|
||||
}
|
||||
{{/operations}}
|
@ -0,0 +1,27 @@
|
||||
ktor {
|
||||
deployment {
|
||||
environment = development
|
||||
port = 8080
|
||||
autoreload = true
|
||||
watch = [ {{packageName}} ]
|
||||
}
|
||||
|
||||
application {
|
||||
modules = [ {{packageName}}.AppMainKt.main ]
|
||||
}
|
||||
}
|
||||
|
||||
# Typesafe config allows multiple ways to provide configuration values without hard-coding them here.
|
||||
# Please see https://github.com/lightbend/config for details.
|
||||
auth {
|
||||
oauth {
|
||||
{{#authMethods}}
|
||||
{{#isOAuth}}
|
||||
{{name}} {
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
}
|
||||
{{/isOAuth}}
|
||||
{{/authMethods}}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
group '{{groupId}}'
|
||||
version '{{artifactVersion}}'
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.3'
|
||||
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.10'
|
||||
ext.ktor_version = '0.9.1-alpha-9'
|
||||
ext.shadow_version = '2.0.2'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.github.jengelman.gradle.plugins:shadow:$shadow_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
|
||||
|
||||
// Initialization order with shadow 2.0.1 and Gradle 4.3 is weird.
|
||||
// See https://github.com/johnrengelman/shadow/issues/336#issuecomment-355402508
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
experimental {
|
||||
coroutines "enable"
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
baseName = '{{artifactId}}'
|
||||
classifier = null
|
||||
version = null
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "http://dl.bintray.com/kotlin/ktor" }
|
||||
maven { url "https://dl.bintray.com/kotlin/kotlinx" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "io.ktor:ktor-server-netty:$ktor_version"
|
||||
compile "io.ktor:ktor-metrics:$ktor_version"
|
||||
compile "io.ktor:ktor-locations:$ktor_version"
|
||||
compile "io.ktor:ktor-gson:$ktor_version"
|
||||
compile "io.ktor:ktor-client-core:$ktor_version"
|
||||
compile "io.ktor:ktor-client-apache:$ktor_version"
|
||||
compile "ch.qos.logback:logback-classic:1.2.1"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
@ -0,0 +1 @@
|
||||
org.gradle.caching=true
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* {{{appName}}}
|
||||
* {{{appDescription}}}
|
||||
*
|
||||
* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
|
||||
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
@ -0,0 +1,15 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="trace">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
|
||||
</configuration>
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* {{{appName}}}
|
||||
* {{{appDescription}}}
|
||||
*
|
||||
* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
|
||||
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
@ -0,0 +1,11 @@
|
||||
{{>licenseInfo}}
|
||||
package {{modelPackage}}
|
||||
|
||||
{{#imports}}import {{import}}
|
||||
{{/imports}}
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}}
|
||||
{{/model}}
|
||||
{{/models}}
|
@ -0,0 +1,3 @@
|
||||
{{#models}}{{#model}}
|
||||
{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}}
|
||||
{{/model}}{{/models}}
|
@ -0,0 +1 @@
|
||||
rootProject.name = '{{artifactId}}'
|
@ -0,0 +1,87 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
public class IndentedLambdaTest extends MustacheTestBase {
|
||||
|
||||
@Test(description = "indents by 4 spaces by default")
|
||||
public void testFourSpaceIndent() throws Exception {
|
||||
// Arrange
|
||||
String indent = StringUtils.repeat(" ", 4);
|
||||
String template = " {{#indent_4}}{{value}}{{/indent_4}}";
|
||||
String input = StringUtils.join(
|
||||
Arrays.asList(
|
||||
"line1",
|
||||
"line2",
|
||||
"",
|
||||
"line3"
|
||||
), System.lineSeparator());
|
||||
String expected = StringUtils.join(
|
||||
Arrays.asList(
|
||||
indent + "line1",
|
||||
indent + "line2",
|
||||
indent + "",
|
||||
indent + "line3"
|
||||
), System.lineSeparator());
|
||||
Object ctx = context(
|
||||
"indent_4", new IndentedLambda(),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomCountAndDelim() throws Exception {
|
||||
// Arrange
|
||||
int count = 12;
|
||||
String delim = ".";
|
||||
String indent = StringUtils.repeat(delim, count);
|
||||
String template = indent + "{{#indent_"+count+"}}{{value}}{{/indent_"+count+"}}";
|
||||
String input = StringUtils.join(
|
||||
Arrays.asList(
|
||||
"line1",
|
||||
"line2",
|
||||
"",
|
||||
"line3"
|
||||
), System.lineSeparator());
|
||||
String expected = StringUtils.join(
|
||||
Arrays.asList(
|
||||
indent + "line1",
|
||||
indent + "line2",
|
||||
indent + "",
|
||||
indent + "line3"
|
||||
), System.lineSeparator());
|
||||
Object ctx = context(
|
||||
"indent_" + count, new IndentedLambda(count, delim),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@Test(description = "throws illegal arg for count < 0.",
|
||||
expectedExceptions = { IllegalArgumentException.class },
|
||||
expectedExceptionsMessageRegExp = "prefixSpaceCount must be greater than 0"
|
||||
)
|
||||
public void testRequiresValidCount() throws Exception {
|
||||
// Arrange
|
||||
int count = -1;
|
||||
|
||||
// Act
|
||||
IndentedLambda instance = new IndentedLambda(count, " ");
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
public class LowercaseLambdaTest extends MustacheTestBase {
|
||||
|
||||
@Test(description = "lowercases expected inputs")
|
||||
public void testExecute() throws Exception {
|
||||
// Arrange
|
||||
String template = "{{#lowercase}}{{value}}{{/lowercase}}";
|
||||
Object lowercaseCtx = context(
|
||||
"lowercase", new LowercaseLambda(),
|
||||
"value", "lowercase input"
|
||||
);
|
||||
Object uppercaseCtx = context(
|
||||
"lowercase", new LowercaseLambda(),
|
||||
"value", "UPPERCASE INPUT"
|
||||
);
|
||||
|
||||
// Act
|
||||
String lowercaseResult = compile(template, lowercaseCtx);
|
||||
String uppercaseResult = compile(template, uppercaseCtx);
|
||||
|
||||
|
||||
// Assert
|
||||
assertEquals(lowercaseResult, "lowercase input");
|
||||
assertEquals(uppercaseResult, "uppercase input");
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class MustacheTestBase {
|
||||
protected Object context(Object... data) {
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
if (data.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("context helper accepts pairs of key/value varargs");
|
||||
}
|
||||
for (int i = 0; i < data.length; i += 2) {
|
||||
ctx.put(data[i].toString(), data[i + 1]);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
protected String compile(String template, Object context) {
|
||||
return Mustache.compiler().compile(template).execute(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
public class TitlecaseLambdaTest extends MustacheTestBase {
|
||||
|
||||
private String template = "{{#titlecase}}{{value}}{{/titlecase}}";
|
||||
|
||||
@Test(description = "title cases single word")
|
||||
public void testTitlecase() throws Exception {
|
||||
// Arrange
|
||||
String input = "single";
|
||||
String expected = "Single";
|
||||
Object ctx = context(
|
||||
"titlecase", new TitlecaseLambda(),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@Test(description = "title cases multiple words based on custom delimeter")
|
||||
public void testTitlecaseCustomDelim() throws Exception {
|
||||
// Arrange
|
||||
String input = "one|or|more|words";
|
||||
String expected = "One|Or|More|Words";
|
||||
Object ctx = context(
|
||||
"titlecase", new TitlecaseLambda("|"),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@Test(description = "title cases first word when delim is null")
|
||||
public void testTitlecaseFirstWord() throws Exception {
|
||||
// Arrange
|
||||
String input = "one or more words";
|
||||
String expected = "One or more words";
|
||||
Object ctx = context(
|
||||
"titlecase", new TitlecaseLambda(null),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@Test(description = "title cases multiple words")
|
||||
public void testTitlecaseMultipleWords() throws Exception {
|
||||
// Arrange
|
||||
String input = "one or more words";
|
||||
String expected = "One Or More Words";
|
||||
Object ctx = context(
|
||||
"titlecase", new TitlecaseLambda(),
|
||||
"value", input
|
||||
);
|
||||
|
||||
// Act
|
||||
String actual = compile(template, ctx);
|
||||
|
||||
// Assert
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.swagger.codegen.mustache;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
public class UppercaseLambdaTest extends MustacheTestBase {
|
||||
|
||||
@Test(description = "uppercases expected inputs")
|
||||
public void testExecute() throws Exception {
|
||||
// Arrange
|
||||
String template = "{{#uppercase}}{{value}}{{/uppercase}}";
|
||||
Object lowercaseCtx = context(
|
||||
"uppercase", new UppercaseLambda(),
|
||||
"value", "lowercase input"
|
||||
);
|
||||
Object uppercaseCtx = context(
|
||||
"uppercase", new UppercaseLambda(),
|
||||
"value", "UPPERCASE INPUT"
|
||||
);
|
||||
|
||||
// Act
|
||||
String lowercaseResult = compile(template, lowercaseCtx);
|
||||
String uppercaseResult = compile(template, uppercaseCtx);
|
||||
|
||||
|
||||
// Assert
|
||||
assertEquals(lowercaseResult, "LOWERCASE INPUT");
|
||||
assertEquals(uppercaseResult, "UPPERCASE INPUT");
|
||||
}
|
||||
}
|
87
new.sh
87
new.sh
@ -1,34 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
set -o noclobber
|
||||
|
||||
usage() {
|
||||
echo "Stubs out files for new generators" && \
|
||||
echo "usage:" && \
|
||||
echo "$0 [options]" && \
|
||||
echo " Options:"
|
||||
grep "[[:space:]].)\ #" $0 | tr -d "#" | sed -r 's/( \| \*)//' | sed -r 's/([a-z])\)/-\1/';
|
||||
cat <<EOF
|
||||
Stubs out files for new generators
|
||||
|
||||
Usage:
|
||||
$0 [options]
|
||||
Options:
|
||||
$(grep "[[:space:]].)\ #" $0 | tr -d "#" | sed -E 's/( \| \*)//' | sed -E 's/([a-z])\)/-\1/')
|
||||
|
||||
Examples:
|
||||
Create a server generator for ktor:
|
||||
$0 -n kotlin -s
|
||||
|
||||
Creates:
|
||||
modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinServerCodegen.java
|
||||
modules/swagger-codegen/src/main/resources/kotlin-server/README.md
|
||||
modules/swagger-codegen/src/main/resources/kotlin-server/model.mustache
|
||||
modules/swagger-codegen/src/main/resources/kotlin-server/api.mustache
|
||||
bin/windows/kotlin-server-petstore.bat
|
||||
bin/kotlin-server-petstore.sh
|
||||
|
||||
Create a generic C# server generator:
|
||||
$0 -n csharp -s -t
|
||||
Creates:
|
||||
modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/CsharpServerCodegen.java
|
||||
modules/swagger-codegen/src/main/resources/csharp-server/README.md
|
||||
modules/swagger-codegen/src/main/resources/csharp-server/model.mustache
|
||||
modules/swagger-codegen/src/main/resources/csharp-server/api.mustache
|
||||
bin/windows/csharp-server-petstore.bat
|
||||
bin/csharp-server-petstore.sh
|
||||
modules/swagger-codegen/src/test/java/io/swagger/codegen/csharp/CsharpServerCodegenTest.java
|
||||
modules/swagger-codegen/src/test/java/io/swagger/codegen/csharp/CsharpServerCodegenModelTest.java
|
||||
modules/swagger-codegen/src/test/java/io/swagger/codegen/csharp/CsharpServerCodegenOptionsTest.java
|
||||
modules/swagger-codegen/src/test/java/io/swagger/codegen/options/CsharpServerCodegenOptionsProvider.java
|
||||
EOF
|
||||
exit 0;
|
||||
}
|
||||
|
||||
root=$(cd $(dirname "${BASH_SOURCE}") && pwd)
|
||||
gen_type=client
|
||||
tests=0
|
||||
gen_name=
|
||||
declare os=${OSTYPE//[0-9.]/}
|
||||
declare root=$(cd $(dirname "${BASH_SOURCE}") && pwd)
|
||||
declare gen_type=
|
||||
declare tests=0
|
||||
declare gen_name
|
||||
|
||||
checkPreviousGenType() {
|
||||
if [ "a" != "a$gen_type" ]; then
|
||||
echo "[error] You may only set a single generator type at a time!" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
[ $# -eq 0 ] && usage
|
||||
while getopts ":hcsdtn:" arg; do
|
||||
case ${arg} in
|
||||
n) # Required. Specify generator name.
|
||||
n) # Required. Specify generator name, should be kebab-cased.
|
||||
gen_name=${OPTARG}
|
||||
;;
|
||||
c) # Create a client generator
|
||||
checkPreviousGenType
|
||||
gen_type=client
|
||||
;;
|
||||
s) # Create a server generator
|
||||
checkPreviousGenType
|
||||
gen_type=server
|
||||
;;
|
||||
d) # Create a documentation generator
|
||||
checkPreviousGenType
|
||||
gen_type=documentation
|
||||
;;
|
||||
t) # When specified, creates test file(s) for the generator.
|
||||
@ -43,9 +85,26 @@ done
|
||||
|
||||
[ -z "${gen_name}" ] && usage
|
||||
|
||||
lang_classname=$(echo "${gen_name}-${gen_type}Codegen" | sed -r 's/(^|_|-)([a-z])/\U\2/g')
|
||||
gen_name_camel=$(echo "${gen_name}" | sed -r 's/(^|_|-)([a-z])/\U\2/g' | sed 's/^./\L&/')
|
||||
codegen_type_enum=$(echo "${gen_type}" | sed -r 's/(.)/\U\1/g')
|
||||
titleCase() {
|
||||
if [ "$os" == "darwin" ]; then
|
||||
echo $1 | tr '-' ' ' | tr '_' ' ' | ruby -e "print STDIN.gets.split.map(&:capitalize).join(' ')" | tr -d ' '
|
||||
else
|
||||
read -ra words <<< $(echo $1 | tr '-' ' ' | tr '_' ' ')
|
||||
echo "${words[@]^}" | tr -d ' '
|
||||
fi
|
||||
}
|
||||
|
||||
kebabCase() {
|
||||
echo $1 | tr '-' ' ' | tr '_' ' ' | tr '[:upper:]' '[:lower:]' | tr ' ' '-'
|
||||
}
|
||||
|
||||
upperCase() {
|
||||
echo $1 | tr '[[:lower:]]' '[[:upper:]]'
|
||||
}
|
||||
|
||||
declare lang_classname=$(titleCase "${gen_name}-${gen_type}-Codegen")
|
||||
declare gen_name_camel=$(kebabCase "${gen_name}")
|
||||
declare codegen_type_enum=$(upperCase "${gen_type}")
|
||||
|
||||
# Step 1: Add Language Generator
|
||||
[ -f "${root}/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/${lang_classname}.java" ] && \
|
||||
@ -109,7 +168,7 @@ echo "Creating modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type
|
||||
touch "${root}/modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/README.md"
|
||||
echo "Creating modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/model.mustache" && \
|
||||
touch "${root}/modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/model.mustache"
|
||||
echo "Creating /modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/api.mustache" && \
|
||||
echo "Creating modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/api.mustache" && \
|
||||
touch "${root}/modules/swagger-codegen/src/main/resources/${gen_name}-${gen_type}/api.mustache"
|
||||
|
||||
# Step 4: Create bash/batch scripts
|
||||
|
4
samples/server/petstore/kotlin-server/ktor/.gitignore
vendored
Normal file
4
samples/server/petstore/kotlin-server/ktor/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build/
|
||||
gradle/
|
||||
gradlew
|
||||
gradlew.bat
|
@ -0,0 +1,23 @@
|
||||
# Swagger Codegen Ignore
|
||||
# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
@ -0,0 +1 @@
|
||||
2.3.0
|
7
samples/server/petstore/kotlin-server/ktor/Dockerfile
Normal file
7
samples/server/petstore/kotlin-server/ktor/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM openjdk:8-jre-alpine
|
||||
|
||||
COPY ./build/libs/kotlin-server.jar /root/kotlin-server.jar
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "kotlin-server.jar"]
|
104
samples/server/petstore/kotlin-server/ktor/README.md
Normal file
104
samples/server/petstore/kotlin-server/ktor/README.md
Normal file
@ -0,0 +1,104 @@
|
||||
# io.swagger.server - Kotlin Server library for Swagger Petstore
|
||||
|
||||
This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
|
||||
Generated by Swagger Codegen 2.3.0 (2018-01-21T22:11:17.518-05:00).
|
||||
|
||||
## Requires
|
||||
|
||||
* Kotlin 1.2.10
|
||||
* Gradle 4.3
|
||||
|
||||
## Build
|
||||
|
||||
First, create the gradle wrapper script:
|
||||
|
||||
```
|
||||
gradle wrapper
|
||||
```
|
||||
|
||||
Then, run:
|
||||
|
||||
```
|
||||
./gradlew check assemble
|
||||
```
|
||||
|
||||
This runs all tests and packages the library.
|
||||
|
||||
## Running
|
||||
|
||||
The server builds as a fat jar with a main entrypoint. To start the service, run `java -jar ./build/libs/kotlin-server.jar`.
|
||||
|
||||
You may also run in docker:
|
||||
|
||||
```
|
||||
docker build -t kotlin-server .
|
||||
docker run -p 8080:8080 kotlin-server
|
||||
```
|
||||
|
||||
## Features/Implementation Notes
|
||||
|
||||
* Supports JSON inputs/outputs, File inputs, and Form inputs (see ktor documentation for more info).
|
||||
* ~Supports collection formats for query parameters: csv, tsv, ssv, pipes.~
|
||||
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions.
|
||||
|
||||
<a name="documentation-for-api-endpoints"></a>
|
||||
## Documentation for API Endpoints
|
||||
|
||||
All URIs are relative to *http://petstore.swagger.io/v2*
|
||||
|
||||
Class | Method | HTTP request | Description
|
||||
------------ | ------------- | ------------- | -------------
|
||||
*PetApi* | [**addPet**](docs/PetApi.md#addpet) | **POST** /pet | Add a new pet to the store
|
||||
*PetApi* | [**deletePet**](docs/PetApi.md#deletepet) | **DELETE** /pet/{petId} | Deletes a pet
|
||||
*PetApi* | [**findPetsByStatus**](docs/PetApi.md#findpetsbystatus) | **GET** /pet/findByStatus | Finds Pets by status
|
||||
*PetApi* | [**findPetsByTags**](docs/PetApi.md#findpetsbytags) | **GET** /pet/findByTags | Finds Pets by tags
|
||||
*PetApi* | [**getPetById**](docs/PetApi.md#getpetbyid) | **GET** /pet/{petId} | Find pet by ID
|
||||
*PetApi* | [**updatePet**](docs/PetApi.md#updatepet) | **PUT** /pet | Update an existing pet
|
||||
*PetApi* | [**updatePetWithForm**](docs/PetApi.md#updatepetwithform) | **POST** /pet/{petId} | Updates a pet in the store with form data
|
||||
*PetApi* | [**uploadFile**](docs/PetApi.md#uploadfile) | **POST** /pet/{petId}/uploadImage | uploads an image
|
||||
*StoreApi* | [**deleteOrder**](docs/StoreApi.md#deleteorder) | **DELETE** /store/order/{orderId} | Delete purchase order by ID
|
||||
*StoreApi* | [**getInventory**](docs/StoreApi.md#getinventory) | **GET** /store/inventory | Returns pet inventories by status
|
||||
*StoreApi* | [**getOrderById**](docs/StoreApi.md#getorderbyid) | **GET** /store/order/{orderId} | Find purchase order by ID
|
||||
*StoreApi* | [**placeOrder**](docs/StoreApi.md#placeorder) | **POST** /store/order | Place an order for a pet
|
||||
*UserApi* | [**createUser**](docs/UserApi.md#createuser) | **POST** /user | Create user
|
||||
*UserApi* | [**createUsersWithArrayInput**](docs/UserApi.md#createuserswitharrayinput) | **POST** /user/createWithArray | Creates list of users with given input array
|
||||
*UserApi* | [**createUsersWithListInput**](docs/UserApi.md#createuserswithlistinput) | **POST** /user/createWithList | Creates list of users with given input array
|
||||
*UserApi* | [**deleteUser**](docs/UserApi.md#deleteuser) | **DELETE** /user/{username} | Delete user
|
||||
*UserApi* | [**getUserByName**](docs/UserApi.md#getuserbyname) | **GET** /user/{username} | Get user by user name
|
||||
*UserApi* | [**loginUser**](docs/UserApi.md#loginuser) | **GET** /user/login | Logs user into the system
|
||||
*UserApi* | [**logoutUser**](docs/UserApi.md#logoutuser) | **GET** /user/logout | Logs out current logged in user session
|
||||
*UserApi* | [**updateUser**](docs/UserApi.md#updateuser) | **PUT** /user/{username} | Updated user
|
||||
|
||||
|
||||
<a name="documentation-for-models"></a>
|
||||
## Documentation for Models
|
||||
|
||||
- [io.swagger.server.models.ApiResponse](docs/ApiResponse.md)
|
||||
- [io.swagger.server.models.Category](docs/Category.md)
|
||||
- [io.swagger.server.models.Order](docs/Order.md)
|
||||
- [io.swagger.server.models.Pet](docs/Pet.md)
|
||||
- [io.swagger.server.models.Tag](docs/Tag.md)
|
||||
- [io.swagger.server.models.User](docs/User.md)
|
||||
|
||||
|
||||
<a name="documentation-for-authorization"></a>
|
||||
## Documentation for Authorization
|
||||
|
||||
<a name="api_key"></a>
|
||||
### api_key
|
||||
|
||||
- **Type**: API key
|
||||
- **API key parameter name**: api_key
|
||||
- **Location**: HTTP header
|
||||
|
||||
<a name="petstore_auth"></a>
|
||||
### petstore_auth
|
||||
|
||||
- **Type**: OAuth
|
||||
- **Flow**: implicit
|
||||
- **Authorization URL**: http://petstore.swagger.io/api/oauth/dialog
|
||||
- **Scopes**:
|
||||
- write:pets: modify pets in your account
|
||||
- read:pets: read your pets
|
||||
|
74
samples/server/petstore/kotlin-server/ktor/build.gradle
Normal file
74
samples/server/petstore/kotlin-server/ktor/build.gradle
Normal file
@ -0,0 +1,74 @@
|
||||
group 'io.swagger'
|
||||
version '1.0.0'
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.3'
|
||||
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.10'
|
||||
ext.ktor_version = '0.9.1-alpha-9'
|
||||
ext.shadow_version = '2.0.2'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.github.jengelman.gradle.plugins:shadow:$shadow_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
|
||||
|
||||
// Initialization order with shadow 2.0.1 and Gradle 4.3 is weird.
|
||||
// See https://github.com/johnrengelman/shadow/issues/336#issuecomment-355402508
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
experimental {
|
||||
coroutines "enable"
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
baseName = 'kotlin-server'
|
||||
classifier = null
|
||||
version = null
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "http://dl.bintray.com/kotlin/ktor" }
|
||||
maven { url "https://dl.bintray.com/kotlin/kotlinx" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
compile "io.ktor:ktor-server-netty:$ktor_version"
|
||||
compile "io.ktor:ktor-metrics:$ktor_version"
|
||||
compile "io.ktor:ktor-locations:$ktor_version"
|
||||
compile "io.ktor:ktor-gson:$ktor_version"
|
||||
compile "io.ktor:ktor-client-core:$ktor_version"
|
||||
compile "io.ktor:ktor-client-apache:$ktor_version"
|
||||
compile "ch.qos.logback:logback-classic:1.2.1"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
@ -0,0 +1 @@
|
||||
org.gradle.caching=true
|
@ -0,0 +1 @@
|
||||
rootProject.name = 'kotlin-server'
|
@ -0,0 +1,52 @@
|
||||
package io.swagger.server
|
||||
|
||||
import com.codahale.metrics.*
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import io.ktor.application.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.apache.Apache
|
||||
import io.ktor.config.HoconApplicationConfig
|
||||
import io.ktor.features.*
|
||||
import io.ktor.gson.GsonConverter
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.metrics.*
|
||||
import io.ktor.routing.*
|
||||
import java.util.concurrent.*
|
||||
import io.swagger.server.apis.*
|
||||
|
||||
|
||||
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
|
||||
|
||||
object HTTP {
|
||||
val client = HttpClient(Apache)
|
||||
}
|
||||
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(Metrics) {
|
||||
val reporter = Slf4jReporter.forRegistry(registry)
|
||||
.outputTo(log)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
reporter.start(10, TimeUnit.SECONDS)
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
register(ContentType.Application.Json, GsonConverter())
|
||||
}
|
||||
install(AutoHeadResponse) // see http://ktor.io/features/autoheadresponse.html
|
||||
install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html
|
||||
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
|
||||
install(Locations) // see http://ktor.io/features/locations.html
|
||||
install(Routing) {
|
||||
PetApi()
|
||||
StoreApi()
|
||||
UserApi()
|
||||
}
|
||||
|
||||
environment.monitor.subscribe(ApplicationStopping)
|
||||
{
|
||||
HTTP.client.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package io.swagger.server
|
||||
|
||||
// Use this file to hold package-level internal functions that return receiver object passed to the `install` method.
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import io.swagger.server.settings
|
||||
|
||||
|
||||
/**
|
||||
* Application block for [HSTS] configuration.
|
||||
*
|
||||
* This file may be excluded in .swagger-codegen-ignore,
|
||||
* and application specific configuration can be applied in this function.
|
||||
*
|
||||
* See http://ktor.io/features/hsts.html
|
||||
*/
|
||||
internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
|
||||
return {
|
||||
maxAge = Duration.ofDays(365)
|
||||
includeSubDomains = true
|
||||
preload = false
|
||||
|
||||
// You may also apply any custom directives supported by specific user-agent. For example:
|
||||
// customDirectives.put("redirectHttpToHttps", "false")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Application block for [Compression] configuration.
|
||||
*
|
||||
* This file may be excluded in .swagger-codegen-ignore,
|
||||
* and application specific configuration can be applied in this function.
|
||||
*
|
||||
* See http://ktor.io/features/compression.html
|
||||
*/
|
||||
internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() -> Unit {
|
||||
return {
|
||||
gzip {
|
||||
priority = 1.0
|
||||
}
|
||||
deflate {
|
||||
priority = 10.0
|
||||
minimumSize(1024) // condition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defines authentication mechanisms used throughout the application.
|
||||
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
|
||||
OAuthServerSettings.OAuth2ServerSettings(
|
||||
name = "petstore_auth",
|
||||
authorizeUrl = "http://petstore.swagger.io/api/oauth/dialog",
|
||||
accessTokenUrl = "",
|
||||
requestMethod = HttpMethod.Get,
|
||||
clientId = settings.property("auth.oauth.petstore_auth.clientId").getString(),
|
||||
clientSecret = settings.property("auth.oauth.petstore_auth.clientSecret").getString(),
|
||||
defaultScopes = listOf("write:pets", "read:pets")
|
||||
)
|
||||
// OAuthServerSettings.OAuth2ServerSettings(
|
||||
// name = "facebook",
|
||||
// authorizeUrl = "https://graph.facebook.com/oauth/authorize",
|
||||
// accessTokenUrl = "https://graph.facebook.com/oauth/access_token",
|
||||
// requestMethod = HttpMethod.Post,
|
||||
//
|
||||
// clientId = "settings.property("auth.oauth.facebook.clientId").getString()",
|
||||
// clientSecret = "settings.property("auth.oauth.facebook.clientSecret").getString()",
|
||||
// defaultScopes = listOf("public_profile")
|
||||
// )
|
||||
).associateBy { it.name }
|
||||
|
||||
// Provides an application-level fixed thread pool on which to execute coroutines (mainly)
|
||||
internal val ApplicationExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4)
|
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server
|
||||
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.pipeline.PipelineContext
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.method
|
||||
import io.swagger.server.models.*
|
||||
|
||||
|
||||
// NOTE: ktor-location@0.9.0 is missing extension for Route.delete. This includes it.
|
||||
inline fun <reified T : Any> Route.delete(noinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit): Route {
|
||||
return location(T::class) {
|
||||
method(HttpMethod.Delete) {
|
||||
handle(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Paths {
|
||||
/**
|
||||
* Deletes a pet
|
||||
*
|
||||
* @param petId Pet id to delete
|
||||
* @param apiKey (optional)
|
||||
*/
|
||||
@Location("/pet/{petId}") class deletePet(val petId: kotlin.Long, val apiKey: kotlin.String)
|
||||
|
||||
/**
|
||||
* Finds Pets by status
|
||||
* Multiple status values can be provided with comma separated strings
|
||||
* @param status Status values that need to be considered for filter
|
||||
*/
|
||||
@Location("/pet/findByStatus") class findPetsByStatus(val status: kotlin.Array<kotlin.String>)
|
||||
|
||||
/**
|
||||
* Finds Pets by tags
|
||||
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
|
||||
* @param tags Tags to filter by
|
||||
*/
|
||||
@Location("/pet/findByTags") class findPetsByTags(val tags: kotlin.Array<kotlin.String>)
|
||||
|
||||
/**
|
||||
* Find pet by ID
|
||||
* Returns a single pet
|
||||
* @param petId ID of pet to return
|
||||
*/
|
||||
@Location("/pet/{petId}") class getPetById(val petId: kotlin.Long)
|
||||
|
||||
/**
|
||||
* Delete purchase order by ID
|
||||
* For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
|
||||
* @param orderId ID of the order that needs to be deleted
|
||||
*/
|
||||
@Location("/store/order/{orderId}") class deleteOrder(val orderId: kotlin.String)
|
||||
|
||||
/**
|
||||
* Returns pet inventories by status
|
||||
* Returns a map of status codes to quantities
|
||||
*/
|
||||
@Location("/store/inventory") class getInventory()
|
||||
|
||||
/**
|
||||
* Find purchase order by ID
|
||||
* For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
|
||||
* @param orderId ID of pet that needs to be fetched
|
||||
*/
|
||||
@Location("/store/order/{orderId}") class getOrderById(val orderId: kotlin.Long)
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
* This can only be done by the logged in user.
|
||||
* @param username The name that needs to be deleted
|
||||
*/
|
||||
@Location("/user/{username}") class deleteUser(val username: kotlin.String)
|
||||
|
||||
/**
|
||||
* Get user by user name
|
||||
*
|
||||
* @param username The name that needs to be fetched. Use user1 for testing.
|
||||
*/
|
||||
@Location("/user/{username}") class getUserByName(val username: kotlin.String)
|
||||
|
||||
/**
|
||||
* Logs user into the system
|
||||
*
|
||||
* @param username The user name for login
|
||||
* @param password The password for login in clear text
|
||||
*/
|
||||
@Location("/user/login") class loginUser(val username: kotlin.String, val password: kotlin.String)
|
||||
|
||||
/**
|
||||
* Logs out current logged in user session
|
||||
*
|
||||
*/
|
||||
@Location("/user/logout") class logoutUser()
|
||||
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.apis
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.auth.authentication
|
||||
import io.ktor.auth.basicAuthentication
|
||||
import io.ktor.auth.oauth
|
||||
import io.ktor.auth.OAuthAccessTokenResponse
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
|
||||
import io.swagger.server.ApplicationAuthProviders
|
||||
import io.swagger.server.Paths
|
||||
import io.swagger.server.ApplicationExecutors
|
||||
import io.swagger.server.HTTP.client
|
||||
import io.swagger.server.infrastructure.ApiPrincipal
|
||||
import io.swagger.server.infrastructure.apiKeyAuth
|
||||
|
||||
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
|
||||
// see https://github.com/ktorio/ktor/issues/288
|
||||
import io.swagger.server.delete
|
||||
|
||||
import io.swagger.server.models.ApiResponse
|
||||
import io.swagger.server.models.Pet
|
||||
|
||||
fun Route.PetApi() {
|
||||
val gson = Gson()
|
||||
val empty = mutableMapOf<String, Any?>()
|
||||
|
||||
route("/pet") {
|
||||
post {
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
delete<Paths.deletePet> { it: Paths.deletePet ->
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauthAtLocation<Paths.deletePet>(client, ApplicationExecutors.asCoroutineDispatcher(),
|
||||
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
|
||||
urlProvider = { currentLocation, provider ->
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
get<Paths.findPetsByStatus> { it: Paths.findPetsByStatus ->
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<Pet>
|
||||
<id>123456789</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>
|
||||
<photoUrls>aeiou</photoUrls>
|
||||
</photoUrls>
|
||||
<tags>
|
||||
</tags>
|
||||
<status>aeiou</status>
|
||||
</Pet>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauthAtLocation<Paths.findPetsByStatus>(client, ApplicationExecutors.asCoroutineDispatcher(),
|
||||
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
|
||||
urlProvider = { currentLocation, provider ->
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/findByStatus' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
get<Paths.findPetsByTags> { it: Paths.findPetsByTags ->
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<Pet>
|
||||
<id>123456789</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>
|
||||
<photoUrls>aeiou</photoUrls>
|
||||
</photoUrls>
|
||||
<tags>
|
||||
</tags>
|
||||
<status>aeiou</status>
|
||||
</Pet>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauthAtLocation<Paths.findPetsByTags>(client, ApplicationExecutors.asCoroutineDispatcher(),
|
||||
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
|
||||
urlProvider = { currentLocation, provider ->
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/findByTags' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
get<Paths.getPetById> { it: Paths.getPetById ->
|
||||
val principal = call.authentication.principal<ApiPrincipal>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<Pet>
|
||||
<id>123456789</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>
|
||||
<photoUrls>aeiou</photoUrls>
|
||||
</photoUrls>
|
||||
<tags>
|
||||
</tags>
|
||||
<status>aeiou</status>
|
||||
</Pet>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
// "Implement API key auth (api_key) for parameter name 'api_key'."
|
||||
apiKeyAuth("api_key", "header") {
|
||||
// TODO: "Verify key here , accessible as it.value"
|
||||
if (it.value == "keyboardcat") {
|
||||
ApiPrincipal(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
route("/pet") {
|
||||
put {
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
route("/pet/{petId}") {
|
||||
post {
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
route("/pet/{petId}/uploadImage") {
|
||||
post {
|
||||
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
val exampleContentType = "application/json"
|
||||
val exampleContentString = """{
|
||||
"code" : 0,
|
||||
"type" : "type",
|
||||
"message" : "message"
|
||||
}"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
|
||||
// TODO: define a callback url here.
|
||||
"/"
|
||||
})
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/pet/{petId}/uploadImage' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.apis
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.auth.authentication
|
||||
import io.ktor.auth.basicAuthentication
|
||||
import io.ktor.auth.oauth
|
||||
import io.ktor.auth.OAuthAccessTokenResponse
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
|
||||
import io.swagger.server.ApplicationAuthProviders
|
||||
import io.swagger.server.Paths
|
||||
import io.swagger.server.ApplicationExecutors
|
||||
import io.swagger.server.HTTP.client
|
||||
import io.swagger.server.infrastructure.ApiPrincipal
|
||||
import io.swagger.server.infrastructure.apiKeyAuth
|
||||
|
||||
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
|
||||
// see https://github.com/ktorio/ktor/issues/288
|
||||
import io.swagger.server.delete
|
||||
|
||||
import io.swagger.server.models.Order
|
||||
|
||||
fun Route.StoreApi() {
|
||||
val gson = Gson()
|
||||
val empty = mutableMapOf<String, Any?>()
|
||||
|
||||
delete<Paths.deleteOrder> { it: Paths.deleteOrder ->
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
|
||||
|
||||
get<Paths.getInventory> { it: Paths.getInventory ->
|
||||
val principal = call.authentication.principal<ApiPrincipal>()
|
||||
|
||||
if (principal == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
val exampleContentType = "application/json"
|
||||
val exampleContentString = """{
|
||||
"key" : 0
|
||||
}"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
|
||||
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
|
||||
|
||||
try {
|
||||
authentication {
|
||||
// "Implement API key auth (api_key) for parameter name 'api_key'."
|
||||
apiKeyAuth("api_key", "header") {
|
||||
// TODO: "Verify key here , accessible as it.value"
|
||||
if (it.value == "keyboardcat") {
|
||||
ApiPrincipal(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
|
||||
application.environment.log.warn("authentication block for '/store/inventory' is duplicated in code. " +
|
||||
"Generated endpoints may need to be merged under a 'route' entry.")
|
||||
}
|
||||
}
|
||||
|
||||
get<Paths.getOrderById> { it: Paths.getOrderById ->
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<Order>
|
||||
<id>123456789</id>
|
||||
<petId>123456789</petId>
|
||||
<quantity>123</quantity>
|
||||
<shipDate>2000-01-23T04:56:07.000Z</shipDate>
|
||||
<status>aeiou</status>
|
||||
<complete>true</complete>
|
||||
</Order>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
route("/store/order") {
|
||||
post {
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<Order>
|
||||
<id>123456789</id>
|
||||
<petId>123456789</petId>
|
||||
<quantity>123</quantity>
|
||||
<shipDate>2000-01-23T04:56:07.000Z</shipDate>
|
||||
<status>aeiou</status>
|
||||
<complete>true</complete>
|
||||
</Order>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.apis
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.auth.authentication
|
||||
import io.ktor.auth.basicAuthentication
|
||||
import io.ktor.auth.oauth
|
||||
import io.ktor.auth.OAuthAccessTokenResponse
|
||||
import io.ktor.auth.OAuthServerSettings
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
|
||||
import io.swagger.server.ApplicationAuthProviders
|
||||
import io.swagger.server.Paths
|
||||
import io.swagger.server.ApplicationExecutors
|
||||
import io.swagger.server.HTTP.client
|
||||
import io.swagger.server.infrastructure.ApiPrincipal
|
||||
import io.swagger.server.infrastructure.apiKeyAuth
|
||||
|
||||
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
|
||||
// see https://github.com/ktorio/ktor/issues/288
|
||||
import io.swagger.server.delete
|
||||
|
||||
import io.swagger.server.models.User
|
||||
|
||||
fun Route.UserApi() {
|
||||
val gson = Gson()
|
||||
val empty = mutableMapOf<String, Any?>()
|
||||
|
||||
route("/user") {
|
||||
post {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
route("/user/createWithArray") {
|
||||
post {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
route("/user/createWithList") {
|
||||
post {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
delete<Paths.deleteUser> { it: Paths.deleteUser ->
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
|
||||
|
||||
get<Paths.getUserByName> { it: Paths.getUserByName ->
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """<User>
|
||||
<id>123456789</id>
|
||||
<username>aeiou</username>
|
||||
<firstName>aeiou</firstName>
|
||||
<lastName>aeiou</lastName>
|
||||
<email>aeiou</email>
|
||||
<password>aeiou</password>
|
||||
<phone>aeiou</phone>
|
||||
<userStatus>123</userStatus>
|
||||
</User>"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get<Paths.loginUser> { it: Paths.loginUser ->
|
||||
val exampleContentType = "application/xml"
|
||||
val exampleContentString = """aeiou"""
|
||||
|
||||
when(exampleContentType) {
|
||||
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
|
||||
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
|
||||
else -> call.respondText(exampleContentString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get<Paths.logoutUser> { it: Paths.logoutUser ->
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
|
||||
|
||||
route("/user/{username}") {
|
||||
put {
|
||||
call.respond(HttpStatusCode.NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package io.swagger.server.infrastructure
|
||||
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.request.ApplicationRequest
|
||||
import io.ktor.response.respond
|
||||
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.pipeline.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import java.util.*
|
||||
|
||||
enum class ApiKeyLocation(val location: String) {
|
||||
QUERY("query"),
|
||||
HEADER("header")
|
||||
}
|
||||
data class ApiKey(val value: String): Credential
|
||||
data class ApiPrincipal(val apiKey: ApiKey?) : Principal
|
||||
fun ApplicationCall.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? = request.apiKey(key, keyLocation)
|
||||
fun ApplicationRequest.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? {
|
||||
val value: String? = when(keyLocation) {
|
||||
ApiKeyLocation.QUERY -> this.queryParameters[key]
|
||||
ApiKeyLocation.HEADER -> this.headers[key]
|
||||
}
|
||||
when (value) {
|
||||
null -> return null
|
||||
else -> return ApiKey(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String, validate: suspend (ApiKey) -> ApiPrincipal?) {
|
||||
intercept(AuthenticationPipeline.RequestAuthentication) { context ->
|
||||
val credentials = call.request.apiKey(apiKeyName, ApiKeyLocation.values().first { it.location == authLocation })
|
||||
val principal = credentials?.let { validate(it) }
|
||||
|
||||
val cause = when {
|
||||
credentials == null -> AuthenticationFailedCause.NoCredentials
|
||||
principal == null -> AuthenticationFailedCause.InvalidCredentials
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (cause != null) {
|
||||
context.challenge(apiKeyName, cause) {
|
||||
// TODO: Verify correct response structure here.
|
||||
call.respond(UnauthorizedResponse(HttpAuthHeader.Parameterized("API_KEY", mapOf("key" to apiKeyName), HeaderValueEncoding.QUOTED_ALWAYS)))
|
||||
it.complete()
|
||||
}
|
||||
}
|
||||
if (principal != null) {
|
||||
context.principal(principal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
|
||||
/**
|
||||
* Describes the result of uploading an image resource
|
||||
* @param code
|
||||
* @param type
|
||||
* @param message
|
||||
*/
|
||||
data class ApiResponse (
|
||||
val code: kotlin.Int? = null,
|
||||
val type: kotlin.String? = null,
|
||||
val message: kotlin.String? = null
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
|
||||
/**
|
||||
* A category for a pet
|
||||
* @param id
|
||||
* @param name
|
||||
*/
|
||||
data class Category (
|
||||
val id: kotlin.Long? = null,
|
||||
val name: kotlin.String? = null
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
|
||||
/**
|
||||
* An order for a pets from the pet store
|
||||
* @param id
|
||||
* @param petId
|
||||
* @param quantity
|
||||
* @param shipDate
|
||||
* @param status Order Status
|
||||
* @param complete
|
||||
*/
|
||||
data class Order (
|
||||
val id: kotlin.Long? = null,
|
||||
val petId: kotlin.Long? = null,
|
||||
val quantity: kotlin.Int? = null,
|
||||
val shipDate: java.time.LocalDateTime? = null,
|
||||
/* Order Status */
|
||||
val status: Order.Status? = null,
|
||||
val complete: kotlin.Boolean? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Order Status
|
||||
* Values: placed,approved,delivered
|
||||
*/
|
||||
enum class Status(val value: kotlin.Any){
|
||||
|
||||
placed("placed"),
|
||||
|
||||
approved("approved"),
|
||||
|
||||
delivered("delivered");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
import io.swagger.server.models.Category
|
||||
import io.swagger.server.models.Tag
|
||||
|
||||
/**
|
||||
* A pet for sale in the pet store
|
||||
* @param id
|
||||
* @param category
|
||||
* @param name
|
||||
* @param photoUrls
|
||||
* @param tags
|
||||
* @param status pet status in the store
|
||||
*/
|
||||
data class Pet (
|
||||
val name: kotlin.String,
|
||||
val photoUrls: kotlin.Array<kotlin.String>,
|
||||
val id: kotlin.Long? = null,
|
||||
val category: Category? = null,
|
||||
val tags: kotlin.Array<Tag>? = null,
|
||||
/* pet status in the store */
|
||||
val status: Pet.Status? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* pet status in the store
|
||||
* Values: available,pending,sold
|
||||
*/
|
||||
enum class Status(val value: kotlin.Any){
|
||||
|
||||
available("available"),
|
||||
|
||||
pending("pending"),
|
||||
|
||||
sold("sold");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
|
||||
/**
|
||||
* A tag for a pet
|
||||
* @param id
|
||||
* @param name
|
||||
*/
|
||||
data class Tag (
|
||||
val id: kotlin.Long? = null,
|
||||
val name: kotlin.String? = null
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Swagger Petstore
|
||||
* This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
*
|
||||
* OpenAPI spec version: 1.0.0
|
||||
* Contact: apiteam@swagger.io
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.server.models
|
||||
|
||||
|
||||
/**
|
||||
* A User who is purchasing from the pet store
|
||||
* @param id
|
||||
* @param username
|
||||
* @param firstName
|
||||
* @param lastName
|
||||
* @param email
|
||||
* @param password
|
||||
* @param phone
|
||||
* @param userStatus User Status
|
||||
*/
|
||||
data class User (
|
||||
val id: kotlin.Long? = null,
|
||||
val username: kotlin.String? = null,
|
||||
val firstName: kotlin.String? = null,
|
||||
val lastName: kotlin.String? = null,
|
||||
val email: kotlin.String? = null,
|
||||
val password: kotlin.String? = null,
|
||||
val phone: kotlin.String? = null,
|
||||
/* User Status */
|
||||
val userStatus: kotlin.Int? = null
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
ktor {
|
||||
deployment {
|
||||
environment = development
|
||||
port = 8080
|
||||
autoreload = true
|
||||
watch = [ io.swagger.server ]
|
||||
}
|
||||
|
||||
application {
|
||||
modules = [ io.swagger.server.AppMainKt.main ]
|
||||
}
|
||||
}
|
||||
|
||||
# Typesafe config allows multiple ways to provide configuration values without hard-coding them here.
|
||||
# Please see https://github.com/lightbend/config for details.
|
||||
auth {
|
||||
oauth {
|
||||
petstore_auth {
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="trace">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
|
||||
</configuration>
|
Loading…
x
Reference in New Issue
Block a user