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")
|
||||
public String getSchemaType(Schema schema) {
|
||||
// datatype is the OAI type (e.g. integer, long, etc)
|
||||
String datatype = null;
|
||||
|
||||
if (StringUtils.isNotBlank(schema.get$ref())) { // object
|
||||
@ -1141,7 +1142,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) {
|
||||
datatype = "long";
|
||||
} else {
|
||||
datatype = schema.getType();
|
||||
datatype = schema.getType(); // integer
|
||||
}
|
||||
} else if (schema instanceof MapSchema) {
|
||||
datatype = "map";
|
||||
@ -1565,7 +1566,6 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
String type = getSchemaType(p);
|
||||
//LOGGER.info("from property = " + p);
|
||||
if (p instanceof IntegerSchema || SchemaTypeUtil.INTEGER_TYPE.equals(p.getType())) {
|
||||
property.isNumeric = Boolean.TRUE;
|
||||
if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) {
|
||||
@ -1718,6 +1718,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
property.allowableValues = allowableValues;
|
||||
}
|
||||
}
|
||||
|
||||
property.datatype = getTypeDeclaration(p);
|
||||
property.dataFormat = p.getFormat();
|
||||
|
||||
@ -1978,7 +1979,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
Operation operation,
|
||||
Map<String, Schema> schemas,
|
||||
OpenAPI openAPI) {
|
||||
//LOGGER.info("fromOperation => operation: " + operation);
|
||||
LOGGER.debug("fromOperation => operation: " + operation);
|
||||
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
|
||||
Set<String> imports = new HashSet<String>();
|
||||
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
|
||||
@ -2191,9 +2192,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
|
||||
// add imports to operation import tag
|
||||
for (String i : imports) {
|
||||
LOGGER.info("debugging fromOperation imports: " + i);
|
||||
if (needToImport(i)) {
|
||||
LOGGER.info("debugging fromOperation imports: " + i + " imported");
|
||||
op.imports.add(i);
|
||||
}
|
||||
}
|
||||
@ -2671,6 +2670,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
setParameterExampleValue(codegenParameter);
|
||||
|
||||
postProcessParameter(codegenParameter);
|
||||
LOGGER.info("debugging 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.HaskellServantCodegen
|
||||
org.openapitools.codegen.languages.ObjcClientCodegen
|
||||
org.openapitools.codegen.languages.PhpClientCodegen
|
||||
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
|
||||
@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() {
|
||||
final OpenAPI model = new OpenAPIV3Parser().read("src/test/resources/2_0/datePropertyTest.json");
|
||||
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