diff --git a/modules/swagger-codegen/pom.xml b/modules/swagger-codegen/pom.xml
index a5c4eedd7406..2f05b690f187 100644
--- a/modules/swagger-codegen/pom.xml
+++ b/modules/swagger-codegen/pom.xml
@@ -115,6 +115,14 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.7
+ 1.7
+
+
diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
index 23f431d9a93b..ebfce0c763cd 100644
--- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
+++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
@@ -1,345 +1,490 @@
package io.swagger.codegen.languages;
import io.swagger.codegen.*;
+import io.swagger.models.ModelImpl;
+import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.*;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.*;
import java.io.File;
public class HaskellServantCodegen extends DefaultCodegen implements CodegenConfig {
- // source folder where to write the files
- protected String sourceFolder = "src";
- protected String apiVersion = "0.0.1";
+ // source folder where to write the files
+ protected String sourceFolder = "src";
+ protected String apiVersion = "0.0.1";
- /**
- * Configures the type of generator.
- *
- * @return the CodegenType for this generator
- * @see io.swagger.codegen.CodegenType
- */
- 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-servant";
- }
-
- /**
- * 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 HaskellServantCodegen library.";
- }
-
- public HaskellServantCodegen() {
- super();
-
- // set the output folder here
- outputFolder = "generated-code/HaskellServantCodegen";
+ // How to encode special characters like $
+ // They are translated to words like "Dollar" and prefixed with '
+ // Then translated back during JSON encoding and decoding
+ private Map specialCharReplacements = new HashMap();
/**
- * Models. You can write model files using the modelTemplateFiles map.
- * if you want to create one template for file, you can do so here.
- * for multiple files for model, just put another entry in the `modelTemplateFiles` with
- * a different extension
+ * Configures the type of generator.
+ *
+ * @return the CodegenType for this generator
+ * @see io.swagger.codegen.CodegenType
*/
- modelTemplateFiles.put(
- "model.mustache", // the template to use
- ".hs"); // the extension for each file to write
+ public CodegenType getTag() {
+ return CodegenType.SERVER;
+ }
/**
- * Api classes. You can write classes for each Api file with the apiTemplateFiles map.
- * as with models, add multiple entries with different extensions for multiple files per
- * class
+ * 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
*/
- apiTemplateFiles.put(
- "api.mustache", // the template to use
- ".hs"); // the extension for each file to write
+ 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();
+
+ // Initialize special characters
+ specialCharReplacements.put('$', "Dollar");
+ specialCharReplacements.put('^', "Caret");
+ specialCharReplacements.put('|', "Pipe");
+ specialCharReplacements.put('=', "Equal");
+ specialCharReplacements.put('*', "Star");
+ specialCharReplacements.put('-', "Dash");
+ specialCharReplacements.put('&', "Ampersand");
+ specialCharReplacements.put('%', "Percent");
+ specialCharReplacements.put('#', "Hash");
+ specialCharReplacements.put('@', "At");
+ specialCharReplacements.put('!', "Exclamation");
+ specialCharReplacements.put('+', "Plus");
+
+
+ // 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";
+ embeddedTemplateDir = templateDir = "haskell-servant";
- /**
+ /*
* Api Package. Optional, if needed, this can be used in templates
*/
- apiPackage = "Api";
+ apiPackage = "API";
- /**
+ /*
* Model Package. Optional, if needed, this can be used in templates
*/
- modelPackage = "Model";
+ modelPackage = "Types";
- /**
- * Reserved words. Override this with reserved words specific to your language
- */
- // from https://wiki.haskell.org/Keywords
- setReservedWordsLowerCase(
- Arrays.asList(
- "as", "case", "of",
- "class", "data", // "data family", "data instance",
- "default", "deriving", // "deriving instance",
- "do",
- "forall", "foreign", "hiding",
- "id",
- "if", "then", "else",
- "import", "infix", "infixl", "infixr",
- "instance", "let", "in",
- "mdo", "module", "newtype",
- "proc", "qualified", "rec",
- "type", // "type family", "type instance",
- "where"
- )
- );
+ // 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);
+ 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("haskell-servant-codegen.mustache", "", "haskell-servant-codegen.cabal"));
- supportingFiles.add(new SupportingFile("Setup.mustache", "", "Setup.hs"));
- supportingFiles.add(new SupportingFile("LICENSE", "", "LICENSE"));
- supportingFiles.add(new SupportingFile("Apis.mustache", "lib", "Apis.hs"));
- supportingFiles.add(new SupportingFile("Utils.mustache", "lib", "Utils.hs"));
- supportingFiles.add(new SupportingFile("Client.mustache", "client", "Main.hs"));
- supportingFiles.add(new SupportingFile("Server.mustache", "server", "Main.hs"));
+ 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(
- Arrays.asList(
- "Bool",
- "String",
- "Int",
- "Integer",
- "Float",
- "Char",
- "Double",
- "List",
- "FilePath"
- )
- );
+ languageSpecificPrimitives = new HashSet(
+ Arrays.asList(
+ "Bool",
+ "String",
+ "Int",
+ "Integer",
+ "Float",
+ "Char",
+ "Double",
+ "List",
+ "FilePath"
+ )
+ );
- typeMapping.clear();
- // typeMapping.put("enum", "NSString");
- typeMapping.put("array", "List");
- typeMapping.put("set", "Set");
- typeMapping.put("boolean", "Bool");
- typeMapping.put("string", "String");
- typeMapping.put("int", "Int");
- typeMapping.put("long", "Integer");
- typeMapping.put("float", "Float");
- // typeMapping.put("byte", "Byte");
- typeMapping.put("short", "Int");
- typeMapping.put("char", "Char");
- typeMapping.put("double", "Double");
- typeMapping.put("DateTime", "Integer");
- // typeMapping.put("object", "Map");
- typeMapping.put("file", "FilePath");
+ typeMapping.clear();
+ typeMapping.put("array", "List");
+ typeMapping.put("set", "Set");
+ typeMapping.put("boolean", "Bool");
+ typeMapping.put("string", "Text");
+ typeMapping.put("int", "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("integer", "Int");
- importMapping.clear();
- importMapping.put("Map", "qualified Data.Map as Map");
+ 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 reseved words
- *
- * @return the escaped term
- */
- @Override
- public String escapeReservedWord(String name) {
- return name + "_";
- }
-
- /**
- * Location to write model files. You can use the modelPackage() as defined when the class is
- * instantiated
- */
- public String modelFileFolder() {
- return outputFolder + File.separatorChar + "lib" + File.separatorChar + modelPackage().replace('.', File.separatorChar);
- }
-
- /**
- * Location to write api files. You can use the apiPackage() as defined when the class is
- * instantiated
- */
- @Override
- public String apiFileFolder() {
- return outputFolder + File.separatorChar + "lib" + File.separatorChar + apiPackage().replace('.', File.separatorChar);
- }
-
- /**
- * 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(Property p) {
- if(p instanceof ArrayProperty) {
- ArrayProperty ap = (ArrayProperty) p;
- Property inner = ap.getItems();
- return "[" + getTypeDeclaration(inner) + "]";
+ cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
+ cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
}
- else if (p instanceof MapProperty) {
- MapProperty mp = (MapProperty) p;
- Property inner = mp.getAdditionalProperties();
- return "Map.Map String " + getTypeDeclaration(inner);
- }
- return super.getTypeDeclaration(p);
- }
- /**
- * Optional - swagger type conversion. This is used to map swagger types in a `Property` 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
- * @see io.swagger.models.properties.Property
- */
- @Override
- public String getSwaggerType(Property p) {
- String swaggerType = super.getSwaggerType(p);
- String type = null;
- if(typeMapping.containsKey(swaggerType)) {
- type = typeMapping.get(swaggerType);
- if(languageSpecificPrimitives.contains(type))
+ /**
+ * 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 reseved words
+ *
+ * @return the escaped term
+ */
+ @Override
+ public String escapeReservedWord(String name) {
+ return name + "_";
+ }
+
+ @Override
+ public void preprocessSwagger(Swagger swagger) {
+ // From the title, compute a reasonable name for the package and the API
+ String title = swagger.getInfo().getTitle();
+
+ // Drop any API suffix
+ 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 wordsLower = new ArrayList();
+ 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 wordsCaps = new ArrayList();
+ for (String word : words) {
+ wordsCaps.add(word.substring(0, 1).toUpperCase() + word.substring(1));
+ }
+ 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", apiName.substring(0, 1).toLowerCase() + apiName.substring(1));
+ additionalProperties.put("package", cabalName);
+
+ List> replacements = new ArrayList<>();
+ Object[] replacementChars = specialCharReplacements.keySet().toArray();
+ for(int i = 0; i < replacementChars.length; i++) {
+ Character c = (Character) replacementChars[i];
+ Map o = new HashMap<>();
+ o.put("char", Character.toString(c));
+ o.put("replacement", "'" + specialCharReplacements.get(c));
+ o.put("hasMore", i != replacementChars.length - 1);
+ replacements.add(o);
+ }
+ additionalProperties.put("specialCharReplacements", replacements);
+
+ super.preprocessSwagger(swagger);
+ }
+
+
+ /**
+ * 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(Property p) {
+ if (p instanceof ArrayProperty) {
+ ArrayProperty ap = (ArrayProperty) p;
+ Property inner = ap.getItems();
+ return "[" + getTypeDeclaration(inner) + "]";
+ } else if (p instanceof MapProperty) {
+ MapProperty mp = (MapProperty) p;
+ Property inner = mp.getAdditionalProperties();
+ return "Map.Map String " + getTypeDeclaration(inner);
+ }
+ return super.getTypeDeclaration(p);
+ }
+
+ /**
+ * Optional - swagger type conversion. This is used to map swagger types in a `Property` 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
+ * @see io.swagger.models.properties.Property
+ */
+ @Override
+ public String getSwaggerType(Property p) {
+ String swaggerType = super.getSwaggerType(p);
+ String type = null;
+ if (typeMapping.containsKey(swaggerType)) {
+ type = typeMapping.get(swaggerType);
+ if (languageSpecificPrimitives.contains(type))
+ return toModelName(type);
+ } else {
+ type = swaggerType;
+ }
return toModelName(type);
}
- else
- type = swaggerType;
- return toModelName(type);
- }
- private String capturePath(String path, List pathParams) {
- for (CodegenParameter p : pathParams) {
- String pName = "{"+p.baseName+"}";
- if (path.indexOf(pName) >= 0) {
- path = path.replace(pName, "Capture " + "\""+p.baseName+"\" " + p.dataType);
- }
+ @Override
+ public String toInstantiationType(Property p) {
+ if (p instanceof MapProperty) {
+ MapProperty ap = (MapProperty) p;
+ Property additionalProperties2 = ap.getAdditionalProperties();
+ String type = additionalProperties2.getType();
+ if (null == type) {
+ LOGGER.error("No Type defined for Additional Property " + additionalProperties2 + "\n" //
+ + "\tIn Property: " + p);
+ }
+ String inner = getSwaggerType(additionalProperties2);
+ return "(Map.Map Text " + inner + ")";
+ } else if (p instanceof ArrayProperty) {
+ ArrayProperty ap = (ArrayProperty) p;
+ String inner = getSwaggerType(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;
+ }
}
- return path;
- }
- private String queryPath(String path, List queryParams) {
- for (CodegenParameter p : queryParams) {
- path += " :> QueryParam \"" + p.baseName + "\" " + p.dataType;
+
+ // Intersperse a separator string between a list of strings, like String.join.
+ private String joinStrings(String sep, List ss) {
+ StringBuilder sb = new StringBuilder();
+ for (String s : ss) {
+ if (sb.length() > 0) {
+ sb.append(sep);
+ }
+ sb.append(s);
+ }
+ return sb.toString();
}
- return path;
- }
- private String bodyPath(String path, List bodyParams) {
- for (CodegenParameter p : bodyParams) {
- path += " :> ReqBody '[JSON] " + p.dataType;
+ // 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 pathToServantRoute(String path, List pathParams) {
+ // Map the capture params by their names.
+ HashMap captureTypes = new HashMap();
+ 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 pathComponents = new ArrayList();
+ 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;
}
- return path;
- }
- private String formPath(String path, List formParams) {
- String names = "Form";
- for (CodegenParameter p : formParams) {
- if(p.dataType.equals("FilePath")){
- // file data processing
- }
- names += p.baseName;
+ // Extract the arguments that are passed in the route path parameters
+ private List pathToClientType(String path, List pathParams) {
+ // Map the capture params by their names.
+ HashMap captureTypes = new HashMap();
+ 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 type = new ArrayList();
+ 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;
}
- if(formParams.size() > 0){
- path += " :> ReqBody '[FormUrlEncoded] " + names;
+
+
+ @Override
+ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map definitions, Swagger swagger) {
+ CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger);
+
+ List path = pathToServantRoute(op.path, op.pathParams);
+ List type = pathToClientType(op.path, op.pathParams);
+
+ // Query parameters appended to routes
+ for (CodegenParameter param : op.queryParams) {
+ String paramType = param.dataType;
+ if(param.isListContainer != null && param.isListContainer) {
+ 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 != null && param.isListContainer) {
+ paramType = makeQueryListType(paramType, param.collectionFormat);
+ }
+ type.add("Maybe " + paramType);
+ }
+
+ // 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(camelize(op.httpMethod.toLowerCase()) + " '[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;
}
- return path;
- }
- private String headerPath(String path, List headerParams) {
- for (CodegenParameter p : headerParams) {
- path += " :> Header \"" + p.baseName + "\" " + p.dataType;
+ 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 NotImplementedException();
+ }
}
- return path;
- }
-
- private String filterReturnType(String rt) {
- if (rt == null || rt.equals("null")) {
- return "()";
- } else if (rt.indexOf(" ") >= 0) {
- return "(" + rt + ")";
+ private String fixOperatorChars(String string) {
+ StringBuilder sb = new StringBuilder();
+ for (char c : string.toCharArray()) {
+ if (specialCharReplacements.containsKey(c)) {
+ sb.append(specialCharReplacements.get(c));
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
}
- return rt;
- }
- private String addReturnPath(String path, String httpMethod, String returnType) {
- return path + " :> " + upperCaseFirst(httpMethod) + " '[JSON] " + filterReturnType(returnType);
- }
+ // Override fromModel to create the appropriate model namings
+ @Override
+ public CodegenModel fromModel(String name, Model mod, Map allDefinitions) {
+ CodegenModel model = super.fromModel(name, mod, allDefinitions);
- private String joinStrings(String sep, List ss) {
- StringBuilder sb = new StringBuilder();
- for (String s : ss) {
- if (sb.length() > 0) {
- sb.append(sep);
- }
- sb.append(s);
+ // From the model name, compute the prefix for the fields.
+ String prefix = camelize(model.classname, true);
+ for(CodegenProperty prop : model.vars) {
+ prop.name = prefix + camelize(fixOperatorChars(prop.name));
+ }
+
+ // Create newtypes for things with non-object types
+ String dataOrNewtype = "data";
+ String modelType = ((ModelImpl) mod).getType();
+ if(modelType != "object" && typeMapping.containsKey(modelType)) {
+ String newtype = typeMapping.get(modelType);
+ 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;
}
- return sb.toString();
- }
- private String replacePathSplitter(String path) {
- String[] ps = path.replaceFirst("/", "").split("/", 0);
- List rs = new ArrayList();
- for (String p : ps) {
- if (p.indexOf("{") < 0) {
- rs.add("\"" + p + "\"");
- } else {
- rs.add(p);
- }
+ @Override
+ public CodegenParameter fromParameter(Parameter param, Set imports) {
+ CodegenParameter p = super.fromParameter(param, imports);
+ p.vendorExtensions.put("x-formParamName", camelize(p.baseName));
+ return p;
}
- return joinStrings(" :> ", rs);
- }
-
- private String upperCaseFirst(String str) {
- char[] array = str.toLowerCase().toCharArray();
- array[0] = Character.toUpperCase(array[0]);
- return new String(array);
- }
-
- private String parseScheme(String basePath) {
- return "Http";
- }
-
- @Override
- public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map definitions, Swagger swagger){
- CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger);
- String path = op.path;
- op.nickname = addReturnPath(headerPath(formPath(bodyPath(queryPath(capturePath(replacePathSplitter(path), op.pathParams), op.queryParams), op.bodyParams), op.formParams), op.headerParams), op.httpMethod, op.returnType);
- return op;
- }
}
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/API.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/API.mustache
new file mode 100644
index 000000000000..fef77e9b1cb0
--- /dev/null
+++ b/modules/swagger-codegen/src/main/resources/haskell-servant/API.mustache
@@ -0,0 +1,150 @@
+{-# LANGUAGE DataKinds, TypeFamilies, TypeOperators, FlexibleInstances, OverloadedStrings, ViewPatterns #-}
+{-# LANGUAGE RecordWildCards, GeneralizedNewtypeDeriving, DeriveTraversable, FlexibleContexts, DeriveGeneric #-}
+module {{title}}.API (
+ -- * Client and Server
+ ServerConfig(..),
+ {{title}}Backend,
+ create{{title}}Client,
+ run{{title}}Server,
+ -- ** Servant
+ {{title}}API,
+ ) where
+
+import {{title}}.Types
+
+import Data.Coerce (coerce)
+import Servant.API
+import Servant (serve, ServantErr)
+import qualified Network.Wai.Handler.Warp as Warp
+import Control.Monad.Trans.Either (EitherT)
+import qualified Data.Text as T
+import Data.Text (Text)
+import Servant.Common.BaseUrl(BaseUrl(..))
+import Servant.Client (ServantError, client, Scheme(..))
+import Data.Proxy (Proxy(..))
+import Control.Monad.IO.Class
+import Data.Function ((&))
+import GHC.Exts (IsString(..))
+import qualified Data.Map as Map
+import GHC.Generics (Generic)
+import Data.Monoid ((<>))
+
+
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#hasFormParams}}
+data {{vendorExtensions.x-formName}} = {{vendorExtensions.x-formName}}
+ { {{#formParams}}{{vendorExtensions.x-formPrefix}}{{vendorExtensions.x-formParamName}} :: {{dataType}}{{#hasMore}}
+ , {{/hasMore}}{{/formParams}}
+ } deriving (Show, Eq, Generic)
+
+instance FromFormUrlEncoded {{vendorExtensions.x-formName}} where
+ fromFormUrlEncoded inputs = {{vendorExtensions.x-formName}} <$> {{#formParams}} lookupEither "{{baseName}}" inputs{{#hasMore}} <*> {{/hasMore}}{{/formParams}}
+instance ToFormUrlEncoded {{vendorExtensions.x-formName}} where
+ toFormUrlEncoded value = [{{#formParams}}("{{baseName}}", toText $ {{vendorExtensions.x-formPrefix}}{{vendorExtensions.x-formParamName}} value){{#hasMore}}, {{/hasMore}}{{/formParams}}]
+{{/hasFormParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
+
+-- For the form data code generation.
+lookupEither :: FromText b => Text -> [(Text, Text)] -> Either String b
+lookupEither key assocs =
+ case lookup key assocs >>= fromText of
+ Nothing -> Left $ T.unpack $ "Could not find parameter " <> key <> " in form data"
+ Just value -> Right value
+
+{{#apiInfo}}
+-- | Servant type-level API, generated from the Swagger spec for {{title}}.
+type {{title}}API
+ = {{#apis}}{{#operations}}{{#operation}}{{& vendorExtensions.x-routeType}} -- '{{operationId}}' route{{#hasMore}}
+ :<|> {{/hasMore}}{{/operation}}{{/operations}}{{#hasMore}}
+ :<|> {{/hasMore}}{{/apis}}
+{{/apiInfo}}
+
+-- | Server or client configuration, specifying the host and port to query or serve on.
+data ServerConfig = ServerConfig {
+ configHost :: String, -- ^ Hostname to serve on, e.g. "127.0.0.1"
+ configPort :: Int -- ^ Port to serve on, e.g. 8080
+ } deriving (Eq, Ord, Show, Read)
+
+-- | List of elements parsed from a query.
+newtype QueryList (p :: CollectionFormat) a = QueryList { fromQueryList :: [a] }
+ deriving (Functor, Applicative, Monad, Foldable, Traversable)
+
+-- | Formats in which a list can be encoded into a HTTP path.
+data CollectionFormat = CommaSeparated -- ^ CSV format for multiple parameters.
+ | SpaceSeparated -- ^ Also called "SSV"
+ | TabSeparated -- ^ Also called "TSV"
+ | PipeSeparated -- ^ `value1|value2|value2`
+ | MultiParamArray -- ^ Using multiple GET parameters, e.g. `foo=bar&foo=baz`. Only for GET params.
+
+instance FromText a => FromText (QueryList 'CommaSeparated a) where
+ fromText = parseSeparatedQueryList ','
+
+instance FromText a => FromText (QueryList 'TabSeparated a) where
+ fromText = parseSeparatedQueryList '\t'
+
+instance FromText a => FromText (QueryList 'SpaceSeparated a) where
+ fromText = parseSeparatedQueryList ' '
+
+instance FromText a => FromText (QueryList 'PipeSeparated a) where
+ fromText = parseSeparatedQueryList '|'
+
+instance FromText a => FromText (QueryList 'MultiParamArray a) where
+ fromText = error "unimplemented FromText for MultiParamArray collection format"
+
+parseSeparatedQueryList :: FromText a => Char -> Text -> Maybe (QueryList p a)
+parseSeparatedQueryList char = fmap QueryList . mapM fromText . T.split (== char)
+
+instance ToText a => ToText (QueryList 'CommaSeparated a) where
+ toText = formatSeparatedQueryList ','
+
+instance ToText a => ToText (QueryList 'TabSeparated a) where
+ toText = formatSeparatedQueryList '\t'
+
+instance ToText a => ToText (QueryList 'SpaceSeparated a) where
+ toText = formatSeparatedQueryList ' '
+
+instance ToText a => ToText (QueryList 'PipeSeparated a) where
+ toText = formatSeparatedQueryList '|'
+
+instance ToText a => ToText (QueryList 'MultiParamArray a) where
+ toText = error "unimplemented ToText for MultiParamArray collection format"
+
+formatSeparatedQueryList :: ToText a => Char -> QueryList p a -> Text
+formatSeparatedQueryList char = T.intercalate (T.singleton char) . map toText . fromQueryList
+
+
+{{#apiInfo}}
+-- | Backend for {{title}}.
+-- The backend can be used both for the client and the server. The client generated from the {{title}} Swagger spec
+-- is a backend that executes actions by sending HTTP requests (see @create{{title}}Client@). Alternatively, provided
+-- a backend, the API can be served using @run{{title}}Server@.
+data {{title}}Backend m = {{title}}Backend {
+ {{#apis}}{{#operations}}{{#operation}}{{operationId}} :: {{& vendorExtensions.x-clientType}}{- ^ {{& notes}} -}{{#hasMore}},
+ {{/hasMore}}{{/operation}}{{/operations}}{{#hasMore}},
+ {{/hasMore}}{{/apis}}
+ }
+{{/apiInfo}}
+
+
+{{#apiInfo}}
+create{{title}}Client :: ServerConfig -> {{title}}Backend (EitherT ServantError IO)
+create{{title}}Client clientConfig = {{title}}Backend{..}
+ where
+ -- Use a strange variable name to avoid conflicts in autogenerated code... (no hygienic templates)
+ servantBaseUrlForClient3928 = BaseUrl Http (configHost clientConfig) (configPort clientConfig)
+ ({{#apis}}{{#operations}}{{#operation}}(coerce -> {{operationId}}){{#hasMore}} :<|>
+ {{/hasMore}}{{/operation}}{{/operations}}{{#hasMore}} :<|>
+ {{/hasMore}}{{/apis}}) = client (Proxy :: Proxy {{title}}API) servantBaseUrlForClient3928
+{{/apiInfo}}
+
+{{#apiInfo}}
+-- | Run the {{title}} server at the provided host and port.
+run{{title}}Server :: MonadIO m => ServerConfig -> {{title}}Backend (EitherT ServantErr IO) -> m ()
+run{{title}}Server ServerConfig{..} backend =
+ liftIO $ Warp.runSettings warpSettings $ serve (Proxy :: Proxy {{title}}API) (serverFromBackend backend)
+
+ where
+ warpSettings = Warp.defaultSettings & Warp.setPort configPort & Warp.setHost (fromString configHost)
+ serverFromBackend {{title}}Backend{..} =
+ ({{#apis}}{{#operations}}{{#operation}}coerce {{operationId}}{{#hasMore}} :<|>
+ {{/hasMore}}{{/operation}}{{/operations}}{{#hasMore}} :<|>
+ {{/hasMore}}{{/apis}})
+{{/apiInfo}}
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache
deleted file mode 100644
index 3b60afafec67..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache
+++ /dev/null
@@ -1,26 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE FlexibleInstances #-}
-module Apis (
- api
- , API
- ) where
-
-{{#apiInfo}}
-{{#apis}}
-import {{package}}.{{classname}} ({{classname}})
-{{/apis}}
-{{/apiInfo}}
-
-import Data.Proxy
-import Servant.API
-import Test.QuickCheck
-import qualified Data.Map as Map
-import Utils
-
-type API = {{#apiInfo}}{{#apis}}{{classname}}{{#hasMore}} :<|> {{/hasMore}}{{/apis}}{{/apiInfo}}
-
-api :: Proxy API
-api = Proxy
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache
deleted file mode 100644
index ab48bcbc43e9..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache
+++ /dev/null
@@ -1,35 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-
-module Main where
-
-import Control.Monad (void)
-import Control.Monad.Trans.Either
-import Control.Monad.IO.Class
-import Servant.API
-import Servant.Client
-
-import Data.List.Split (splitOn)
-import Network.URI (URI (..), URIAuth (..), parseURI)
-import Data.Maybe (fromMaybe)
-import Test.QuickCheck
-import Control.Monad
-{{#models}}
-import {{importPath}}
-{{/models}}
-{{#apiInfo}}
-{{#apis}}
-import {{package}}.{{classname}}
-{{/apis}}
-{{/apiInfo}}
-
--- userClient :: IO ()
--- userClient = do
--- users <- sample' (arbitrary :: Gen String)
--- let user = last users
--- void . runEitherT $ do
--- getUserByName user >>= (liftIO . putStrLn . show)
-
-main :: IO ()
-main = putStrLn "Hello Server!"
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE b/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE
deleted file mode 100644
index b0033f5f837b..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2015 Masahiro Yamauchi
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache
index c8f4b4bbb9db..67068d96ccd4 100644
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache
+++ b/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache
@@ -1,7 +1,79 @@
-# Generated Servant Codes
+# Auto-Generated Swagger Bindings to `{{title}}`
-## How to use
+The library in `lib` provides auto-generated-from-Swagger bindings to the {{title}} API.
-0. Install haskell-stack
-1. stack build
-2. stack exec client
+## Installation
+
+Installation follows the standard approach to installing Stack-based projects.
+
+1. Install the [Haskell `stack` tool](http://docs.haskellstack.org/en/stable/README).
+2. Run `stack install` to install this package.
+
+## Main Interface
+
+The main interface to this library is in the `{{title}}.API` module, which exports the {{title}}Backend type. The {{title}}Backend
+type can be used to create and define servers and clients for the API.
+
+## Creating a Client
+
+A client can be created via the `create{{title}}Client` function, which, if provided with a hostname and a port, will generate
+a client that can be used to access the API if it is being served at that hostname / port combination. For example, if
+`localhost:8080` is serving the {{title}} API, you can write:
+
+```haskell
+{-# LANGUAGE RecordWildCards #-}
+
+import {{title}}.API
+
+main :: IO ()
+main = do
+ {{title}}Backend{..} <- create{{title}}Client (ServerConfig "localhost" 8080)
+ -- Any {{title}} API call can go here.
+ return ()
+```
+
+## Creating a Server
+
+In order to create a server, you must use the `run{{title}}Server` function. However, you unlike the client, in which case you *got* a `{{title}}Backend`
+from the library, you must instead *provide* a `{{title}}Backend`. For example, if you have defined handler functions for all the
+functions in `{{title}}.Handlers`, you can write:
+
+```haskell
+{-# LANGUAGE RecordWildCards #-}
+
+import {{title}}.API
+
+-- A module you wrote yourself, containing all handlers needed for the {{title}}Backend type.
+import {{title}}.Handlers
+
+-- Run a {{title}} server on localhost:8080
+main :: IO ()
+main = do
+ let server = {{title}}Backend{..}
+ run{{title}}Server (ServerConfig "localhost" 8080) server
+```
+
+You could use `optparse-applicative` or a similar library to read the host and port from command-line arguments:
+```
+{-# LANGUAGE RecordWildCards #-}
+
+module Main (main) where
+
+import {{title}}.API (run{{title}}Server, {{title}}Backend(..), ServerConfig(..))
+
+import Control.Applicative ((<$>), (<*>))
+import Options.Applicative (execParser, option, str, auto, long, metavar, help)
+
+main :: IO ()
+main = do
+ config <- parseArguments
+ run{{title}}Server config {{title}}Backend{}
+
+-- | Parse host and port from the command line arguments.
+parseArguments :: IO ServerConfig
+parseArguments =
+ execParser $
+ ServerConfig
+ <$> option str (long "host" <> metavar "HOST" <> help "Host to serve on")
+ <*> option auto (long "port" <> metavar "PORT" <> help "Port to serve on")
+```
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache
deleted file mode 100644
index 68b4ff6ce33d..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache
+++ /dev/null
@@ -1,13 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-
-module Main where
-
-import Apis
-import Servant
-import Servant.Mock
-import qualified Network.Wai.Handler.Warp as Warp
-
-main :: IO ()
-main = Warp.run 8080 $ serve api (mock api)
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Types.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Types.mustache
new file mode 100644
index 000000000000..96abb461fb93
--- /dev/null
+++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Types.mustache
@@ -0,0 +1,63 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module {{title}}.Types (
+{{#models}}
+{{#model}}
+ {{classname}} (..),
+{{/model}}
+{{/models}}
+ ) where
+
+import Data.List (stripPrefix)
+import Data.Maybe (fromMaybe)
+import Data.Aeson (FromJSON(..), ToJSON(..), genericToJSON, genericParseJSON)
+import Data.Aeson.Types (Options(..), defaultOptions)
+import Data.Text (Text)
+import qualified Data.Text as T
+import GHC.Generics (Generic)
+import Data.Function ((&))
+{{#imports}}import {{import}}
+{{/imports}}
+
+{{#models}}
+{{#model}}
+
+-- | {{description}}
+{{^vendorExtensions.x-customNewtype}}
+{{^parent}}
+{{vendorExtensions.x-data}} {{classname}} = {{classname}}
+ { {{#vars}}{{& name}} :: {{datatype}} -- ^ {{& description}}{{#hasMore}}
+ , {{/hasMore}}{{/vars}}
+ } deriving (Show, Eq, Generic)
+
+instance FromJSON {{classname}} where
+ parseJSON = genericParseJSON (removeFieldLabelPrefix True "{{vendorExtensions.x-prefix}}")
+instance ToJSON {{classname}} where
+ toJSON = genericToJSON (removeFieldLabelPrefix False "{{vendorExtensions.x-prefix}}")
+{{/parent}}
+{{#parent}}
+newtype {{classname}} = {{classname}} { un{{classname}} :: {{parent}} }
+ deriving (Show, Eq, FromJSON, ToJSON, Generic)
+{{/parent}}
+{{/vendorExtensions.x-customNewtype}}
+{{#vendorExtensions.x-customNewtype}}
+newtype {{classname}} = {{classname}} {{vendorExtensions.x-customNewtype}} deriving (Show, Eq, FromJSON, ToJSON, Generic)
+{{/vendorExtensions.x-customNewtype}}
+{{/model}}
+{{/models}}
+
+-- Remove a field label prefix during JSON parsing.
+-- Also perform any replacements for special characters.
+removeFieldLabelPrefix :: Bool -> String -> Options
+removeFieldLabelPrefix forParsing prefix =
+ defaultOptions
+ { fieldLabelModifier = fromMaybe (error ("did not find prefix " ++ prefix)) . stripPrefix prefix . replaceSpecialChars
+ }
+ where
+ replaceSpecialChars field = foldl (&) field (map mkCharReplacement specialChars)
+ specialChars = [{{#specialCharReplacements}}("{{char}}", "{{&replacement}}"){{#hasMore}}, {{/hasMore}}{{/specialCharReplacements}}]
+ mkCharReplacement (replaceStr, searchStr) = T.unpack . replacer (T.pack searchStr) (T.pack replaceStr) . T.pack
+ replacer = if forParsing then flip T.replace else T.replace
+
+
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache
deleted file mode 100644
index f6db2602ce89..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache
+++ /dev/null
@@ -1,27 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE FlexibleInstances #-}
-module Utils where
-
-import GHC.Generics
-import Servant.API
-import Data.List (intercalate)
-import Data.List.Split (splitOn)
-import qualified Data.Map as Map
-import qualified Data.Text as T
-import Test.QuickCheck
-
-instance FromText [String] where
- fromText = Just . splitOn "," . T.unpack
-
-instance ToText [String] where
- toText = T.pack . intercalate ","
-
-lkp inputs l = case lookup l inputs of
- Nothing -> Left $ "label " ++ T.unpack l ++ " not found"
- Just v -> Right $ read (T.unpack v)
-
-instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map.Map k v) where
- arbitrary = Map.fromList <$> arbitrary
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache
deleted file mode 100644
index c49fcd5eff8c..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache
+++ /dev/null
@@ -1,82 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-{{#operations}}
-module {{package}}.{{classname}} (
- {{#operation}}{{operationId}}{{#hasMore}}
- , {{/hasMore}}{{/operation}}
- , proxy{{classname}}
- , {{classname}}
- ) where
-{{/operations}}
-
-import GHC.Generics
-import Data.Proxy
-import Servant.API
-import Servant.Client
-import Network.URI (URI (..), URIAuth (..), parseURI)
-import Data.Maybe (fromMaybe)
-import Servant.Common.Text
-import Data.List (intercalate)
-import qualified Data.Text as T
-import Utils
-import Test.QuickCheck
-{{#imports}}import {{import}}
-{{/imports}}
-
-{{#operations}}
-{{#operation}}
-{{#hasFormParams}}
-data Form{{#formParams}}{{baseName}}{{/formParams}} = Form{{#formParams}}{{baseName}}{{/formParams}}
- { {{#formParams}}{{baseName}} :: {{dataType}}{{#hasMore}}
- , {{/hasMore}}{{/formParams}}
- } deriving (Show, Eq, Generic)
-
-instance FromFormUrlEncoded Form{{#formParams}}{{baseName}}{{/formParams}} where
- fromFormUrlEncoded inputs = Form{{#formParams}}{{baseName}}{{/formParams}} <$> {{#formParams}} lkp inputs "{{baseName}}"{{#hasMore}} <*> {{/hasMore}}{{/formParams}}
-instance ToFormUrlEncoded Form{{#formParams}}{{baseName}}{{/formParams}} where
- toFormUrlEncoded x = [({{#formParams}}(T.pack $ show $ {{package}}.{{classname}}.{{baseName}} x){{#hasMore}}, {{/hasMore}}{{/formParams}})]
-instance Arbitrary Form{{#formParams}}{{baseName}}{{/formParams}} where
- arbitrary = Form{{#formParams}}{{baseName}}{{/formParams}} <$> {{#formParams}}arbitrary{{#hasMore}} <*> {{/hasMore}}{{/formParams}}
-{{/hasFormParams}}
-
-{{/operation}}
-{{/operations}}
-
-{{#operations}}
-type {{classname}} = {{#operation}}{{& nickname}} -- {{operationId}}{{#hasMore}}
- :<|> {{/hasMore}}{{/operation}}
-{{/operations}}
-
-proxy{{classname}} :: Proxy {{classname}}
-proxy{{classname}} = Proxy
-
-{{#operations}}
-
-serverPath :: String
-serverPath = "{{basePath}}"
-
-parseHostPort :: String -> (String, Int)
-parseHostPort path = (host,port)
- where
- authority = case parseURI path of
- Just x -> uriAuthority x
- _ -> Nothing
- (host, port) = case authority of
- Just y -> (uriRegName y, (getPort . uriPort) y)
- _ -> ("localhost", 8080)
- getPort p = case (length p) of
- 0 -> 80
- _ -> (read . drop 1) p
-
-(host, port) = parseHostPort serverPath
-
-{{#operation}}
-{{operationId}}{{#hasMore}}
- :<|> {{/hasMore}}{{/operation}}
- = client proxy{{classname}} $ BaseUrl Http host port
-{{/operations}}
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache
index 8b7d7fc54b20..eef9ce0b10b3 100644
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache
+++ b/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache
@@ -1,62 +1,29 @@
-name: haskell-servant-codegen
+name: {{package}}
version: 0.1.0.0
-synopsis: Swagger-codegen example for Haskell servant
+synopsis: Auto-generated API bindings for {{package}}
description: Please see README.md
homepage: https://github.com/swagger-api/swagger-codegen#readme
-license: Apache-2.0
-license-file: LICENSE
-author: Masahiro Yamauchi
-maintainer: sgt.yamauchi@gmail.com
-copyright: 2015- Masahiro Yamauchi
+author: Author Name Here
+maintainer: author.name@email.com
+copyright: YEAR - AUTHOR
category: Web
build-type: Simple
--- extra-source-files:
cabal-version: >=1.10
library
hs-source-dirs: lib
- exposed-modules: Utils{{#models}}{{#model}}
- , {{importPath}}{{/model}}{{/models}}
-{{#apiInfo}}
-{{#apis}}
- , {{package}}.{{classname}}
-{{/apis}}
-{{/apiInfo}}
- , Apis
+ exposed-modules: {{title}}.API
+ , {{title}}.Types
ghc-options: -Wall
build-depends: base
, aeson
, text
- , split
, containers
, network-uri
- , QuickCheck
, servant
, servant-client
- default-language: Haskell2010
-
-executable client
- hs-source-dirs: client
- main-is: Main.hs
- ghc-options: -threaded -rtsopts -with-rtsopts=-N
- build-depends: base
- , either
- , transformers
- , split
- , network-uri
- , QuickCheck
- , servant
- , servant-client
- , haskell-servant-codegen
- default-language: Haskell2010
-
-executable server
- hs-source-dirs: server
- main-is: Main.hs
- ghc-options: -threaded -rtsopts -with-rtsopts=-N
- build-depends: base
, warp
, servant-server
- , servant-mock
- , haskell-servant-codegen
+ , transformers
+ , either
default-language: Haskell2010
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache
deleted file mode 100644
index 1f76c197ae29..000000000000
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache
+++ /dev/null
@@ -1,34 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-{{#models}}
-{{#model}}
-module {{package}}.{{classname}}
- ( {{classname}} (..)
- ) where
-{{/model}}
-{{/models}}
-
-import Data.Aeson
-import GHC.Generics
-import Test.QuickCheck
-{{#imports}}import {{import}}
-{{/imports}}
-
-{{#models}}
-{{#model}}
-
-data {{classname}} = {{classname}}
- { {{#vars}}{{& name}} :: {{datatype}}{{#hasMore}}
- , {{/hasMore}}{{/vars}}
- } deriving (Show, Eq, Generic)
-
-instance FromJSON {{classname}}
-instance ToJSON {{classname}}
-instance Arbitrary {{classname}} where
- arbitrary = {{classname}} <$> {{#vars}}arbitrary{{#hasMore}} <*> {{/hasMore}}{{/vars}}
-{{/model}}
-{{/models}}
diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache
index 00448df3e959..e06adf4ba3d3 100644
--- a/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache
+++ b/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache
@@ -1,33 +1,3 @@
-# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md
-
-# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
-resolver: lts-3.17
-
-# Local packages, usually specified by relative directory name
+resolver: lts-5.5
packages:
- '.'
-
-# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
-extra-deps:
-- servant-mock-0.4.4.6
-
-# Override default flag values for local packages and extra-deps
-flags: {}
-
-# Extra package databases containing global packages
-extra-package-dbs: []
-
-# Control whether we use the GHC we find on the path
-# system-ghc: true
-
-# Require a specific version of stack, using version ranges
-# require-stack-version: -any # Default
-# require-stack-version: >= 0.1.10.0
-
-# Override the architecture used by stack, especially useful on Windows
-# arch: i386
-# arch: x86_64
-
-# Extra directories used by stack for building
-# extra-include-dirs: [/path/to/dir]
-# extra-lib-dirs: [/path/to/dir]