mirror of
https://github.com/valitydev/openapi-generator.git
synced 2024-11-08 11:23:58 +00:00
add haskell generator
This commit is contained in:
parent
246ed57547
commit
7fbc8fa31e
@ -1097,6 +1097,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
**/
|
**/
|
||||||
@SuppressWarnings("static-method")
|
@SuppressWarnings("static-method")
|
||||||
public String getSchemaType(Schema schema) {
|
public String getSchemaType(Schema schema) {
|
||||||
|
// datatype is the OAI type (e.g. integer, long, etc)
|
||||||
String datatype = null;
|
String datatype = null;
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(schema.get$ref())) { // object
|
if (StringUtils.isNotBlank(schema.get$ref())) { // object
|
||||||
@ -1141,7 +1142,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) {
|
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) {
|
||||||
datatype = "long";
|
datatype = "long";
|
||||||
} else {
|
} else {
|
||||||
datatype = schema.getType();
|
datatype = schema.getType(); // integer
|
||||||
}
|
}
|
||||||
} else if (schema instanceof MapSchema) {
|
} else if (schema instanceof MapSchema) {
|
||||||
datatype = "map";
|
datatype = "map";
|
||||||
@ -1565,7 +1566,6 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String type = getSchemaType(p);
|
String type = getSchemaType(p);
|
||||||
//LOGGER.info("from property = " + p);
|
|
||||||
if (p instanceof IntegerSchema || SchemaTypeUtil.INTEGER_TYPE.equals(p.getType())) {
|
if (p instanceof IntegerSchema || SchemaTypeUtil.INTEGER_TYPE.equals(p.getType())) {
|
||||||
property.isNumeric = Boolean.TRUE;
|
property.isNumeric = Boolean.TRUE;
|
||||||
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) {
|
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) {
|
||||||
@ -1718,6 +1718,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
property.allowableValues = allowableValues;
|
property.allowableValues = allowableValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property.datatype = getTypeDeclaration(p);
|
property.datatype = getTypeDeclaration(p);
|
||||||
property.dataFormat = p.getFormat();
|
property.dataFormat = p.getFormat();
|
||||||
|
|
||||||
@ -1978,7 +1979,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
Operation operation,
|
Operation operation,
|
||||||
Map<String, Schema> schemas,
|
Map<String, Schema> schemas,
|
||||||
OpenAPI openAPI) {
|
OpenAPI openAPI) {
|
||||||
//LOGGER.info("fromOperation => operation: " + operation);
|
LOGGER.debug("fromOperation => operation: " + operation);
|
||||||
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
|
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
|
||||||
Set<String> imports = new HashSet<String>();
|
Set<String> imports = new HashSet<String>();
|
||||||
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
|
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
|
||||||
@ -2191,9 +2192,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
|
|
||||||
// add imports to operation import tag
|
// add imports to operation import tag
|
||||||
for (String i : imports) {
|
for (String i : imports) {
|
||||||
LOGGER.info("debugging fromOperation imports: " + i);
|
|
||||||
if (needToImport(i)) {
|
if (needToImport(i)) {
|
||||||
LOGGER.info("debugging fromOperation imports: " + i + " imported");
|
|
||||||
op.imports.add(i);
|
op.imports.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2671,6 +2670,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
setParameterExampleValue(codegenParameter);
|
setParameterExampleValue(codegenParameter);
|
||||||
|
|
||||||
postProcessParameter(codegenParameter);
|
postProcessParameter(codegenParameter);
|
||||||
|
LOGGER.info("debugging codegenParameter return: " + codegenParameter);
|
||||||
return codegenParameter;
|
return codegenParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,563 @@
|
|||||||
|
package org.openapitools.codegen.languages;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openapitools.codegen.CliOption;
|
||||||
|
import org.openapitools.codegen.CodegenConfig;
|
||||||
|
import org.openapitools.codegen.CodegenConstants;
|
||||||
|
import org.openapitools.codegen.CodegenModel;
|
||||||
|
import org.openapitools.codegen.CodegenOperation;
|
||||||
|
import org.openapitools.codegen.CodegenParameter;
|
||||||
|
import org.openapitools.codegen.CodegenProperty;
|
||||||
|
import org.openapitools.codegen.CodegenType;
|
||||||
|
import org.openapitools.codegen.DefaultCodegen;
|
||||||
|
import org.openapitools.codegen.SupportingFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.Operation;
|
||||||
|
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||||
|
import io.swagger.v3.oas.models.media.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class HaskellServantCodegen extends DefaultCodegen implements CodegenConfig {
|
||||||
|
|
||||||
|
// source folder where to write the files
|
||||||
|
protected String sourceFolder = "src";
|
||||||
|
protected String apiVersion = "0.0.1";
|
||||||
|
private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the type of generator.
|
||||||
|
*
|
||||||
|
* @return the CodegenType for this generator
|
||||||
|
*/
|
||||||
|
public CodegenType getTag() {
|
||||||
|
return CodegenType.SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a friendly name for the generator. This will be used by the generator
|
||||||
|
* to select the library with the -l flag.
|
||||||
|
*
|
||||||
|
* @return the friendly name for the generator
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return "haskell";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns human-friendly help for the generator. Provide the consumer with help
|
||||||
|
* tips, parameters here
|
||||||
|
*
|
||||||
|
* @return A string value for the help message
|
||||||
|
*/
|
||||||
|
public String getHelp() {
|
||||||
|
return "Generates a Haskell server and client library.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public HaskellServantCodegen() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// override the mapping to keep the original mapping in Haskell
|
||||||
|
specialCharReplacements.put("-", "Dash");
|
||||||
|
specialCharReplacements.put(">", "GreaterThan");
|
||||||
|
specialCharReplacements.put("<", "LessThan");
|
||||||
|
|
||||||
|
// backslash and double quote need double the escapement for both Java and Haskell
|
||||||
|
specialCharReplacements.remove("\\");
|
||||||
|
specialCharReplacements.remove("\"");
|
||||||
|
specialCharReplacements.put("\\\\", "Back_Slash");
|
||||||
|
specialCharReplacements.put("\\\"", "Double_Quote");
|
||||||
|
|
||||||
|
// set the output folder here
|
||||||
|
outputFolder = "generated-code/haskell-servant";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Template Location. This is the location which templates will be read from. The generator
|
||||||
|
* will use the resource stream to attempt to read the templates.
|
||||||
|
*/
|
||||||
|
embeddedTemplateDir = templateDir = "haskell-servant";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Api Package. Optional, if needed, this can be used in templates
|
||||||
|
*/
|
||||||
|
apiPackage = "API";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Model Package. Optional, if needed, this can be used in templates
|
||||||
|
*/
|
||||||
|
modelPackage = "Types";
|
||||||
|
|
||||||
|
// Haskell keywords and reserved function names, taken mostly from https://wiki.haskell.org/Keywords
|
||||||
|
setReservedWordsLowerCase(
|
||||||
|
Arrays.asList(
|
||||||
|
// Keywords
|
||||||
|
"as", "case", "of",
|
||||||
|
"class", "data", "family",
|
||||||
|
"default", "deriving",
|
||||||
|
"do", "forall", "foreign", "hiding",
|
||||||
|
"if", "then", "else",
|
||||||
|
"import", "infix", "infixl", "infixr",
|
||||||
|
"instance", "let", "in",
|
||||||
|
"mdo", "module", "newtype",
|
||||||
|
"proc", "qualified", "rec",
|
||||||
|
"type", "where"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Additional Properties. These values can be passed to the templates and
|
||||||
|
* are available in models, apis, and supporting files
|
||||||
|
*/
|
||||||
|
additionalProperties.put("apiVersion", apiVersion);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supporting Files. You can write single files for the generator with the
|
||||||
|
* entire object tree available. If the input file has a suffix of `.mustache
|
||||||
|
* it will be processed by the template engine. Otherwise, it will be copied
|
||||||
|
*/
|
||||||
|
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
|
||||||
|
supportingFiles.add(new SupportingFile("stack.mustache", "", "stack.yaml"));
|
||||||
|
supportingFiles.add(new SupportingFile("Setup.mustache", "", "Setup.hs"));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Language Specific Primitives. These types will not trigger imports by
|
||||||
|
* the client generator
|
||||||
|
*/
|
||||||
|
languageSpecificPrimitives = new HashSet<String>(
|
||||||
|
Arrays.asList(
|
||||||
|
"Bool",
|
||||||
|
"String",
|
||||||
|
"Int",
|
||||||
|
"Integer",
|
||||||
|
"Float",
|
||||||
|
"Char",
|
||||||
|
"Double",
|
||||||
|
"List",
|
||||||
|
"FilePath"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
typeMapping.clear();
|
||||||
|
typeMapping.put("array", "List");
|
||||||
|
typeMapping.put("set", "Set");
|
||||||
|
typeMapping.put("boolean", "Bool");
|
||||||
|
typeMapping.put("string", "Text");
|
||||||
|
typeMapping.put("integer", "Int");
|
||||||
|
typeMapping.put("long", "Integer");
|
||||||
|
typeMapping.put("short", "Int");
|
||||||
|
typeMapping.put("char", "Char");
|
||||||
|
typeMapping.put("float", "Float");
|
||||||
|
typeMapping.put("double", "Double");
|
||||||
|
typeMapping.put("DateTime", "Integer");
|
||||||
|
typeMapping.put("file", "FilePath");
|
||||||
|
typeMapping.put("number", "Double");
|
||||||
|
typeMapping.put("any", "Value");
|
||||||
|
typeMapping.put("UUID", "Text");
|
||||||
|
typeMapping.put("ByteArray", "Text");
|
||||||
|
typeMapping.put("object", "Value");
|
||||||
|
|
||||||
|
importMapping.clear();
|
||||||
|
importMapping.put("Map", "qualified Data.Map as Map");
|
||||||
|
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
|
||||||
|
* those terms here. This logic is only called if a variable matches the reserved words
|
||||||
|
*
|
||||||
|
* @return the escaped term
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String escapeReservedWord(String name) {
|
||||||
|
if(this.reservedWordsMappings().containsKey(name)) {
|
||||||
|
return this.reservedWordsMappings().get(name);
|
||||||
|
}
|
||||||
|
return "_" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String firstLetterToUpper(String word) {
|
||||||
|
if (word.length() == 0) {
|
||||||
|
return word;
|
||||||
|
} else if (word.length() == 1) {
|
||||||
|
return word.substring(0, 1).toUpperCase();
|
||||||
|
} else {
|
||||||
|
return word.substring(0, 1).toUpperCase() + word.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String firstLetterToLower(String word) {
|
||||||
|
if (word.length() == 0) {
|
||||||
|
return word;
|
||||||
|
} else if (word.length() == 1) {
|
||||||
|
return word.substring(0, 1).toLowerCase();
|
||||||
|
} else {
|
||||||
|
return word.substring(0, 1).toLowerCase() + word.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||||
|
// From the title, compute a reasonable name for the package and the API
|
||||||
|
String title = openAPI.getInfo().getTitle();
|
||||||
|
|
||||||
|
// Drop any API suffix
|
||||||
|
if(title == null) {
|
||||||
|
title = "OpenAPI";
|
||||||
|
} else {
|
||||||
|
title = title.trim();
|
||||||
|
if (title.toUpperCase().endsWith("API")) {
|
||||||
|
title = title.substring(0, title.length() - 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] words = title.split(" ");
|
||||||
|
|
||||||
|
// The package name is made by appending the lowercased words of the title interspersed with dashes
|
||||||
|
List<String> wordsLower = new ArrayList<String>();
|
||||||
|
for (String word : words) {
|
||||||
|
wordsLower.add(word.toLowerCase());
|
||||||
|
}
|
||||||
|
String cabalName = joinStrings("-", wordsLower);
|
||||||
|
|
||||||
|
// The API name is made by appending the capitalized words of the title
|
||||||
|
List<String> wordsCaps = new ArrayList<String>();
|
||||||
|
for (String word : words) {
|
||||||
|
wordsCaps.add(firstLetterToUpper(word));
|
||||||
|
}
|
||||||
|
String apiName = joinStrings("", wordsCaps);
|
||||||
|
|
||||||
|
// Set the filenames to write for the API
|
||||||
|
supportingFiles.add(new SupportingFile("haskell-servant-codegen.mustache", "", cabalName + ".cabal"));
|
||||||
|
supportingFiles.add(new SupportingFile("API.mustache", "lib/" + apiName, "API.hs"));
|
||||||
|
supportingFiles.add(new SupportingFile("Types.mustache", "lib/" + apiName, "Types.hs"));
|
||||||
|
|
||||||
|
|
||||||
|
additionalProperties.put("title", apiName);
|
||||||
|
additionalProperties.put("titleLower", firstLetterToLower(apiName));
|
||||||
|
additionalProperties.put("package", cabalName);
|
||||||
|
|
||||||
|
// Due to the way servant resolves types, we need a high context stack limit
|
||||||
|
additionalProperties.put("contextStackLimit", openAPI.getPaths().size() * 2 + 300);
|
||||||
|
|
||||||
|
List<Map<String, Object>> replacements = new ArrayList<>();
|
||||||
|
Object[] replacementChars = specialCharReplacements.keySet().toArray();
|
||||||
|
for(int i = 0; i < replacementChars.length; i++) {
|
||||||
|
String c = (String) replacementChars[i];
|
||||||
|
Map<String, Object> o = new HashMap<>();
|
||||||
|
o.put("char", c);
|
||||||
|
o.put("replacement", "'" + specialCharReplacements.get(c));
|
||||||
|
o.put("hasMore", i != replacementChars.length - 1);
|
||||||
|
replacements.add(o);
|
||||||
|
}
|
||||||
|
additionalProperties.put("specialCharReplacements", replacements);
|
||||||
|
|
||||||
|
super.preprocessOpenAPI(openAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional - type declaration. This is a String which is used by the templates to instantiate your
|
||||||
|
* types. There is typically special handling for different property types
|
||||||
|
*
|
||||||
|
* @return a string value used as the `dataType` field for model templates, `returnType` for api templates
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getTypeDeclaration(Schema p) {
|
||||||
|
if (p instanceof ArraySchema) {
|
||||||
|
ArraySchema ap = (ArraySchema) p;
|
||||||
|
Schema inner = ap.getItems();
|
||||||
|
return "[" + getTypeDeclaration(inner) + "]";
|
||||||
|
} else if (p instanceof MapSchema) {
|
||||||
|
MapSchema mp = (MapSchema) p;
|
||||||
|
Schema inner = (Schema) mp.getAdditionalProperties();
|
||||||
|
return "Map.Map String " + getTypeDeclaration(inner);
|
||||||
|
}
|
||||||
|
return fixModelChars(super.getTypeDeclaration(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional - OpenAPI type conversion. This is used to map OpenAPI types in a `Schema` into
|
||||||
|
* either language specific types via `typeMapping` or into complex models if there is not a mapping.
|
||||||
|
*
|
||||||
|
* @return a string value of the type or complex model for this property
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSchemaType(Schema p) {
|
||||||
|
String swaggerType = super.getSchemaType(p);
|
||||||
|
LOGGER.debug("debugging swager type: " + p.getType() + ", " + p.getFormat() + " => " + swaggerType);
|
||||||
|
String type = null;
|
||||||
|
if (typeMapping.containsKey(swaggerType)) {
|
||||||
|
type = typeMapping.get(swaggerType);
|
||||||
|
return type;
|
||||||
|
//if (languageSpecificPrimitives.contains(type))
|
||||||
|
// return toModelName(type);
|
||||||
|
} else if(typeMapping.containsValue(swaggerType)) {
|
||||||
|
// TODO what's this case for?
|
||||||
|
type = swaggerType + "_";
|
||||||
|
} else {
|
||||||
|
type = swaggerType;
|
||||||
|
}
|
||||||
|
// it's a model
|
||||||
|
return toModelName(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toInstantiationType(Schema p) {
|
||||||
|
if (p instanceof MapSchema) {
|
||||||
|
MapSchema ap = (MapSchema) p;
|
||||||
|
Schema additionalProperties2 = (Schema) ap.getAdditionalProperties();
|
||||||
|
String type = additionalProperties2.getType();
|
||||||
|
if (null == type) {
|
||||||
|
LOGGER.error("No Type defined for Additional Property " + additionalProperties2 + "\n" //
|
||||||
|
+ "\tIn Property: " + p);
|
||||||
|
}
|
||||||
|
String inner = getSchemaType(additionalProperties2);
|
||||||
|
return "(Map.Map Text " + inner + ")";
|
||||||
|
} else if (p instanceof ArraySchema) {
|
||||||
|
ArraySchema ap = (ArraySchema) p;
|
||||||
|
String inner = getSchemaType(ap.getItems());
|
||||||
|
// Return only the inner type; the wrapping with QueryList is done
|
||||||
|
// somewhere else, where we have access to the collection format.
|
||||||
|
return inner;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Intersperse a separator string between a list of strings, like String.join.
|
||||||
|
private String joinStrings(String sep, List<String> ss) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String s : ss) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(sep);
|
||||||
|
}
|
||||||
|
sb.append(s);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an HTTP path to a Servant route, including captured parameters.
|
||||||
|
// For example, the path /api/jobs/info/{id}/last would become:
|
||||||
|
// "api" :> "jobs" :> "info" :> Capture "id" IdType :> "last"
|
||||||
|
// IdType is provided by the capture params.
|
||||||
|
private List<String> pathToServantRoute(String path, List<CodegenParameter> pathParams) {
|
||||||
|
// Map the capture params by their names.
|
||||||
|
HashMap<String, String> captureTypes = new HashMap<String, String>();
|
||||||
|
for (CodegenParameter param : pathParams) {
|
||||||
|
captureTypes.put(param.baseName, param.dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut off the leading slash, if it is present.
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the path into a list of servant route components.
|
||||||
|
List<String> pathComponents = new ArrayList<String>();
|
||||||
|
for (String piece : path.split("/")) {
|
||||||
|
if (piece.startsWith("{") && piece.endsWith("}")) {
|
||||||
|
String name = piece.substring(1, piece.length() - 1);
|
||||||
|
pathComponents.add("Capture \"" + name + "\" " + captureTypes.get(name));
|
||||||
|
} else {
|
||||||
|
pathComponents.add("\"" + piece + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersperse the servant route pieces with :> to construct the final API type
|
||||||
|
return pathComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the arguments that are passed in the route path parameters
|
||||||
|
private List<String> pathToClientType(String path, List<CodegenParameter> pathParams) {
|
||||||
|
// Map the capture params by their names.
|
||||||
|
HashMap<String, String> captureTypes = new HashMap<String, String>();
|
||||||
|
for (CodegenParameter param : pathParams) {
|
||||||
|
captureTypes.put(param.baseName, param.dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut off the leading slash, if it is present.
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the path into a list of servant route components.
|
||||||
|
List<String> type = new ArrayList<String>();
|
||||||
|
for (String piece : path.split("/")) {
|
||||||
|
if (piece.startsWith("{") && piece.endsWith("}")) {
|
||||||
|
String name = piece.substring(1, piece.length() - 1);
|
||||||
|
type.add(captureTypes.get(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map<String, Schema> definitions, OpenAPI openAPI) {
|
||||||
|
CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, openAPI);
|
||||||
|
|
||||||
|
List<String> path = pathToServantRoute(op.path, op.pathParams);
|
||||||
|
List<String> type = pathToClientType(op.path, op.pathParams);
|
||||||
|
|
||||||
|
// Query parameters appended to routes
|
||||||
|
for (CodegenParameter param : op.queryParams) {
|
||||||
|
String paramType = param.dataType;
|
||||||
|
if (param.isListContainer) {
|
||||||
|
if (StringUtils.isEmpty(param.collectionFormat)) {
|
||||||
|
param.collectionFormat = "csv";
|
||||||
|
}
|
||||||
|
paramType = makeQueryListType(paramType, param.collectionFormat);
|
||||||
|
}
|
||||||
|
path.add("QueryParam \"" + param.baseName + "\" " + paramType);
|
||||||
|
type.add("Maybe " + param.dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either body or form data parameters appended to route
|
||||||
|
// As far as I know, you cannot have two ReqBody routes.
|
||||||
|
// Is it possible to have body params AND have form params?
|
||||||
|
String bodyType = null;
|
||||||
|
if (op.getHasBodyParam()) {
|
||||||
|
for (CodegenParameter param : op.bodyParams) {
|
||||||
|
path.add("ReqBody '[JSON] " + param.dataType);
|
||||||
|
bodyType = param.dataType;
|
||||||
|
}
|
||||||
|
} else if(op.getHasFormParams()) {
|
||||||
|
// Use the FormX data type, where X is the conglomerate of all things being passed
|
||||||
|
String formName = "Form" + camelize(op.operationId);
|
||||||
|
bodyType = formName;
|
||||||
|
path.add("ReqBody '[FormUrlEncoded] " + formName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyType != null) {
|
||||||
|
type.add(bodyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special headers appended to route
|
||||||
|
for (CodegenParameter param : op.headerParams) {
|
||||||
|
path.add("Header \"" + param.baseName + "\" " + param.dataType);
|
||||||
|
|
||||||
|
String paramType = param.dataType;
|
||||||
|
if (param.isListContainer) {
|
||||||
|
if (StringUtils.isEmpty(param.collectionFormat)) {
|
||||||
|
param.collectionFormat = "csv";
|
||||||
|
}
|
||||||
|
paramType = makeQueryListType(paramType, param.collectionFormat);
|
||||||
|
}
|
||||||
|
type.add("Maybe " + paramType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store form parameter name in the vendor extensions
|
||||||
|
for (CodegenParameter param : op.formParams) {
|
||||||
|
param.vendorExtensions.put("x-formParamName", camelize(param.baseName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the HTTP method and return type
|
||||||
|
String returnType = op.returnType;
|
||||||
|
if (returnType == null || returnType.equals("null")) {
|
||||||
|
returnType = "()";
|
||||||
|
}
|
||||||
|
if (returnType.indexOf(" ") >= 0) {
|
||||||
|
returnType = "(" + returnType + ")";
|
||||||
|
}
|
||||||
|
path.add("Verb '" + op.httpMethod.toUpperCase() + " 200 '[JSON] " + returnType);
|
||||||
|
type.add("m " + returnType);
|
||||||
|
|
||||||
|
op.vendorExtensions.put("x-routeType", joinStrings(" :> ", path));
|
||||||
|
op.vendorExtensions.put("x-clientType", joinStrings(" -> ", type));
|
||||||
|
op.vendorExtensions.put("x-formName", "Form" + camelize(op.operationId));
|
||||||
|
for(CodegenParameter param : op.formParams) {
|
||||||
|
param.vendorExtensions.put("x-formPrefix", camelize(op.operationId, true));
|
||||||
|
}
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeQueryListType(String type, String collectionFormat) {
|
||||||
|
type = type.substring(1, type.length() - 1);
|
||||||
|
switch(collectionFormat) {
|
||||||
|
case "csv": return "(QueryList 'CommaSeparated (" + type + "))";
|
||||||
|
case "tsv": return "(QueryList 'TabSeparated (" + type + "))";
|
||||||
|
case "ssv": return "(QueryList 'SpaceSeparated (" + type + "))";
|
||||||
|
case "pipes": return "(QueryList 'PipeSeparated (" + type + "))";
|
||||||
|
case "multi": return "(QueryList 'MultiParamArray (" + type + "))";
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fixOperatorChars(String string) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String name = string;
|
||||||
|
//Check if it is a reserved word, in which case the underscore is added when property name is generated.
|
||||||
|
if (string.startsWith("_")) {
|
||||||
|
if (reservedWords.contains(string.substring(1, string.length()))) {
|
||||||
|
name = string.substring(1, string.length());
|
||||||
|
} else if (reservedWordsMappings.containsValue(string)) {
|
||||||
|
name = LEADING_UNDERSCORE.matcher(string).replaceFirst("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (char c : name.toCharArray()) {
|
||||||
|
String cString = String.valueOf(c);
|
||||||
|
if (specialCharReplacements.containsKey(cString)) {
|
||||||
|
sb.append("'");
|
||||||
|
sb.append(specialCharReplacements.get(cString));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove characters from a string that do not belong in a model classname
|
||||||
|
private String fixModelChars(String string) {
|
||||||
|
return string.replace(".", "").replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override fromModel to create the appropriate model namings
|
||||||
|
@Override
|
||||||
|
public CodegenModel fromModel(String name, Schema mod, Map<String, Schema> allDefinitions) {
|
||||||
|
CodegenModel model = super.fromModel(name, mod, allDefinitions);
|
||||||
|
|
||||||
|
// Clean up the class name to remove invalid characters
|
||||||
|
model.classname = fixModelChars(model.classname);
|
||||||
|
if(typeMapping.containsValue(model.classname)) {
|
||||||
|
model.classname += "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the model name, compute the prefix for the fields.
|
||||||
|
String prefix = camelize(model.classname, true);
|
||||||
|
for(CodegenProperty prop : model.vars) {
|
||||||
|
prop.name = toVarName(prefix + camelize(fixOperatorChars(prop.name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create newtypes for things with non-object types
|
||||||
|
String dataOrNewtype = "data";
|
||||||
|
if(model.dataType != "object" && typeMapping.containsKey(model.dataType)) {
|
||||||
|
String newtype = typeMapping.get(model.dataType);
|
||||||
|
model.vendorExtensions.put("x-customNewtype", newtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the prefix as a vendor extension, so that it can be used in the ToJSON and FromJSON instances.
|
||||||
|
model.vendorExtensions.put("x-prefix", prefix);
|
||||||
|
model.vendorExtensions.put("x-data", dataOrNewtype);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeQuotationMark(String input) {
|
||||||
|
// remove " to avoid code injection
|
||||||
|
return input.replace("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeUnsafeCharacters(String input) {
|
||||||
|
return input.replace("{-", "{_-").replace("-}", "-_}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
org.openapitools.codegen.languages.BashClientCodegen
|
org.openapitools.codegen.languages.BashClientCodegen
|
||||||
|
org.openapitools.codegen.languages.HaskellServantCodegen
|
||||||
org.openapitools.codegen.languages.ObjcClientCodegen
|
org.openapitools.codegen.languages.ObjcClientCodegen
|
||||||
org.openapitools.codegen.languages.PhpClientCodegen
|
org.openapitools.codegen.languages.PhpClientCodegen
|
||||||
org.openapitools.codegen.languages.RubyClientCodegen
|
org.openapitools.codegen.languages.RubyClientCodegen
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.openapitools.codegen.haskellservant;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.PathItem;
|
||||||
|
import org.openapitools.codegen.CodegenModel;
|
||||||
|
import org.openapitools.codegen.CodegenOperation;
|
||||||
|
import org.openapitools.codegen.CodegenProperty;
|
||||||
|
import org.openapitools.codegen.DefaultCodegen;
|
||||||
|
import org.openapitools.codegen.languages.HaskellServantCodegen;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.Operation;
|
||||||
|
import io.swagger.v3.oas.models.media.*;
|
||||||
|
import io.swagger.v3.parser.OpenAPIV3Parser;
|
||||||
|
import io.swagger.v3.parser.util.SchemaTypeUtil;
|
||||||
|
|
||||||
|
import org.testng.Assert;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
public class HaskellModelTest {
|
||||||
|
|
||||||
|
@Test(description = "convert a haskell model with dots")
|
||||||
|
public void modelTest() {
|
||||||
|
Assert.assertEquals(true, true);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.openapitools.codegen.haskellservant;
|
||||||
|
|
||||||
|
|
||||||
|
import org.openapitools.codegen.AbstractOptionsTest;
|
||||||
|
import org.openapitools.codegen.CodegenConfig;
|
||||||
|
import org.openapitools.codegen.languages.HaskellServantCodegen;
|
||||||
|
import org.openapitools.codegen.options.HaskellServantOptionsProvider;
|
||||||
|
|
||||||
|
import mockit.Expectations;
|
||||||
|
import mockit.Tested;
|
||||||
|
|
||||||
|
public class HaskellServantOptionsTest extends AbstractOptionsTest {
|
||||||
|
|
||||||
|
@Tested
|
||||||
|
private HaskellServantCodegen clientCodegen;
|
||||||
|
|
||||||
|
public HaskellServantOptionsTest() {
|
||||||
|
super(new HaskellServantOptionsProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodegenConfig getCodegenConfig() {
|
||||||
|
return clientCodegen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setExpectations() {
|
||||||
|
new Expectations(clientCodegen) {{
|
||||||
|
clientCodegen.setModelPackage(HaskellServantOptionsProvider.MODEL_PACKAGE_VALUE);
|
||||||
|
times = 1;
|
||||||
|
clientCodegen.setApiPackage(HaskellServantOptionsProvider.API_PACKAGE_VALUE);
|
||||||
|
times = 1;
|
||||||
|
clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(HaskellServantOptionsProvider.SORT_PARAMS_VALUE));
|
||||||
|
times = 1;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
@ -347,7 +347,7 @@ public class PhpModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// datetime (or primitive type) not yet supported in HTTP request body
|
// datetime (or primitive type) not yet supported in HTTP request body
|
||||||
@Test(description = "returns DateTime when using `--model-name-prefix`", enabled = false)
|
@Test(description = "returns DateTime when using `--model-name-prefix`", enabled = false)
|
||||||
public void dateTest() {
|
public void dateTest() {
|
||||||
final OpenAPI model = new OpenAPIV3Parser().read("src/test/resources/2_0/datePropertyTest.json");
|
final OpenAPI model = new OpenAPIV3Parser().read("src/test/resources/2_0/datePropertyTest.json");
|
||||||
final DefaultCodegen codegen = new PhpClientCodegen();
|
final DefaultCodegen codegen = new PhpClientCodegen();
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.openapitools.codegen.options;
|
||||||
|
|
||||||
|
import org.openapitools.codegen.CodegenConstants;
|
||||||
|
import org.openapitools.codegen.languages.HaskellServantCodegen;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HaskellServantOptionsProvider implements OptionsProvider {
|
||||||
|
public static final String MODEL_PACKAGE_VALUE = "Model";
|
||||||
|
public static final String API_PACKAGE_VALUE = "Api";
|
||||||
|
public static final String SORT_PARAMS_VALUE = "false";
|
||||||
|
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
|
||||||
|
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
|
||||||
|
public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLanguage() {
|
||||||
|
return "haskell";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> createOptions() {
|
||||||
|
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<String, String>();
|
||||||
|
return builder.put(CodegenConstants.MODEL_PACKAGE, MODEL_PACKAGE_VALUE)
|
||||||
|
.put(CodegenConstants.API_PACKAGE, API_PACKAGE_VALUE)
|
||||||
|
.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE)
|
||||||
|
.put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE)
|
||||||
|
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
|
||||||
|
.put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isServer() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user