[haskell-http-client] handle Alias models + refactoring. (#6712)

* handle Alias models with newtypes

* add inlineConsumesContentTypes cli option

* generate swagger.yaml instead of swagger.json

* check for/validate unhandled authMethods

* refactoring
This commit is contained in:
Jon Schoning
2017-10-17 21:47:56 -05:00
committed by wing328
parent 1ac04ae13a
commit 5219035b3a
77 changed files with 7058 additions and 7204 deletions

View File

@@ -19,10 +19,8 @@ import io.swagger.codegen.CodegenModel;
import io.swagger.codegen.CodegenOperation;
import io.swagger.codegen.CodegenProperty;
import io.swagger.codegen.SupportingFile;
import io.swagger.util.Json;
import java.io.IOException;
import java.io.File;
import io.swagger.util.Yaml;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils;
@@ -41,17 +39,18 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
protected Boolean useMonadLogger = false;
// CLI
public static final String ALLOW_FROMJSON_NULLS = "allowFromJsonNulls";
public static final String ALLOW_TOJSON_NULLS = "allowToJsonNulls";
public static final String DATETIME_FORMAT = "dateTimeFormat";
public static final String DATE_FORMAT = "dateFormat";
public static final String GENERATE_FORM_URLENCODED_INSTANCES = "generateFormUrlEncodedInstances";
public static final String GENERATE_LENSES = "generateLenses";
public static final String GENERATE_MODEL_CONSTRUCTORS = "generateModelConstructors";
public static final String MODEL_DERIVING = "modelDeriving";
public static final String STRICT_FIELDS = "strictFields";
public static final String USE_MONAD_LOGGER = "useMonadLogger";
// CLI PROPS
public static final String PROP_ALLOW_FROMJSON_NULLS = "allowFromJsonNulls";
public static final String PROP_ALLOW_TOJSON_NULLS = "allowToJsonNulls";
public static final String PROP_DATETIME_FORMAT = "dateTimeFormat";
public static final String PROP_DATE_FORMAT = "dateFormat";
public static final String PROP_GENERATE_FORM_URLENCODED_INSTANCES = "generateFormUrlEncodedInstances";
public static final String PROP_GENERATE_LENSES = "generateLenses";
public static final String PROP_GENERATE_MODEL_CONSTRUCTORS = "generateModelConstructors";
public static final String PROP_INLINE_CONSUMES_CONTENT_TYPES = "inlineConsumesContentTypes";
public static final String PROP_MODEL_DERIVING = "modelDeriving";
public static final String PROP_STRICT_FIELDS = "strictFields";
public static final String PROP_USE_MONAD_LOGGER = "useMonadLogger";
// protected String MODEL_IMPORTS = "modelImports";
// protected String MODEL_EXTENSIONS = "modelExtensions";
@@ -59,8 +58,30 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+");
static final String MEDIA_TYPE = "mediaType";
static final String MEDIA_DATA_TYPE = "x-mediaDataType";
static final String MEDIA_IS_JSON = "x-mediaIsJson";
// vendor extensions
static final String X_ALL_UNIQUE_PARAMS = "x-allUniqueParams";
static final String X_COLLECTION_FORMAT = "x-collectionFormat";
static final String X_DUPLICATE = "x-duplicate";
static final String X_HADDOCK_PATH = "x-haddockPath";
static final String X_HAS_BODY_OR_FORM_PARAM = "x-hasBodyOrFormParam";
static final String X_HAS_MIME_FORM_URL_ENCODED = "x-hasMimeFormUrlEncoded";
static final String X_HAS_NEW_TAG = "x-hasNewTag";
static final String X_HAS_OPTIONAL_PARAMS = "x-hasOptionalParams";
static final String X_HAS_UNKNOWN_MIME_TYPES = "x-hasUnknownMimeTypes";
static final String X_HAS_UNKNOWN_RETURN = "x-hasUnknownReturn";
static final String X_INLINE_CONTENT_TYPE = "x-inlineContentType";
static final String X_IS_BODY_OR_FORM_PARAM = "x-isBodyOrFormParam";
static final String X_MEDIA_DATA_TYPE = "x-mediaDataType";
static final String X_MEDIA_IS_JSON = "x-mediaIsJson";
static final String X_MIME_TYPES = "x-mimeTypes";
static final String X_OPERATION_TYPE = "x-operationType";
static final String X_PARAM_NAME_TYPE = "x-paramNameType";
static final String X_PATH = "x-path";
static final String X_RETURN_TYPE = "x-returnType";
static final String X_STRICT_FIELDS = "x-strictFields";
static final String X_UNKNOWN_MIME_TYPES = "x-unknownMimeTypes";
static final String X_USE_MONAD_LOGGER = "x-useMonadLogger";
protected Map<String, CodegenParameter> uniqueParamsByName = new HashMap<String, CodegenParameter>();
@@ -183,79 +204,82 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
knownMimeDataTypes.put("*/*", "MimeAny");
importMapping.clear();
importMapping.put("Map", "qualified Data.Map as Map");
cliOptions.add(CliOption.newString(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
cliOptions.add(CliOption.newString(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
cliOptions.add(CliOption.newBoolean(ALLOW_FROMJSON_NULLS, "allow JSON Null during model decoding from JSON").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(ALLOW_TOJSON_NULLS, "allow emitting JSON Null during model encoding to JSON").defaultValue(Boolean.FALSE.toString()));
cliOptions.add(CliOption.newBoolean(GENERATE_LENSES, "Generate Lens optics for Models").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(GENERATE_MODEL_CONSTRUCTORS, "Generate smart constructors (only supply required fields) for models").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(GENERATE_FORM_URLENCODED_INSTANCES, "Generate FromForm/ToForm instances for models that are used by operations that produce or consume application/x-www-form-urlencoded").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_ALLOW_FROMJSON_NULLS, "allow JSON Null during model decoding from JSON").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_ALLOW_TOJSON_NULLS, "allow emitting JSON Null during model encoding to JSON").defaultValue(Boolean.FALSE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_GENERATE_LENSES, "Generate Lens optics for Models").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_GENERATE_MODEL_CONSTRUCTORS, "Generate smart constructors (only supply required fields) for models").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_GENERATE_FORM_URLENCODED_INSTANCES, "Generate FromForm/ToForm instances for models that are used by operations that produce or consume application/x-www-form-urlencoded").defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(PROP_INLINE_CONSUMES_CONTENT_TYPES, "Inline (hardcode) the content-type on operations that do not have multiple content-types (Consumes)").defaultValue(Boolean.FALSE.toString()));
cliOptions.add(CliOption.newString(MODEL_DERIVING, "Additional classes to include in the deriving() clause of Models"));
cliOptions.add(CliOption.newBoolean(STRICT_FIELDS, "Add strictness annotations to all model fields").defaultValue((Boolean.TRUE.toString())));
cliOptions.add(CliOption.newBoolean(USE_MONAD_LOGGER, "Use the monad-logger package to provide logging (if false, use the katip logging package)").defaultValue((Boolean.FALSE.toString())));
cliOptions.add(CliOption.newString(PROP_MODEL_DERIVING, "Additional classes to include in the deriving() clause of Models"));
cliOptions.add(CliOption.newBoolean(PROP_STRICT_FIELDS, "Add strictness annotations to all model fields").defaultValue((Boolean.TRUE.toString())));
cliOptions.add(CliOption.newBoolean(PROP_USE_MONAD_LOGGER, "Use the monad-logger package to provide logging (if false, use the katip logging package)").defaultValue((Boolean.FALSE.toString())));
cliOptions.add(CliOption.newString(DATETIME_FORMAT, "format string used to parse/render a datetime"));
cliOptions.add(CliOption.newString(DATE_FORMAT, "format string used to parse/render a date").defaultValue(defaultDateFormat));
cliOptions.add(CliOption.newString(PROP_DATETIME_FORMAT, "format string used to parse/render a datetime"));
cliOptions.add(CliOption.newString(PROP_DATE_FORMAT, "format string used to parse/render a date").defaultValue(defaultDateFormat));
cliOptions.add(CliOption.newBoolean(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated").defaultValue(Boolean.TRUE.toString()));
}
public void setAllowFromJsonNulls(Boolean value) {
additionalProperties.put(ALLOW_FROMJSON_NULLS, value);
additionalProperties.put(PROP_ALLOW_FROMJSON_NULLS, value);
}
public void setAllowToJsonNulls(Boolean value) {
additionalProperties.put(ALLOW_TOJSON_NULLS, value);
additionalProperties.put(PROP_ALLOW_TOJSON_NULLS, value);
}
public void setGenerateModelConstructors(Boolean value) {
additionalProperties.put(GENERATE_MODEL_CONSTRUCTORS, value);
additionalProperties.put(PROP_GENERATE_MODEL_CONSTRUCTORS, value);
}
public void setGenerateFormUrlEncodedInstances(Boolean value) {
additionalProperties.put(GENERATE_FORM_URLENCODED_INSTANCES, value);
additionalProperties.put(PROP_GENERATE_FORM_URLENCODED_INSTANCES, value);
}
public void setInlineConsumesContentTypes (Boolean value) {
additionalProperties.put(PROP_INLINE_CONSUMES_CONTENT_TYPES, value);
}
public void setGenerateLenses(Boolean value) {
additionalProperties.put(GENERATE_LENSES, value);
additionalProperties.put(PROP_GENERATE_LENSES, value);
}
public void setModelDeriving(String value) {
if (StringUtils.isBlank(value)) {
additionalProperties.remove(MODEL_DERIVING);
additionalProperties.remove(PROP_MODEL_DERIVING);
} else {
additionalProperties.put(MODEL_DERIVING, StringUtils.join(value.split(" "), ","));
additionalProperties.put(PROP_MODEL_DERIVING, StringUtils.join(value.split(" "), ","));
}
}
public void setDateTimeFormat(String value) {
if (StringUtils.isBlank(value)) {
additionalProperties.remove(DATETIME_FORMAT);
additionalProperties.remove(PROP_DATETIME_FORMAT);
} else {
additionalProperties.put(DATETIME_FORMAT, value);
additionalProperties.put(PROP_DATETIME_FORMAT, value);
}
}
public void setDateFormat(String value) {
if (StringUtils.isBlank(value)) {
additionalProperties.remove(DATE_FORMAT);
additionalProperties.remove(PROP_DATE_FORMAT);
} else {
additionalProperties.put(DATE_FORMAT, value);
additionalProperties.put(PROP_DATE_FORMAT, value);
}
}
public void setStrictFields(Boolean value) {
additionalProperties.put("x-strictFields", value);
additionalProperties.put(X_STRICT_FIELDS, value);
}
public void setUseMonadLogger(Boolean value) {
additionalProperties.put("x-useMonadLogger", value);
additionalProperties.put(X_USE_MONAD_LOGGER, value);
this.useMonadLogger = value;
}
@@ -269,61 +293,67 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, true);
}
if (additionalProperties.containsKey(ALLOW_FROMJSON_NULLS)) {
setAllowFromJsonNulls(convertPropertyToBoolean(ALLOW_FROMJSON_NULLS));
if (additionalProperties.containsKey(PROP_ALLOW_FROMJSON_NULLS)) {
setAllowFromJsonNulls(convertPropertyToBoolean(PROP_ALLOW_FROMJSON_NULLS));
} else {
setAllowFromJsonNulls(true);
}
if (additionalProperties.containsKey(ALLOW_TOJSON_NULLS)) {
setAllowToJsonNulls(convertPropertyToBoolean(ALLOW_TOJSON_NULLS));
if (additionalProperties.containsKey(PROP_ALLOW_TOJSON_NULLS)) {
setAllowToJsonNulls(convertPropertyToBoolean(PROP_ALLOW_TOJSON_NULLS));
} else {
setAllowToJsonNulls(false);
}
if (additionalProperties.containsKey(GENERATE_MODEL_CONSTRUCTORS)) {
setGenerateModelConstructors(convertPropertyToBoolean(GENERATE_MODEL_CONSTRUCTORS));
if (additionalProperties.containsKey(PROP_GENERATE_MODEL_CONSTRUCTORS)) {
setGenerateModelConstructors(convertPropertyToBoolean(PROP_GENERATE_MODEL_CONSTRUCTORS));
} else {
setGenerateModelConstructors(true);
}
if (additionalProperties.containsKey(GENERATE_FORM_URLENCODED_INSTANCES)) {
setGenerateFormUrlEncodedInstances(convertPropertyToBoolean(GENERATE_FORM_URLENCODED_INSTANCES));
if (additionalProperties.containsKey(PROP_GENERATE_FORM_URLENCODED_INSTANCES)) {
setGenerateFormUrlEncodedInstances(convertPropertyToBoolean(PROP_GENERATE_FORM_URLENCODED_INSTANCES));
} else {
setGenerateFormUrlEncodedInstances(true);
}
if (additionalProperties.containsKey(GENERATE_LENSES)) {
setGenerateLenses(convertPropertyToBoolean(GENERATE_LENSES));
if (additionalProperties.containsKey(PROP_INLINE_CONSUMES_CONTENT_TYPES)) {
setInlineConsumesContentTypes(convertPropertyToBoolean(PROP_INLINE_CONSUMES_CONTENT_TYPES));
} else {
setInlineConsumesContentTypes(false);
}
if (additionalProperties.containsKey(PROP_GENERATE_LENSES)) {
setGenerateLenses(convertPropertyToBoolean(PROP_GENERATE_LENSES));
} else {
setGenerateLenses(true);
}
if (additionalProperties.containsKey(MODEL_DERIVING)) {
setModelDeriving(additionalProperties.get(MODEL_DERIVING).toString());
if (additionalProperties.containsKey(PROP_MODEL_DERIVING)) {
setModelDeriving(additionalProperties.get(PROP_MODEL_DERIVING).toString());
} else {
setModelDeriving("");
}
if (additionalProperties.containsKey(DATETIME_FORMAT)) {
setDateTimeFormat(additionalProperties.get(DATETIME_FORMAT).toString());
if (additionalProperties.containsKey(PROP_DATETIME_FORMAT)) {
setDateTimeFormat(additionalProperties.get(PROP_DATETIME_FORMAT).toString());
} else {
setDateTimeFormat(null); // default should be null
}
if (additionalProperties.containsKey(DATE_FORMAT)) {
setDateFormat(additionalProperties.get(DATE_FORMAT).toString());
if (additionalProperties.containsKey(PROP_DATE_FORMAT)) {
setDateFormat(additionalProperties.get(PROP_DATE_FORMAT).toString());
} else {
setDateFormat(defaultDateFormat);
}
if (additionalProperties.containsKey(STRICT_FIELDS)) {
setStrictFields(convertPropertyToBoolean(STRICT_FIELDS));
if (additionalProperties.containsKey(PROP_STRICT_FIELDS)) {
setStrictFields(convertPropertyToBoolean(PROP_STRICT_FIELDS));
} else {
setStrictFields(true);
}
if (additionalProperties.containsKey(USE_MONAD_LOGGER)) {
setUseMonadLogger(convertPropertyToBoolean(USE_MONAD_LOGGER));
if (additionalProperties.containsKey(PROP_USE_MONAD_LOGGER)) {
setUseMonadLogger(convertPropertyToBoolean(PROP_USE_MONAD_LOGGER));
} else {
setUseMonadLogger(false);
}
@@ -366,12 +396,14 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
// root
supportingFiles.add(new SupportingFile("haskell-http-client.cabal.mustache", "", cabalName + ".cabal"));
supportingFiles.add(new SupportingFile("swagger.mustache", "", "swagger.yaml"));
// lib
supportingFiles.add(new SupportingFile("TopLevel.mustache", "lib/", apiName + ".hs"));
supportingFiles.add(new SupportingFile("Client.mustache", "lib/" + apiName, "Client.hs"));
supportingFiles.add(new SupportingFile("API.mustache", "lib/" + apiName, "API.hs"));
supportingFiles.add(new SupportingFile("Core.mustache", "lib/" + apiName, "Core.hs"));
supportingFiles.add(new SupportingFile("Model.mustache", "lib/" + apiName, "Model.hs"));
supportingFiles.add(new SupportingFile("MimeTypes.mustache", "lib/" + apiName, "MimeTypes.hs"));
@@ -382,8 +414,8 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
// apiTemplateFiles.put("Model.mustache", ".hs");
// lens
if ((boolean)additionalProperties.get(GENERATE_LENSES)) {
supportingFiles.add(new SupportingFile("Lens.mustache", "lib/" + apiName, "Lens.hs"));
if ((boolean)additionalProperties.get(PROP_GENERATE_LENSES)) {
supportingFiles.add(new SupportingFile("ModelLens.mustache", "lib/" + apiName, "ModelLens.hs"));
}
additionalProperties.put("title", apiName);
@@ -394,11 +426,22 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
additionalProperties.put("configType", apiName + "Config");
additionalProperties.put("swaggerVersion", swagger.getSwagger());
WriteInputSwaggerToFile(swagger);
super.preprocessSwagger(swagger);
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Swagger swagger = (Swagger)objs.get("swagger");
if(swagger != null) {
try {
objs.put("swagger-yaml", Yaml.mapper().writeValueAsString(swagger));
} catch (JsonProcessingException e) {
LOGGER.error(e.getMessage(), e);
}
}
return super.postProcessSupportingFileData(objs);
}
@Override
public String getTypeDeclaration(Property p) {
@@ -417,15 +460,11 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
String type = null;
if (typeMapping.containsKey(swaggerType)) {
return typeMapping.get(swaggerType);
} else if (languageSpecificPrimitives.contains(type)) {
return type;
} else if (swaggerType == "object") {
return "A.Value";
// } else if (typeMapping.containsValue(swaggerType)) {
// return toModelName(swaggerType) + "_";
} else {
return toModelName(swaggerType);
}
@@ -459,21 +498,21 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
op.vendorExtensions = new LinkedHashMap();
String operationType = toTypeName("Op", op.operationId);
op.vendorExtensions.put("x-operationType", operationType);
op.vendorExtensions.put(X_OPERATION_TYPE, operationType);
typeNames.add(operationType);
op.vendorExtensions.put("x-haddockPath", String.format("%s %s", op.httpMethod, op.path.replace("/", "\\/")));
op.vendorExtensions.put("x-hasBodyOrFormParam", op.getHasBodyParam() || op.getHasFormParams());
op.vendorExtensions.put(X_HADDOCK_PATH, String.format("%s %s", op.httpMethod, op.path.replace("/", "\\/")));
op.vendorExtensions.put(X_HAS_BODY_OR_FORM_PARAM, op.getHasBodyParam() || op.getHasFormParams());
for (CodegenParameter param : op.allParams) {
param.vendorExtensions = new LinkedHashMap(); // prevent aliasing/sharing
param.vendorExtensions.put("x-operationType", operationType);
param.vendorExtensions.put("x-isBodyOrFormParam", param.isBodyParam || param.isFormParam);
param.vendorExtensions.put(X_OPERATION_TYPE, operationType);
param.vendorExtensions.put(X_IS_BODY_OR_FORM_PARAM, param.isBodyParam || param.isFormParam);
if (!StringUtils.isBlank(param.collectionFormat)) {
param.vendorExtensions.put("x-collectionFormat", mapCollectionFormat(param.collectionFormat));
param.vendorExtensions.put(X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat));
}
if(!param.required) {
op.vendorExtensions.put("x-hasOptionalParams", true);
op.vendorExtensions.put(X_HAS_OPTIONAL_PARAMS, true);
}
deduplicateParameter(param);
@@ -508,10 +547,10 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
HashMap<String, Object> pathOps = (HashMap<String, Object>)ret.get("operations");
ArrayList<CodegenOperation> ops = (ArrayList<CodegenOperation>)pathOps.get("operation");
if(ops.size() > 0) {
ops.get(0).vendorExtensions.put("x-hasNewTag", true);
ops.get(0).vendorExtensions.put(X_HAS_NEW_TAG, true);
}
additionalProperties.put("x-hasUnknownMimeTypes", !unknownMimeTypes.isEmpty());
additionalProperties.put(X_HAS_UNKNOWN_MIME_TYPES, !unknownMimeTypes.isEmpty());
Collections.sort(unknownMimeTypes, new Comparator<Map<String, String>>() {
@Override
@@ -519,19 +558,19 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
return o1.get(MEDIA_TYPE).compareTo(o2.get(MEDIA_TYPE));
}
});
additionalProperties.put("x-unknownMimeTypes", unknownMimeTypes);
additionalProperties.put(X_UNKNOWN_MIME_TYPES, unknownMimeTypes);
ArrayList<CodegenParameter> params = new ArrayList<>(uniqueParamsByName.values());
Collections.sort(params, new Comparator<CodegenParameter>() {
@Override
public int compare(CodegenParameter o1, CodegenParameter o2) {
return
((String) o1.vendorExtensions.get("x-paramNameType"))
((String) o1.vendorExtensions.get(X_PARAM_NAME_TYPE))
.compareTo(
(String) o2.vendorExtensions.get("x-paramNameType"));
(String) o2.vendorExtensions.get(X_PARAM_NAME_TYPE));
}
});
additionalProperties.put("x-allUniqueParams", params);
additionalProperties.put(X_ALL_UNIQUE_PARAMS, params);
return ret;
}
@@ -543,8 +582,8 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
CodegenModel m = (CodegenModel) h.get("model");
if (modelMimeTypes.containsKey(m.classname)) {
Set<String> mimeTypes = modelMimeTypes.get(m.classname);
m.vendorExtensions.put("x-mimeTypes", mimeTypes);
if ((boolean)additionalProperties.get(GENERATE_FORM_URLENCODED_INSTANCES) && mimeTypes.contains("MimeFormUrlEncoded")) {
m.vendorExtensions.put(X_MIME_TYPES, mimeTypes);
if ((boolean)additionalProperties.get(PROP_GENERATE_FORM_URLENCODED_INSTANCES) && mimeTypes.contains("MimeFormUrlEncoded")) {
Boolean hasMimeFormUrlEncoded = true;
for (CodegenProperty v : m.vars) {
if (!(v.isPrimitiveType || v.isString || v.isDate || v.isDateTime)) {
@@ -552,7 +591,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
}
}
if (hasMimeFormUrlEncoded) {
m.vendorExtensions.put("x-hasMimeFormUrlEncoded", true);
m.vendorExtensions.put(X_HAS_MIME_FORM_URL_ENCODED, true);
}
}
}
@@ -614,22 +653,12 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
return dataType != null && dataType.equals("B.ByteString");
}
//copy input swagger to output folder
private void WriteInputSwaggerToFile(Swagger swagger) {
try {
String swaggerJson = Json.pretty(swagger);
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e.getCause());
}
}
private void processReturnType(CodegenOperation op) {
String returnType = op.returnType;
if (returnType == null || returnType.equals("null")) {
if(op.hasProduces) {
returnType = "res";
op.vendorExtensions.put("x-hasUnknownReturn", true);
op.vendorExtensions.put(X_HAS_UNKNOWN_RETURN, true);
} else {
returnType = "NoContent";
}
@@ -637,13 +666,15 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
if (returnType.indexOf(" ") >= 0) {
returnType = "(" + returnType + ")";
}
op.vendorExtensions.put("x-returnType", returnType);
op.vendorExtensions.put(X_RETURN_TYPE, returnType);
}
private void processProducesConsumes(CodegenOperation op) {
if (op.hasConsumes) {
for (Map<String, String> m : op.consumes) {
processMediaType(op,m);
processInlineConsumesContentType(op, m);
}
if (isMultipartOperation(op.consumes)) {
op.isMultipart = Boolean.TRUE;
@@ -656,6 +687,18 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
}
}
private void processInlineConsumesContentType(CodegenOperation op, Map<String, String> m) {
if ((boolean) additionalProperties.get(PROP_INLINE_CONSUMES_CONTENT_TYPES)
&& op.consumes.size() == 1) {
op.vendorExtensions.put(X_INLINE_CONTENT_TYPE, m);
for (CodegenParameter param : op.allParams) {
if (param.isBodyParam && param.required) {
param.vendorExtensions.put(X_INLINE_CONTENT_TYPE, m);
}
}
}
}
private void deduplicateParameter(CodegenParameter param) {
if (typeMapping.containsKey(param.dataType) || param.isPrimitiveType || param.isListContainer || param.isMapContainer || param.isFile) {
@@ -687,7 +730,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
uniqueParamsByName.put(paramNameType, param);
}
param.vendorExtensions.put("x-paramNameType", paramNameType);
param.vendorExtensions.put(X_PARAM_NAME_TYPE, paramNameType);
typeNames.add(paramNameType);
}
}
@@ -695,7 +738,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
public Boolean checkParamForDuplicates(String paramNameType, CodegenParameter param) {
CodegenParameter lastParam = this.uniqueParamsByName.get(paramNameType);
if (lastParam != null && lastParam.dataType != null && lastParam.dataType.equals(param.dataType)) {
param.vendorExtensions.put("x-duplicate", true);
param.vendorExtensions.put(X_DUPLICATE, true);
return true;
}
return false;
@@ -714,7 +757,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
xPath = xPath.replaceAll("^\\[,", "[");
xPath = xPath.replaceAll(",\\]$", "]");
}
op.vendorExtensions.put("x-path", xPath);
op.vendorExtensions.put(X_PATH, xPath);
}
@@ -725,9 +768,9 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
String mimeType = getMimeDataType(mediaType);
typeNames.add(mimeType);
m.put(MEDIA_DATA_TYPE, mimeType);
m.put(X_MEDIA_DATA_TYPE, mimeType);
if (isJsonMimeType(mediaType)) {
m.put(MEDIA_IS_JSON, "true");
m.put(X_MEDIA_IS_JSON, "true");
}
if (!knownMimeDataTypes.containsValue(mimeType) && !unknownMimeTypesContainsType(mimeType)) {
@@ -744,7 +787,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
private Boolean unknownMimeTypesContainsType(String mimeType) {
for(Map<String,String> m : unknownMimeTypes) {
String mimeType0 = m.get(MEDIA_DATA_TYPE);
String mimeType0 = m.get(X_MEDIA_DATA_TYPE);
if(mimeType0 != null && mimeType0.equals(mimeType)) {
return true;
}
@@ -913,31 +956,50 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
else
return "True";
}
} else if (p instanceof DoubleProperty) {
DoubleProperty dp = (DoubleProperty) p;
if (dp.getDefault() != null) {
return dp.getDefault().toString();
}
} else if (p instanceof FloatProperty) {
FloatProperty dp = (FloatProperty) p;
if (dp.getDefault() != null) {
return dp.getDefault().toString();
}
} else if (p instanceof IntegerProperty) {
IntegerProperty dp = (IntegerProperty) p;
if (dp.getDefault() != null) {
return dp.getDefault().toString();
}
} else if (p instanceof LongProperty) {
LongProperty dp = (LongProperty) p;
if (dp.getDefault() != null) {
return dp.getDefault().toString();
}
}
return null;
}
@Override
public String toEnumName(CodegenProperty property) {
return "Enum'" + toTypeName("", property.name);
}
@Override
public String toEnumVarName(String value, String datatype) {
List<String> num = new ArrayList<>(Arrays.asList("integer","int","double","long","float"));
if (value.length() == 0) {
return "E'Empty";
}
// for symbol, e.g. $, #
if (getSymbolName(value) != null) {
return "E'" + sanitizeName(getSymbolName(value));
}
// number
if (num.contains(datatype.toLowerCase())) {
String varName = "Num" + value;
varName = varName.replaceAll("-", "Minus_");
varName = varName.replaceAll("\\+", "Plus_");
varName = varName.replaceAll("\\.", "_Dot_");
return "E'" + sanitizeName(varName);
}
return "E'" + sanitizeName(value);
}
@Override
public String toEnumValue(String value, String datatype) {
List<String> num = new ArrayList<>(Arrays.asList("integer","int","double","long","float"));
if(num.contains(datatype.toLowerCase())) {
return value;
} else {
return "\"" + escapeText(value) + "\"";
}
}
// override with any special text escaping logic
@SuppressWarnings("static-method")
public String escapeText(String input) {

View File

@@ -4,6 +4,7 @@ Module : {{title}}.API
-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -13,49 +14,38 @@ Module : {{title}}.API
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-unused-binds -fno-warn-unused-imports #-}
module {{title}}.API where
import {{title}}.Model as M
import {{title}}.Core
import {{title}}.MimeTypes
import {{title}}.Lens
import {{title}}.Model as M
import qualified Data.Aeson as A
import qualified Data.Time as TI
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Data.ByteString.Base64 as B64
import qualified Network.HTTP.Client.MultipartFormData as NH
import qualified Network.HTTP.Media as ME
import qualified Network.HTTP.Types as NH
import qualified Web.HttpApiData as WH
import qualified Web.FormUrlEncoded as WH
import qualified Data.CaseInsensitive as CI
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.Data as P (Typeable, TypeRep, typeOf, typeRep)
import qualified Data.Foldable as P
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Maybe as P
import qualified Data.Proxy as P (Proxy(..))
import qualified Data.Set as Set
import qualified Data.String as P
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.Time as TI
import qualified GHC.Base as P (Alternative)
import qualified Control.Arrow as P (left)
import qualified Lens.Micro as L
import qualified Network.HTTP.Client.MultipartFormData as NH
import qualified Network.HTTP.Media as ME
import qualified Network.HTTP.Types as NH
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import Data.Monoid ((<>))
import Data.Function ((&))
@@ -83,11 +73,11 @@ import qualified Prelude as P
-- Note: Has 'Produces' instances, but no response schema
-- {{/vendorExtensions.x-hasUnknownReturn}}
{{operationId}}
:: {{#vendorExtensions.x-hasBodyOrFormParam}}(Consumes {{{vendorExtensions.x-operationType}}} contentType{{#allParams}}{{#isBodyParam}}{{#required}}, MimeRender contentType {{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}}{{/required}}{{/isBodyParam}}{{/allParams}})
=> contentType -- ^ request content-type ('MimeType')
-> {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}} -- ^ "{{{paramName}}}"{{#description}} - {{/description}} {{{description}}}
-> {{/required}}{{/allParams}}{{requestType}} {{{vendorExtensions.x-operationType}}} {{#vendorExtensions.x-hasBodyOrFormParam}}contentType{{/vendorExtensions.x-hasBodyOrFormParam}}{{^vendorExtensions.x-hasBodyOrFormParam}}MimeNoContent{{/vendorExtensions.x-hasBodyOrFormParam}} {{vendorExtensions.x-returnType}}
{{operationId}} {{#vendorExtensions.x-hasBodyOrFormParam}}_ {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#isBodyParam}}{{{paramName}}}{{/isBodyParam}}{{^isBodyParam}}({{{vendorExtensions.x-paramNameType}}} {{{paramName}}}){{/isBodyParam}} {{/required}}{{/allParams}}=
:: {{#vendorExtensions.x-hasBodyOrFormParam}}(Consumes {{{vendorExtensions.x-operationType}}} {{>_contentType}}{{#allParams}}{{#isBodyParam}}{{#required}}, MimeRender {{>_contentType}} {{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}}{{/required}}{{/isBodyParam}}{{/allParams}})
=> {{^vendorExtensions.x-inlineContentType}}contentType -- ^ request content-type ('MimeType')
-> {{/vendorExtensions.x-inlineContentType}}{{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}} -- ^ "{{{paramName}}}"{{#description}} - {{/description}} {{{description}}}
-> {{/required}}{{/allParams}}{{requestType}} {{{vendorExtensions.x-operationType}}} {{#vendorExtensions.x-hasBodyOrFormParam}}{{>_contentType}}{{/vendorExtensions.x-hasBodyOrFormParam}}{{^vendorExtensions.x-hasBodyOrFormParam}}MimeNoContent{{/vendorExtensions.x-hasBodyOrFormParam}} {{vendorExtensions.x-returnType}}
{{operationId}} {{#vendorExtensions.x-hasBodyOrFormParam}}{{^vendorExtensions.x-inlineContentType}}_ {{/vendorExtensions.x-inlineContentType}}{{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#isBodyParam}}{{{paramName}}}{{/isBodyParam}}{{^isBodyParam}}({{{vendorExtensions.x-paramNameType}}} {{{paramName}}}){{/isBodyParam}} {{/required}}{{/allParams}}=
_mkRequest "{{httpMethod}}" {{{vendorExtensions.x-path}}}{{#authMethods}}
`_hasAuthType` (P.Proxy :: P.Proxy {{name}}){{/authMethods}}{{#allParams}}{{#required}}{{#isHeaderParam}}
`setHeader` {{>_headerColl}} ("{{{baseName}}}", {{{paramName}}}){{/isHeaderParam}}{{#isQueryParam}}
@@ -117,240 +107,13 @@ instance Produces {{{vendorExtensions.x-operationType}}} {{{x-mediaDataType}}}
{{/produces}}{{/hasProduces}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
-- * HasBodyParam
-- * Parameter newtypes
-- | Designates the body parameter of a request
class HasBodyParam req param where
setBodyParam :: forall contentType res. (Consumes req contentType, MimeRender contentType param) => {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
setBodyParam req xs =
req `_setBodyLBS` mimeRender (P.Proxy :: P.Proxy contentType) xs & _setContentTypeHeader
{{#x-allUniqueParams}}
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
{{/x-allUniqueParams}}
-- * HasOptionalParam
-- | Designates the optional parameters of a request
class HasOptionalParam req param where
{-# MINIMAL applyOptionalParam | (-&-) #-}
-- | Apply an optional parameter to a request
applyOptionalParam :: {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
applyOptionalParam = (-&-)
{-# INLINE applyOptionalParam #-}
-- | infix operator \/ alias for 'addOptionalParam'
(-&-) :: {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
(-&-) = applyOptionalParam
{-# INLINE (-&-) #-}
infixl 2 -&-
-- * {{requestType}}
-- | Represents a request. The "req" type variable is the request type. The "res" type variable is the response type.
data {{requestType}} req contentType res = {{requestType}}
{ rMethod :: NH.Method -- ^ Method of {{requestType}}
, rUrlPath :: [BCL.ByteString] -- ^ Endpoint of {{requestType}}
, rParams :: Params -- ^ params of {{requestType}}
, rAuthTypes :: [P.TypeRep] -- ^ types of auth methods
}
deriving (P.Show)
-- | 'rMethod' Lens
rMethodL :: Lens_' ({{requestType}} req contentType res) NH.Method
rMethodL f {{requestType}}{..} = (\rMethod -> {{requestType}} { rMethod, ..} ) <$> f rMethod
{-# INLINE rMethodL #-}
-- | 'rUrlPath' Lens
rUrlPathL :: Lens_' ({{requestType}} req contentType res) [BCL.ByteString]
rUrlPathL f {{requestType}}{..} = (\rUrlPath -> {{requestType}} { rUrlPath, ..} ) <$> f rUrlPath
{-# INLINE rUrlPathL #-}
-- | 'rParams' Lens
rParamsL :: Lens_' ({{requestType}} req contentType res) Params
rParamsL f {{requestType}}{..} = (\rParams -> {{requestType}} { rParams, ..} ) <$> f rParams
{-# INLINE rParamsL #-}
-- | 'rParams' Lens
rAuthTypesL :: Lens_' ({{requestType}} req contentType res) [P.TypeRep]
rAuthTypesL f {{requestType}}{..} = (\rAuthTypes -> {{requestType}} { rAuthTypes, ..} ) <$> f rAuthTypes
{-# INLINE rAuthTypesL #-}
-- | Request Params
data Params = Params
{ paramsQuery :: NH.Query
, paramsHeaders :: NH.RequestHeaders
, paramsBody :: ParamBody
}
deriving (P.Show)
-- | 'paramsQuery' Lens
paramsQueryL :: Lens_' Params NH.Query
paramsQueryL f Params{..} = (\paramsQuery -> Params { paramsQuery, ..} ) <$> f paramsQuery
{-# INLINE paramsQueryL #-}
-- | 'paramsHeaders' Lens
paramsHeadersL :: Lens_' Params NH.RequestHeaders
paramsHeadersL f Params{..} = (\paramsHeaders -> Params { paramsHeaders, ..} ) <$> f paramsHeaders
{-# INLINE paramsHeadersL #-}
-- | 'paramsBody' Lens
paramsBodyL :: Lens_' Params ParamBody
paramsBodyL f Params{..} = (\paramsBody -> Params { paramsBody, ..} ) <$> f paramsBody
{-# INLINE paramsBodyL #-}
-- | Request Body
data ParamBody
= ParamBodyNone
| ParamBodyB B.ByteString
| ParamBodyBL BL.ByteString
| ParamBodyFormUrlEncoded WH.Form
| ParamBodyMultipartFormData [NH.Part]
deriving (P.Show)
-- ** {{requestType}} Utils
_mkRequest :: NH.Method -- ^ Method
-> [BCL.ByteString] -- ^ Endpoint
-> {{requestType}} req contentType res -- ^ req: Request Type, res: Response Type
_mkRequest m u = {{requestType}} m u _mkParams []
_mkParams :: Params
_mkParams = Params [] [] ParamBodyNone
setHeader :: {{requestType}} req contentType res -> [NH.Header] -> {{requestType}} req contentType res
setHeader req header =
req `removeHeader` P.fmap P.fst header &
L.over (rParamsL . paramsHeadersL) (header P.++)
removeHeader :: {{requestType}} req contentType res -> [NH.HeaderName] -> {{requestType}} req contentType res
removeHeader req header =
req &
L.over
(rParamsL . paramsHeadersL)
(P.filter (\h -> cifst h `P.notElem` P.fmap CI.mk header))
where
cifst = CI.mk . P.fst
_setContentTypeHeader :: forall req contentType res. MimeType contentType => {{requestType}} req contentType res -> {{requestType}} req contentType res
_setContentTypeHeader req =
case mimeType (P.Proxy :: P.Proxy contentType) of
Just m -> req `setHeader` [("content-type", BC.pack $ P.show m)]
Nothing -> req `removeHeader` ["content-type"]
_setAcceptHeader :: forall req contentType res accept. MimeType accept => {{requestType}} req contentType res -> accept -> {{requestType}} req contentType res
_setAcceptHeader req accept =
case mimeType' accept of
Just m -> req `setHeader` [("accept", BC.pack $ P.show m)]
Nothing -> req `removeHeader` ["accept"]
setQuery :: {{requestType}} req contentType res -> [NH.QueryItem] -> {{requestType}} req contentType res
setQuery req query =
req &
L.over
(rParamsL . paramsQueryL)
((query P.++) . P.filter (\q -> cifst q `P.notElem` P.fmap cifst query))
where
cifst = CI.mk . P.fst
addForm :: {{requestType}} req contentType res -> WH.Form -> {{requestType}} req contentType res
addForm req newform =
let form = case paramsBody (rParams req) of
ParamBodyFormUrlEncoded _form -> _form
_ -> mempty
in req & L.set (rParamsL . paramsBodyL) (ParamBodyFormUrlEncoded (newform <> form))
_addMultiFormPart :: {{requestType}} req contentType res -> NH.Part -> {{requestType}} req contentType res
_addMultiFormPart req newpart =
let parts = case paramsBody (rParams req) of
ParamBodyMultipartFormData _parts -> _parts
_ -> []
in req & L.set (rParamsL . paramsBodyL) (ParamBodyMultipartFormData (newpart : parts))
_setBodyBS :: {{requestType}} req contentType res -> B.ByteString -> {{requestType}} req contentType res
_setBodyBS req body =
req & L.set (rParamsL . paramsBodyL) (ParamBodyB body)
_setBodyLBS :: {{requestType}} req contentType res -> BL.ByteString -> {{requestType}} req contentType res
_setBodyLBS req body =
req & L.set (rParamsL . paramsBodyL) (ParamBodyBL body)
_hasAuthType :: AuthMethod authMethod => {{requestType}} req contentType res -> P.Proxy authMethod -> {{requestType}} req contentType res
_hasAuthType req proxy =
req & L.over rAuthTypesL (P.typeRep proxy :)
-- ** Params Utils
toPath
:: WH.ToHttpApiData a
=> a -> BCL.ByteString
toPath = BB.toLazyByteString . WH.toEncodedUrlPiece
toHeader :: WH.ToHttpApiData a => (NH.HeaderName, a) -> [NH.Header]
toHeader x = [fmap WH.toHeader x]
toForm :: WH.ToHttpApiData v => (BC.ByteString, v) -> WH.Form
toForm (k,v) = WH.toForm [(BC.unpack k,v)]
toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
toQuery x = [(fmap . fmap) toQueryParam x]
where toQueryParam = T.encodeUtf8 . WH.toQueryParam
-- *** Swagger `CollectionFormat` Utils
-- | Determines the format of the array if type array is used.
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`. This is valid only for parameters in "query" ('NH.Query') or "formData" ('WH.Form')
toHeaderColl :: WH.ToHttpApiData a => CollectionFormat -> (NH.HeaderName, [a]) -> [NH.Header]
toHeaderColl c xs = _toColl c toHeader xs
toFormColl :: WH.ToHttpApiData v => CollectionFormat -> (BC.ByteString, [v]) -> WH.Form
toFormColl c xs = WH.toForm $ fmap unpack $ _toColl c toHeader $ pack xs
where
pack (k,v) = (CI.mk k, v)
unpack (k,v) = (BC.unpack (CI.original k), BC.unpack v)
toQueryColl :: WH.ToHttpApiData a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query
toQueryColl c xs = _toCollA c toQuery xs
_toColl :: P.Traversable f => CollectionFormat -> (f a -> [(b, BC.ByteString)]) -> f [a] -> [(b, BC.ByteString)]
_toColl c encode xs = fmap (fmap P.fromJust) (_toCollA' c fencode BC.singleton (fmap Just xs))
where fencode = fmap (fmap Just) . encode . fmap P.fromJust
{-# INLINE fencode #-}
_toCollA :: (P.Traversable f, P.Traversable t, P.Alternative t) => CollectionFormat -> (f (t a) -> [(b, t BC.ByteString)]) -> f (t [a]) -> [(b, t BC.ByteString)]
_toCollA c encode xs = _toCollA' c encode BC.singleton xs
_toCollA' :: (P.Monoid c, P.Traversable f, P.Traversable t, P.Alternative t) => CollectionFormat -> (f (t a) -> [(b, t c)]) -> (Char -> c) -> f (t [a]) -> [(b, t c)]
_toCollA' c encode one xs = case c of
CommaSeparated -> go (one ',')
SpaceSeparated -> go (one ' ')
TabSeparated -> go (one '\t')
PipeSeparated -> go (one '|')
MultiParamArray -> expandList
where
go sep =
[P.foldl1 (\(sk, sv) (_, v) -> (sk, (combine sep <$> sv <*> v) <|> sv <|> v)) expandList]
combine sep x y = x <> sep <> y
expandList = (P.concatMap encode . (P.traverse . P.traverse) P.toList) xs
{-# INLINE go #-}
{-# INLINE expandList #-}
{-# INLINE combine #-}
-- * AuthMethods
-- | Provides a method to apply auth methods to requests
class P.Typeable a => AuthMethod a where
applyAuthMethod :: {{requestType}} req contentType res -> a -> {{requestType}} req contentType res
-- | An existential wrapper for any AuthMethod
data AnyAuthMethod = forall a. AuthMethod a => AnyAuthMethod a deriving (P.Typeable)
instance AuthMethod AnyAuthMethod where applyAuthMethod req (AnyAuthMethod a) = applyAuthMethod req a
-- * Auth Methods
{{#authMethods}}{{#isBasic}}-- ** {{name}}
data {{name}} =
@@ -358,9 +121,11 @@ data {{name}} =
deriving (P.Eq, P.Show, P.Typeable)
instance AuthMethod {{name}} where
applyAuthMethod req a@({{name}} user pw) =
applyAuthMethod _ a@({{name}} user pw) req =
P.pure $
if (P.typeOf a `P.elem` rAuthTypes req)
then req `setHeader` toHeader ("Authorization", T.decodeUtf8 cred)
& L.over rAuthTypesL (P.filter (/= P.typeOf a))
else req
where cred = BC.append "Basic " (B64.encode $ BC.concat [ user, ":", pw ])
@@ -370,9 +135,11 @@ data {{name}} =
deriving (P.Eq, P.Show, P.Typeable)
instance AuthMethod {{name}} where
applyAuthMethod req a@({{name}} secret) =
applyAuthMethod _ a@({{name}} secret) req =
P.pure $
if (P.typeOf a `P.elem` rAuthTypes req)
then req {{#isKeyInHeader}}`setHeader` toHeader ("{{keyParamName}}", secret){{/isKeyInHeader}}{{^isKeyInHeader}}`setQuery` toQuery ("{{keyParamName}}", Just secret){{/isKeyInHeader}}
& L.over rAuthTypesL (P.filter (/= P.typeOf a))
else req
{{/isApiKey}}{{#isOAuth}}-- ** {{name}}
@@ -381,9 +148,28 @@ data {{name}} =
deriving (P.Eq, P.Show, P.Typeable)
instance AuthMethod {{name}} where
applyAuthMethod req a@({{name}} secret) =
applyAuthMethod _ a@({{name}} secret) req =
P.pure $
if (P.typeOf a `P.elem` rAuthTypes req)
then req `setHeader` toHeader ("Authorization", "Bearer " <> secret)
& L.over rAuthTypesL (P.filter (/= P.typeOf a))
else req
{{/isOAuth}}{{/authMethods}}
{{#x-hasUnknownMimeTypes}}
-- * Custom Mime Types
{{#x-unknownMimeTypes}}-- ** {{{x-mediaDataType}}}
data {{{x-mediaDataType}}} = {{{x-mediaDataType}}} deriving (P.Typeable)
-- | @{{{mediaType}}}@
instance MimeType {{{x-mediaDataType}}} where
mimeType _ = Just $ P.fromString "{{{mediaType}}}"{{#x-mediaIsJson}}
instance A.ToJSON a => MimeRender {{{x-mediaDataType}}} a where mimeRender _ = A.encode
instance A.FromJSON a => MimeUnrender {{{x-mediaDataType}}} a where mimeUnrender _ = A.eitherDecode{{/x-mediaIsJson}}
-- instance MimeRender {{{x-mediaDataType}}} T.Text where mimeRender _ = undefined
-- instance MimeUnrender {{{x-mediaDataType}}} T.Text where mimeUnrender _ = undefined
{{/x-unknownMimeTypes}}{{/x-hasUnknownMimeTypes}}

View File

@@ -15,102 +15,30 @@ Module : {{title}}.Client
module {{title}}.Client where
import {{title}}.Model
import {{title}}.API
import {{title}}.MimeTypes
import {{title}}.Core
import {{title}}.Logging
import {{title}}.MimeTypes
import qualified Control.Exception.Safe as E
import qualified Control.Monad.IO.Class as P
import qualified Data.Aeson as A
import qualified Control.Monad as P
import qualified Data.Aeson.Types as A
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Data.Proxy as P (Proxy(..))
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Network.HTTP.Client as NH
import qualified Network.HTTP.Client.MultipartFormData as NH
import qualified Network.HTTP.Types as NH
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import Data.Function ((&))
import Data.Monoid ((<>))
import Data.Text (Text)
import GHC.Exts (IsString(..))
import Web.FormUrlEncoded as WH
import Web.HttpApiData as WH
import Control.Monad.Catch (MonadThrow)
import qualified Data.Time as TI
import qualified Data.Map as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Text.Printf as T
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Data.ByteString.Builder as BB
import qualified Network.HTTP.Client as NH
import qualified Network.HTTP.Client.TLS as NH
import qualified Network.HTTP.Client.MultipartFormData as NH
import qualified Network.HTTP.Types.Method as NH
import qualified Network.HTTP.Types as NH
import qualified Network.HTTP.Types.URI as NH
import qualified Control.Exception.Safe as E
-- * Config
-- |
data {{configType}} = {{configType}}
{ configHost :: BCL.ByteString -- ^ host supplied in the Request
, configUserAgent :: Text -- ^ user-agent supplied in the Request
, configLogExecWithContext :: LogExecWithContext -- ^ Run a block using a Logger instance
, configLogContext :: LogContext -- ^ Configures the logger
, configAuthMethods :: [AnyAuthMethod] -- ^ List of configured auth methods
}
-- | display the config
instance Show {{configType}} where
show c =
T.printf
"{ configHost = %v, configUserAgent = %v, ..}"
(show (configHost c))
(show (configUserAgent c))
-- | constructs a default {{configType}}
--
-- configHost:
--
-- @{{basePath}}@
--
-- configUserAgent:
--
-- @"{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}{{{artifactId}}}/{{{artifactVersion}}}{{/httpUserAgent}}"@
--
newConfig :: IO {{configType}}
newConfig = do
logCxt <- initLogContext
return $ {{configType}}
{ configHost = "{{{basePath}}}"
, configUserAgent = "{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}{{{artifactId}}}/{{{artifactVersion}}}{{/httpUserAgent}}"
, configLogExecWithContext = runDefaultLogExecWithContext
, configLogContext = logCxt
, configAuthMethods = []
}
-- | updates config use AuthMethod on matching requests
addAuthMethod :: AuthMethod auth => {{configType}} -> auth -> {{configType}}
addAuthMethod config@{{configType}} {configAuthMethods = as} a =
config { configAuthMethods = AnyAuthMethod a : as}
-- | updates the config to use stdout logging
withStdoutLogging :: {{configType}} -> IO {{configType}}
withStdoutLogging p = do
logCxt <- stdoutLoggingContext (configLogContext p)
return $ p { configLogExecWithContext = stdoutLoggingExec, configLogContext = logCxt }
-- | updates the config to use stderr logging
withStderrLogging :: {{configType}} -> IO {{configType}}
withStderrLogging p = do
logCxt <- stderrLoggingContext (configLogContext p)
return $ p { configLogExecWithContext = stderrLoggingExec, configLogContext = logCxt }
-- | updates the config to disable logging
withNoLogging :: {{configType}} -> {{configType}}
withNoLogging p = p { configLogExecWithContext = runNullLogExec}
-- * Dispatch
@@ -233,35 +161,28 @@ _toInitRequest
-> {{requestType}} req contentType res -- ^ request
-> accept -- ^ "accept" 'MimeType'
-> IO (InitRequest req contentType res accept) -- ^ initialized request
_toInitRequest config req0 accept = do
parsedReq <- NH.parseRequest $ BCL.unpack $ BCL.append (configHost config) (BCL.concat (rUrlPath req0))
let req1 = _applyAuthMethods req0 config
& _setContentTypeHeader
& flip _setAcceptHeader accept
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders (rParams req1)
reqQuery = NH.renderQuery True (paramsQuery (rParams req1))
pReq = parsedReq { NH.method = (rMethod req1)
, NH.requestHeaders = reqHeaders
, NH.queryString = reqQuery
}
outReq <- case paramsBody (rParams req1) of
ParamBodyNone -> pure (pReq { NH.requestBody = mempty })
ParamBodyB bs -> pure (pReq { NH.requestBody = NH.RequestBodyBS bs })
ParamBodyBL bl -> pure (pReq { NH.requestBody = NH.RequestBodyLBS bl })
ParamBodyFormUrlEncoded form -> pure (pReq { NH.requestBody = NH.RequestBodyLBS (WH.urlEncodeForm form) })
ParamBodyMultipartFormData parts -> NH.formDataBody parts pReq
_toInitRequest config req0 accept =
runConfigLogWithExceptions "Client" config $ do
parsedReq <- P.liftIO $ NH.parseRequest $ BCL.unpack $ BCL.append (configHost config) (BCL.concat (rUrlPath req0))
req1 <- P.liftIO $ _applyAuthMethods req0 config
P.when
(configValidateAuthMethods config && (not . null . rAuthTypes) req1)
(E.throwString $ "AuthMethod not configured: " <> (show . head . rAuthTypes) req1)
let req2 = req1 & _setContentTypeHeader & flip _setAcceptHeader accept
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders (rParams req2)
reqQuery = NH.renderQuery True (paramsQuery (rParams req2))
pReq = parsedReq { NH.method = (rMethod req2)
, NH.requestHeaders = reqHeaders
, NH.queryString = reqQuery
}
outReq <- case paramsBody (rParams req2) of
ParamBodyNone -> pure (pReq { NH.requestBody = mempty })
ParamBodyB bs -> pure (pReq { NH.requestBody = NH.RequestBodyBS bs })
ParamBodyBL bl -> pure (pReq { NH.requestBody = NH.RequestBodyLBS bl })
ParamBodyFormUrlEncoded form -> pure (pReq { NH.requestBody = NH.RequestBodyLBS (WH.urlEncodeForm form) })
ParamBodyMultipartFormData parts -> NH.formDataBody parts pReq
pure (InitRequest outReq)
-- | apply all matching AuthMethods in config to request
_applyAuthMethods
:: {{requestType}} req contentType res
-> {{configType}}
-> {{requestType}} req contentType res
_applyAuthMethods req {{configType}} {configAuthMethods = as} =
foldl go req as
where
go r (AnyAuthMethod a) = r `applyAuthMethod` a
pure (InitRequest outReq)
-- | modify the underlying Request
modifyInitRequest :: InitRequest req contentType res accept -> (NH.Request -> NH.Request) -> InitRequest req contentType res accept

View File

@@ -0,0 +1,522 @@
{{>partial_header}}
{-|
Module : {{title}}.Core
-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-unused-binds #-}
module {{title}}.Core where
import {{title}}.MimeTypes
import {{title}}.Logging
import qualified Control.Arrow as P (left)
import qualified Control.DeepSeq as NF
import qualified Data.Aeson as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64.Lazy as BL64
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Data.CaseInsensitive as CI
import qualified Data.Data as P (Data, Typeable, TypeRep, typeRep)
import qualified Data.Foldable as P
import qualified Data.Ix as P
import qualified Data.Maybe as P
import qualified Data.Proxy as P (Proxy(..))
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Time as TI
import qualified Data.Time.ISO8601 as TI
import qualified GHC.Base as P (Alternative)
import qualified Lens.Micro as L
import qualified Network.HTTP.Client.MultipartFormData as NH
import qualified Network.HTTP.Types as NH
import qualified Prelude as P
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import qualified Text.Printf as T
import Control.Applicative ((<|>))
import Control.Applicative (Alternative)
import Data.Function ((&))
import Data.Foldable(foldlM)
import Data.Monoid ((<>))
import Data.Text (Text)
import Prelude (($), (.), (<$>), (<*>), Maybe(..), Bool(..), Char, String, fmap, mempty, pure, return, show, IO, Monad, Functor)
-- * {{configType}}
-- |
data {{configType}} = {{configType}}
{ configHost :: BCL.ByteString -- ^ host supplied in the Request
, configUserAgent :: Text -- ^ user-agent supplied in the Request
, configLogExecWithContext :: LogExecWithContext -- ^ Run a block using a Logger instance
, configLogContext :: LogContext -- ^ Configures the logger
, configAuthMethods :: [AnyAuthMethod] -- ^ List of configured auth methods
, configValidateAuthMethods :: Bool -- ^ throw exceptions if auth methods are not configured
}
-- | display the config
instance P.Show {{configType}} where
show c =
T.printf
"{ configHost = %v, configUserAgent = %v, ..}"
(show (configHost c))
(show (configUserAgent c))
-- | constructs a default {{configType}}
--
-- configHost:
--
-- @{{basePath}}@
--
-- configUserAgent:
--
-- @"{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}{{{artifactId}}}/{{{artifactVersion}}}{{/httpUserAgent}}"@
--
newConfig :: IO {{configType}}
newConfig = do
logCxt <- initLogContext
return $ {{configType}}
{ configHost = "{{{basePath}}}"
, configUserAgent = "{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}{{{artifactId}}}/{{{artifactVersion}}}{{/httpUserAgent}}"
, configLogExecWithContext = runDefaultLogExecWithContext
, configLogContext = logCxt
, configAuthMethods = []
, configValidateAuthMethods = True
}
-- | updates config use AuthMethod on matching requests
addAuthMethod :: AuthMethod auth => {{configType}} -> auth -> {{configType}}
addAuthMethod config@{{configType}} {configAuthMethods = as} a =
config { configAuthMethods = AnyAuthMethod a : as}
-- | updates the config to use stdout logging
withStdoutLogging :: {{configType}} -> IO {{configType}}
withStdoutLogging p = do
logCxt <- stdoutLoggingContext (configLogContext p)
return $ p { configLogExecWithContext = stdoutLoggingExec, configLogContext = logCxt }
-- | updates the config to use stderr logging
withStderrLogging :: {{configType}} -> IO {{configType}}
withStderrLogging p = do
logCxt <- stderrLoggingContext (configLogContext p)
return $ p { configLogExecWithContext = stderrLoggingExec, configLogContext = logCxt }
-- | updates the config to disable logging
withNoLogging :: {{configType}} -> {{configType}}
withNoLogging p = p { configLogExecWithContext = runNullLogExec}
-- * {{requestType}}
-- | Represents a request. The "req" type variable is the request type. The "res" type variable is the response type.
data {{requestType}} req contentType res = {{requestType}}
{ rMethod :: NH.Method -- ^ Method of {{requestType}}
, rUrlPath :: [BCL.ByteString] -- ^ Endpoint of {{requestType}}
, rParams :: Params -- ^ params of {{requestType}}
, rAuthTypes :: [P.TypeRep] -- ^ types of auth methods
}
deriving (P.Show)
-- | 'rMethod' Lens
rMethodL :: Lens_' ({{requestType}} req contentType res) NH.Method
rMethodL f {{requestType}}{..} = (\rMethod -> {{requestType}} { rMethod, ..} ) <$> f rMethod
{-# INLINE rMethodL #-}
-- | 'rUrlPath' Lens
rUrlPathL :: Lens_' ({{requestType}} req contentType res) [BCL.ByteString]
rUrlPathL f {{requestType}}{..} = (\rUrlPath -> {{requestType}} { rUrlPath, ..} ) <$> f rUrlPath
{-# INLINE rUrlPathL #-}
-- | 'rParams' Lens
rParamsL :: Lens_' ({{requestType}} req contentType res) Params
rParamsL f {{requestType}}{..} = (\rParams -> {{requestType}} { rParams, ..} ) <$> f rParams
{-# INLINE rParamsL #-}
-- | 'rParams' Lens
rAuthTypesL :: Lens_' ({{requestType}} req contentType res) [P.TypeRep]
rAuthTypesL f {{requestType}}{..} = (\rAuthTypes -> {{requestType}} { rAuthTypes, ..} ) <$> f rAuthTypes
{-# INLINE rAuthTypesL #-}
-- * HasBodyParam
-- | Designates the body parameter of a request
class HasBodyParam req param where
setBodyParam :: forall contentType res. (Consumes req contentType, MimeRender contentType param) => {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
setBodyParam req xs =
req `_setBodyLBS` mimeRender (P.Proxy :: P.Proxy contentType) xs & _setContentTypeHeader
-- * HasOptionalParam
-- | Designates the optional parameters of a request
class HasOptionalParam req param where
{-# MINIMAL applyOptionalParam | (-&-) #-}
-- | Apply an optional parameter to a request
applyOptionalParam :: {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
applyOptionalParam = (-&-)
{-# INLINE applyOptionalParam #-}
-- | infix operator \/ alias for 'addOptionalParam'
(-&-) :: {{requestType}} req contentType res -> param -> {{requestType}} req contentType res
(-&-) = applyOptionalParam
{-# INLINE (-&-) #-}
infixl 2 -&-
-- | Request Params
data Params = Params
{ paramsQuery :: NH.Query
, paramsHeaders :: NH.RequestHeaders
, paramsBody :: ParamBody
}
deriving (P.Show)
-- | 'paramsQuery' Lens
paramsQueryL :: Lens_' Params NH.Query
paramsQueryL f Params{..} = (\paramsQuery -> Params { paramsQuery, ..} ) <$> f paramsQuery
{-# INLINE paramsQueryL #-}
-- | 'paramsHeaders' Lens
paramsHeadersL :: Lens_' Params NH.RequestHeaders
paramsHeadersL f Params{..} = (\paramsHeaders -> Params { paramsHeaders, ..} ) <$> f paramsHeaders
{-# INLINE paramsHeadersL #-}
-- | 'paramsBody' Lens
paramsBodyL :: Lens_' Params ParamBody
paramsBodyL f Params{..} = (\paramsBody -> Params { paramsBody, ..} ) <$> f paramsBody
{-# INLINE paramsBodyL #-}
-- | Request Body
data ParamBody
= ParamBodyNone
| ParamBodyB B.ByteString
| ParamBodyBL BL.ByteString
| ParamBodyFormUrlEncoded WH.Form
| ParamBodyMultipartFormData [NH.Part]
deriving (P.Show)
-- ** {{requestType}} Utils
_mkRequest :: NH.Method -- ^ Method
-> [BCL.ByteString] -- ^ Endpoint
-> {{requestType}} req contentType res -- ^ req: Request Type, res: Response Type
_mkRequest m u = {{requestType}} m u _mkParams []
_mkParams :: Params
_mkParams = Params [] [] ParamBodyNone
setHeader :: {{requestType}} req contentType res -> [NH.Header] -> {{requestType}} req contentType res
setHeader req header =
req `removeHeader` P.fmap P.fst header &
L.over (rParamsL . paramsHeadersL) (header P.++)
removeHeader :: {{requestType}} req contentType res -> [NH.HeaderName] -> {{requestType}} req contentType res
removeHeader req header =
req &
L.over
(rParamsL . paramsHeadersL)
(P.filter (\h -> cifst h `P.notElem` P.fmap CI.mk header))
where
cifst = CI.mk . P.fst
_setContentTypeHeader :: forall req contentType res. MimeType contentType => {{requestType}} req contentType res -> {{requestType}} req contentType res
_setContentTypeHeader req =
case mimeType (P.Proxy :: P.Proxy contentType) of
Just m -> req `setHeader` [("content-type", BC.pack $ P.show m)]
Nothing -> req `removeHeader` ["content-type"]
_setAcceptHeader :: forall req contentType res accept. MimeType accept => {{requestType}} req contentType res -> accept -> {{requestType}} req contentType res
_setAcceptHeader req accept =
case mimeType' accept of
Just m -> req `setHeader` [("accept", BC.pack $ P.show m)]
Nothing -> req `removeHeader` ["accept"]
setQuery :: {{requestType}} req contentType res -> [NH.QueryItem] -> {{requestType}} req contentType res
setQuery req query =
req &
L.over
(rParamsL . paramsQueryL)
((query P.++) . P.filter (\q -> cifst q `P.notElem` P.fmap cifst query))
where
cifst = CI.mk . P.fst
addForm :: {{requestType}} req contentType res -> WH.Form -> {{requestType}} req contentType res
addForm req newform =
let form = case paramsBody (rParams req) of
ParamBodyFormUrlEncoded _form -> _form
_ -> mempty
in req & L.set (rParamsL . paramsBodyL) (ParamBodyFormUrlEncoded (newform <> form))
_addMultiFormPart :: {{requestType}} req contentType res -> NH.Part -> {{requestType}} req contentType res
_addMultiFormPart req newpart =
let parts = case paramsBody (rParams req) of
ParamBodyMultipartFormData _parts -> _parts
_ -> []
in req & L.set (rParamsL . paramsBodyL) (ParamBodyMultipartFormData (newpart : parts))
_setBodyBS :: {{requestType}} req contentType res -> B.ByteString -> {{requestType}} req contentType res
_setBodyBS req body =
req & L.set (rParamsL . paramsBodyL) (ParamBodyB body)
_setBodyLBS :: {{requestType}} req contentType res -> BL.ByteString -> {{requestType}} req contentType res
_setBodyLBS req body =
req & L.set (rParamsL . paramsBodyL) (ParamBodyBL body)
_hasAuthType :: AuthMethod authMethod => {{requestType}} req contentType res -> P.Proxy authMethod -> {{requestType}} req contentType res
_hasAuthType req proxy =
req & L.over rAuthTypesL (P.typeRep proxy :)
-- ** Params Utils
toPath
:: WH.ToHttpApiData a
=> a -> BCL.ByteString
toPath = BB.toLazyByteString . WH.toEncodedUrlPiece
toHeader :: WH.ToHttpApiData a => (NH.HeaderName, a) -> [NH.Header]
toHeader x = [fmap WH.toHeader x]
toForm :: WH.ToHttpApiData v => (BC.ByteString, v) -> WH.Form
toForm (k,v) = WH.toForm [(BC.unpack k,v)]
toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
toQuery x = [(fmap . fmap) toQueryParam x]
where toQueryParam = T.encodeUtf8 . WH.toQueryParam
-- *** Swagger `CollectionFormat` Utils
-- | Determines the format of the array if type array is used.
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`. This is valid only for parameters in "query" ('NH.Query') or "formData" ('WH.Form')
toHeaderColl :: WH.ToHttpApiData a => CollectionFormat -> (NH.HeaderName, [a]) -> [NH.Header]
toHeaderColl c xs = _toColl c toHeader xs
toFormColl :: WH.ToHttpApiData v => CollectionFormat -> (BC.ByteString, [v]) -> WH.Form
toFormColl c xs = WH.toForm $ fmap unpack $ _toColl c toHeader $ pack xs
where
pack (k,v) = (CI.mk k, v)
unpack (k,v) = (BC.unpack (CI.original k), BC.unpack v)
toQueryColl :: WH.ToHttpApiData a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query
toQueryColl c xs = _toCollA c toQuery xs
_toColl :: P.Traversable f => CollectionFormat -> (f a -> [(b, BC.ByteString)]) -> f [a] -> [(b, BC.ByteString)]
_toColl c encode xs = fmap (fmap P.fromJust) (_toCollA' c fencode BC.singleton (fmap Just xs))
where fencode = fmap (fmap Just) . encode . fmap P.fromJust
{-# INLINE fencode #-}
_toCollA :: (P.Traversable f, P.Traversable t, P.Alternative t) => CollectionFormat -> (f (t a) -> [(b, t BC.ByteString)]) -> f (t [a]) -> [(b, t BC.ByteString)]
_toCollA c encode xs = _toCollA' c encode BC.singleton xs
_toCollA' :: (P.Monoid c, P.Traversable f, P.Traversable t, P.Alternative t) => CollectionFormat -> (f (t a) -> [(b, t c)]) -> (Char -> c) -> f (t [a]) -> [(b, t c)]
_toCollA' c encode one xs = case c of
CommaSeparated -> go (one ',')
SpaceSeparated -> go (one ' ')
TabSeparated -> go (one '\t')
PipeSeparated -> go (one '|')
MultiParamArray -> expandList
where
go sep =
[P.foldl1 (\(sk, sv) (_, v) -> (sk, (combine sep <$> sv <*> v) <|> sv <|> v)) expandList]
combine sep x y = x <> sep <> y
expandList = (P.concatMap encode . (P.traverse . P.traverse) P.toList) xs
{-# INLINE go #-}
{-# INLINE expandList #-}
{-# INLINE combine #-}
-- * AuthMethods
-- | Provides a method to apply auth methods to requests
class P.Typeable a =>
AuthMethod a where
applyAuthMethod
:: {{configType}}
-> a
-> {{requestType}} req contentType res
-> IO ({{requestType}} req contentType res)
-- | An existential wrapper for any AuthMethod
data AnyAuthMethod = forall a. AuthMethod a => AnyAuthMethod a deriving (P.Typeable)
instance AuthMethod AnyAuthMethod where applyAuthMethod config (AnyAuthMethod a) req = applyAuthMethod config a req
-- | apply all matching AuthMethods in config to request
_applyAuthMethods
:: {{requestType}} req contentType res
-> {{configType}}
-> IO ({{requestType}} req contentType res)
_applyAuthMethods req config@({{configType}} {configAuthMethods = as}) =
foldlM go req as
where
go r (AnyAuthMethod a) = applyAuthMethod config a r
-- * Utils
-- | Removes Null fields. (OpenAPI-Specification 2.0 does not allow Null in JSON)
_omitNulls :: [(Text, A.Value)] -> A.Value
_omitNulls = A.object . P.filter notNull
where
notNull (_, A.Null) = False
notNull _ = True
-- | Encodes fields using WH.toQueryParam
_toFormItem :: (WH.ToHttpApiData a, Functor f) => t -> f a -> f (t, [Text])
_toFormItem name x = (name,) . (:[]) . WH.toQueryParam <$> x
-- | Collapse (Just "") to Nothing
_emptyToNothing :: Maybe String -> Maybe String
_emptyToNothing (Just "") = Nothing
_emptyToNothing x = x
{-# INLINE _emptyToNothing #-}
-- | Collapse (Just mempty) to Nothing
_memptyToNothing :: (P.Monoid a, P.Eq a) => Maybe a -> Maybe a
_memptyToNothing (Just x) | x P.== P.mempty = Nothing
_memptyToNothing x = x
{-# INLINE _memptyToNothing #-}
-- * DateTime Formatting
newtype DateTime = DateTime { unDateTime :: TI.UTCTime }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData,TI.ParseTime,TI.FormatTime)
instance A.FromJSON DateTime where
parseJSON = A.withText "DateTime" (_readDateTime . T.unpack)
instance A.ToJSON DateTime where
toJSON (DateTime t) = A.toJSON (_showDateTime t)
instance WH.FromHttpApiData DateTime where
parseUrlPiece = P.left T.pack . _readDateTime . T.unpack
instance WH.ToHttpApiData DateTime where
toUrlPiece (DateTime t) = T.pack (_showDateTime t)
instance P.Show DateTime where
show (DateTime t) = _showDateTime t
instance MimeRender MimeMultipartFormData DateTime where
mimeRender _ = mimeRenderDefaultMultipartFormData
-- | @{{^dateTimeFormat}}_parseISO8601{{/dateTimeFormat}}{{#dateTimeFormat}}TI.parseTimeM True TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}@
_readDateTime :: (TI.ParseTime t, Monad m, {{^dateTimeFormat}}Alternative m{{/dateTimeFormat}}) => String -> m t
_readDateTime =
{{^dateTimeFormat}}_parseISO8601{{/dateTimeFormat}}{{#dateTimeFormat}}TI.parseTimeM True TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}
{-# INLINE _readDateTime #-}
-- | @{{^dateTimeFormat}}TI.formatISO8601Millis{{/dateTimeFormat}}{{#dateTimeFormat}}TI.formatTime TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}@
_showDateTime :: ({{^dateTimeFormat}}t ~ TI.UTCTime, {{/dateTimeFormat}}TI.FormatTime t) => t -> String
_showDateTime =
{{^dateTimeFormat}}TI.formatISO8601Millis{{/dateTimeFormat}}{{#dateTimeFormat}}TI.formatTime TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}
{-# INLINE _showDateTime #-}
-- | parse an ISO8601 date-time string
_parseISO8601 :: (TI.ParseTime t, Monad m, Alternative m) => String -> m t
_parseISO8601 t =
P.asum $
P.flip (TI.parseTimeM True TI.defaultTimeLocale) t <$>
["%FT%T%QZ", "%FT%T%Q%z", "%FT%T%Q%Z"]
{-# INLINE _parseISO8601 #-}
-- * Date Formatting
newtype Date = Date { unDate :: TI.Day }
deriving (P.Enum,P.Eq,P.Data,P.Ord,P.Ix,NF.NFData,TI.ParseTime,TI.FormatTime)
instance A.FromJSON Date where
parseJSON = A.withText "Date" (_readDate . T.unpack)
instance A.ToJSON Date where
toJSON (Date t) = A.toJSON (_showDate t)
instance WH.FromHttpApiData Date where
parseUrlPiece = P.left T.pack . _readDate . T.unpack
instance WH.ToHttpApiData Date where
toUrlPiece (Date t) = T.pack (_showDate t)
instance P.Show Date where
show (Date t) = _showDate t
instance MimeRender MimeMultipartFormData Date where
mimeRender _ = mimeRenderDefaultMultipartFormData
-- | @TI.parseTimeM True TI.defaultTimeLocale "{{{dateFormat}}}"@
_readDate :: (TI.ParseTime t, Monad m) => String -> m t
_readDate =
TI.parseTimeM True TI.defaultTimeLocale "{{{dateFormat}}}"
{-# INLINE _readDate #-}
-- | @TI.formatTime TI.defaultTimeLocale "{{{dateFormat}}}"@
_showDate :: TI.FormatTime t => t -> String
_showDate =
TI.formatTime TI.defaultTimeLocale "{{{dateFormat}}}"
{-# INLINE _showDate #-}
-- * Byte/Binary Formatting
-- | base64 encoded characters
newtype ByteArray = ByteArray { unByteArray :: BL.ByteString }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData)
instance A.FromJSON ByteArray where
parseJSON = A.withText "ByteArray" _readByteArray
instance A.ToJSON ByteArray where
toJSON = A.toJSON . _showByteArray
instance WH.FromHttpApiData ByteArray where
parseUrlPiece = P.left T.pack . _readByteArray
instance WH.ToHttpApiData ByteArray where
toUrlPiece = _showByteArray
instance P.Show ByteArray where
show = T.unpack . _showByteArray
instance MimeRender MimeMultipartFormData ByteArray where
mimeRender _ = mimeRenderDefaultMultipartFormData
-- | read base64 encoded characters
_readByteArray :: Monad m => Text -> m ByteArray
_readByteArray = P.either P.fail (pure . ByteArray) . BL64.decode . BL.fromStrict . T.encodeUtf8
{-# INLINE _readByteArray #-}
-- | show base64 encoded characters
_showByteArray :: ByteArray -> Text
_showByteArray = T.decodeUtf8 . BL.toStrict . BL64.encode . unByteArray
{-# INLINE _showByteArray #-}
-- | any sequence of octets
newtype Binary = Binary { unBinary :: BL.ByteString }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData)
instance A.FromJSON Binary where
parseJSON = A.withText "Binary" _readBinaryBase64
instance A.ToJSON Binary where
toJSON = A.toJSON . _showBinaryBase64
instance WH.FromHttpApiData Binary where
parseUrlPiece = P.left T.pack . _readBinaryBase64
instance WH.ToHttpApiData Binary where
toUrlPiece = _showBinaryBase64
instance P.Show Binary where
show = T.unpack . _showBinaryBase64
instance MimeRender MimeMultipartFormData Binary where
mimeRender _ = unBinary
_readBinaryBase64 :: Monad m => Text -> m Binary
_readBinaryBase64 = P.either P.fail (pure . Binary) . BL64.decode . BL.fromStrict . T.encodeUtf8
{-# INLINE _readBinaryBase64 #-}
_showBinaryBase64 :: Binary -> Text
_showBinaryBase64 = T.decodeUtf8 . BL.toStrict . BL64.encode . unBinary
{-# INLINE _showBinaryBase64 #-}
-- * Lens Type Aliases
type Lens_' s a = Lens_ s s a a
type Lens_ s t a b = forall (f :: * -> *). Functor f => (a -> f b) -> s -> f t

View File

@@ -10,9 +10,6 @@ Katip Logging functions
module {{title}}.Logging where
import Data.Text (Text)
import GHC.Exts (IsString(..))
import qualified Control.Exception.Safe as E
import qualified Control.Monad.IO.Class as P
import qualified Control.Monad.Trans.Reader as P
@@ -20,6 +17,9 @@ import qualified Data.Text as T
import qualified Lens.Micro as L
import qualified System.IO as IO
import Data.Text (Text)
import GHC.Exts (IsString(..))
import qualified Katip as LG
-- * Type Aliases (for compatability)

View File

@@ -10,13 +10,13 @@ monad-logger Logging functions
module {{title}}.Logging where
import Data.Text (Text)
import qualified Control.Exception.Safe as E
import qualified Control.Monad.IO.Class as P
import qualified Data.Text as T
import qualified Data.Time as TI
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Control.Monad.Logger as LG

View File

@@ -13,39 +13,35 @@ Module : {{title}}.MimeTypes
module {{title}}.MimeTypes where
import {{title}}.Model as M
import qualified Control.Arrow as P (left)
import qualified Data.Aeson as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BCL
import qualified Network.HTTP.Media as ME
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import qualified Data.Data as P (Typeable)
import qualified Data.Proxy as P (Proxy(..))
import qualified Data.Text as T
import qualified Data.String as P
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Control.Arrow as P (left)
import qualified Network.HTTP.Media as ME
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import Prelude (($), (.),(<$>),(<*>),Maybe(..),Bool(..),Char,Double,FilePath,Float,Int,Integer,String,fmap,undefined,mempty)
import qualified Prelude as P
-- * Content Negotiation
-- | A type for responses without content-body.
data NoContent = NoContent
deriving (P.Show, P.Eq)
-- * Consumes Class
-- ** Mime Types
class MimeType mtype => Consumes req mtype where
-- * Produces Class
class MimeType mtype => Produces req mtype where
-- * Default Mime Types
data MimeJSON = MimeJSON deriving (P.Typeable)
data MimeXML = MimeXML deriving (P.Typeable)
@@ -56,10 +52,12 @@ data MimeOctetStream = MimeOctetStream deriving (P.Typeable)
data MimeNoContent = MimeNoContent deriving (P.Typeable)
data MimeAny = MimeAny deriving (P.Typeable)
{{#x-unknownMimeTypes}}data {{{x-mediaDataType}}} = {{{x-mediaDataType}}} deriving (P.Typeable)
{{/x-unknownMimeTypes}}
-- | A type for responses without content-body.
data NoContent = NoContent
deriving (P.Show, P.Eq, P.Typeable)
-- ** MimeType Class
-- * MimeType Class
class P.Typeable mtype => MimeType mtype where
{-# MINIMAL mimeType | mimeTypes #-}
@@ -81,7 +79,7 @@ class P.Typeable mtype => MimeType mtype where
mimeTypes' :: mtype -> [ME.MediaType]
mimeTypes' _ = mimeTypes (P.Proxy :: P.Proxy mtype)
-- ** MimeType Instances
-- Default MimeType Instances
-- | @application/json; charset=utf-8@
instance MimeType MimeJSON where
@@ -107,16 +105,7 @@ instance MimeType MimeAny where
instance MimeType MimeNoContent where
mimeType _ = Nothing
{{#x-unknownMimeTypes}}
-- | @{{{mediaType}}}@
instance MimeType {{{x-mediaDataType}}} where
mimeType _ = Just $ P.fromString "{{{mediaType}}}"{{#x-mediaIsJson}}
instance A.ToJSON a => MimeRender {{{x-mediaDataType}}} a where mimeRender _ = A.encode
instance A.FromJSON a => MimeUnrender {{{x-mediaDataType}}} a where mimeUnrender _ = A.eitherDecode{{/x-mediaIsJson}}
{{/x-unknownMimeTypes}}
-- ** MimeRender Class
-- * MimeRender Class
class MimeType mtype => MimeRender mtype x where
mimeRender :: P.Proxy mtype -> x -> BL.ByteString
@@ -124,7 +113,10 @@ class MimeType mtype => MimeRender mtype x where
mimeRender' _ x = mimeRender (P.Proxy :: P.Proxy mtype) x
-- ** MimeRender Instances
mimeRenderDefaultMultipartFormData :: WH.ToHttpApiData a => a -> BL.ByteString
mimeRenderDefaultMultipartFormData = BL.fromStrict . T.encodeUtf8 . WH.toQueryParam
-- Default MimeRender Instances
-- | `A.encode`
instance A.ToJSON a => MimeRender MimeJSON a where mimeRender _ = A.encode
@@ -146,13 +138,9 @@ instance MimeRender MimeOctetStream T.Text where mimeRender _ = BL.fromStrict .
instance MimeRender MimeOctetStream String where mimeRender _ = BCL.pack
instance MimeRender MimeMultipartFormData BL.ByteString where mimeRender _ = P.id
instance MimeRender MimeMultipartFormData Binary where mimeRender _ = unBinary
instance MimeRender MimeMultipartFormData ByteArray where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Bool where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Char where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Date where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData DateTime where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Double where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Float where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData Int where mimeRender _ = mimeRenderDefaultMultipartFormData
@@ -160,29 +148,18 @@ instance MimeRender MimeMultipartFormData Integer where mimeRender _ = mimeRende
instance MimeRender MimeMultipartFormData String where mimeRender _ = mimeRenderDefaultMultipartFormData
instance MimeRender MimeMultipartFormData T.Text where mimeRender _ = mimeRenderDefaultMultipartFormData
mimeRenderDefaultMultipartFormData :: WH.ToHttpApiData a => a -> BL.ByteString
mimeRenderDefaultMultipartFormData = BL.fromStrict . T.encodeUtf8 . WH.toQueryParam
-- | @P.Right . P.const NoContent@
instance MimeRender MimeNoContent NoContent where mimeRender _ = P.const BCL.empty
-- instance MimeRender MimeOctetStream Double where mimeRender _ = BB.toLazyByteString . BB.doubleDec
-- instance MimeRender MimeOctetStream Float where mimeRender _ = BB.toLazyByteString . BB.floatDec
-- instance MimeRender MimeOctetStream Int where mimeRender _ = BB.toLazyByteString . BB.intDec
-- instance MimeRender MimeOctetStream Integer where mimeRender _ = BB.toLazyByteString . BB.integerDec
{{#x-unknownMimeTypes}}
-- instance MimeRender {{{x-mediaDataType}}} T.Text where mimeRender _ = undefined
{{/x-unknownMimeTypes}}
-- ** MimeUnrender Class
-- * MimeUnrender Class
class MimeType mtype => MimeUnrender mtype o where
mimeUnrender :: P.Proxy mtype -> BL.ByteString -> P.Either String o
mimeUnrender' :: mtype -> BL.ByteString -> P.Either String o
mimeUnrender' _ x = mimeUnrender (P.Proxy :: P.Proxy mtype) x
-- ** MimeUnrender Instances
-- Default MimeUnrender Instances
-- | @A.eitherDecode@
instance A.FromJSON a => MimeUnrender MimeJSON a where mimeUnrender _ = A.eitherDecode
@@ -204,16 +181,4 @@ instance MimeUnrender MimeOctetStream T.Text where mimeUnrender _ = P.left P.sho
instance MimeUnrender MimeOctetStream String where mimeUnrender _ = P.Right . BCL.unpack
-- | @P.Right . P.const NoContent@
instance MimeUnrender MimeNoContent NoContent where mimeUnrender _ = P.Right . P.const NoContent
{{#x-unknownMimeTypes}}
-- instance MimeUnrender {{{x-mediaDataType}}} T.Text where mimeUnrender _ = undefined
{{/x-unknownMimeTypes}}
-- ** Request Consumes
class MimeType mtype => Consumes req mtype where
-- ** Request Produces
class MimeType mtype => Produces req mtype where
instance MimeUnrender MimeNoContent NoContent where mimeUnrender _ = P.Right . P.const NoContent

View File

@@ -17,33 +17,30 @@ Module : {{title}}.Model
module {{title}}.Model where
import {{title}}.Core
import Data.Aeson ((.:),(.:!),(.:?),(.=))
import qualified Data.Aeson as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Base64.Lazy as BL64
import qualified Data.Data as P (Data, Typeable)
import qualified Data.Foldable as P
import qualified Data.HashMap.Lazy as HM
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Maybe as P
import qualified Data.Foldable as P
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import qualified Control.DeepSeq as NF
import qualified Data.Ix as P
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Control.Arrow as P (left)
import Data.Text (Text)
import qualified Data.Time as TI
import qualified Data.Time.ISO8601 as TI
import qualified Web.FormUrlEncoded as WH
import qualified Web.HttpApiData as WH
import Control.Applicative ((<|>))
import Control.Applicative (Alternative)
import Data.Text (Text)
import Prelude (($), (.),(<$>),(<*>),(>>=),Maybe(..),Bool(..),Char,Double,FilePath,Float,Int,Integer,String,fmap,undefined,mempty,maybe,pure,Monad,Applicative,Functor)
import qualified Prelude as P
@@ -59,14 +56,16 @@ import qualified Prelude as P
-- | {{classname}}{{#title}}
-- {{{.}}}
-- {{/title}}{{#description}}
-- {{{.}}}{{/description}}
-- {{{.}}}{{/description}}{{#isAlias}}
newtype {{classname}} = {{classname}}
{ un{{classname}} :: {{{dataType}}}
} deriving (P.Eq, P.Show, P.Typeable, A.ToJSON, A.FromJSON, WH.ToHttpApiData, WH.FromHttpApiData{{#modelDeriving}}, {{modelDeriving}}{{/modelDeriving}}){{/isAlias}}{{^isAlias}}
data {{classname}} = {{classname}}
{ {{#vars}}{{name}} :: {{#x-strictFields}}!({{/x-strictFields}}{{^required}}Maybe {{/required}}{{datatype}}{{#x-strictFields}}){{/x-strictFields}} -- ^ {{#required}}/Required/ {{/required}}{{#readOnly}}/ReadOnly/ {{/readOnly}}"{{baseName}}"{{#description}} - {{description}}{{/description}}{{#hasMore}}
, {{/hasMore}}{{/vars}}
} deriving (P.Show,P.Eq,P.Typeable{{#modelDeriving}},{{modelDeriving}}{{/modelDeriving}})
} deriving (P.Show, P.Eq, P.Typeable{{#modelDeriving}}, {{modelDeriving}}{{/modelDeriving}}){{/isAlias}}
-- | FromJSON {{classname}}
{{^isAlias}}-- | FromJSON {{classname}}
instance A.FromJSON {{classname}} where
parseJSON = A.withObject "{{classname}}" $ \o ->
{{^hasVars}}pure {{/hasVars}}{{classname}}
@@ -109,150 +108,9 @@ mk{{classname}} {{#requiredVars}}{{name}} {{/requiredVars}}=
, {{/hasMore}}{{/vars}}
}
{{/generateModelConstructors}}
{{/isAlias}}
{{/model}}
{{/models}}
-- * Parameter newtypes
{{#x-allUniqueParams}}
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
{{/x-allUniqueParams}}
-- * Utils
-- | Removes Null fields. (OpenAPI-Specification 2.0 does not allow Null in JSON)
_omitNulls :: [(Text, A.Value)] -> A.Value
_omitNulls = A.object . P.filter notNull
where
notNull (_, A.Null) = False
notNull _ = True
-- | Encodes fields using WH.toQueryParam
_toFormItem :: (WH.ToHttpApiData a, Functor f) => t -> f a -> f (t, [Text])
_toFormItem name x = (name,) . (:[]) . WH.toQueryParam <$> x
-- | Collapse (Just "") to Nothing
_emptyToNothing :: Maybe String -> Maybe String
_emptyToNothing (Just "") = Nothing
_emptyToNothing x = x
{-# INLINE _emptyToNothing #-}
-- | Collapse (Just mempty) to Nothing
_memptyToNothing :: (P.Monoid a, P.Eq a) => Maybe a -> Maybe a
_memptyToNothing (Just x) | x P.== P.mempty = Nothing
_memptyToNothing x = x
{-# INLINE _memptyToNothing #-}
-- * DateTime Formatting
newtype DateTime = DateTime { unDateTime :: TI.UTCTime }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData,TI.ParseTime,TI.FormatTime)
instance A.FromJSON DateTime where
parseJSON = A.withText "DateTime" (_readDateTime . T.unpack)
instance A.ToJSON DateTime where
toJSON (DateTime t) = A.toJSON (_showDateTime t)
instance WH.FromHttpApiData DateTime where
parseUrlPiece = P.left T.pack . _readDateTime . T.unpack
instance WH.ToHttpApiData DateTime where
toUrlPiece (DateTime t) = T.pack (_showDateTime t)
instance P.Show DateTime where
show (DateTime t) = _showDateTime t
-- | @{{^dateTimeFormat}}_parseISO8601{{/dateTimeFormat}}{{#dateTimeFormat}}TI.parseTimeM True TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}@
_readDateTime :: (TI.ParseTime t, Monad m, {{^dateTimeFormat}}Alternative m{{/dateTimeFormat}}) => String -> m t
_readDateTime =
{{^dateTimeFormat}}_parseISO8601{{/dateTimeFormat}}{{#dateTimeFormat}}TI.parseTimeM True TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}
{-# INLINE _readDateTime #-}
-- | @{{^dateTimeFormat}}TI.formatISO8601Millis{{/dateTimeFormat}}{{#dateTimeFormat}}TI.formatTime TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}@
_showDateTime :: ({{^dateTimeFormat}}t ~ TI.UTCTime, {{/dateTimeFormat}}TI.FormatTime t) => t -> String
_showDateTime =
{{^dateTimeFormat}}TI.formatISO8601Millis{{/dateTimeFormat}}{{#dateTimeFormat}}TI.formatTime TI.defaultTimeLocale "{{{dateTimeFormat}}}"{{/dateTimeFormat}}
{-# INLINE _showDateTime #-}
-- | parse an ISO8601 date-time string
_parseISO8601 :: (TI.ParseTime t, Monad m, Alternative m) => String -> m t
_parseISO8601 t =
P.asum $
P.flip (TI.parseTimeM True TI.defaultTimeLocale) t <$>
["%FT%T%QZ", "%FT%T%Q%z", "%FT%T%Q%Z"]
{-# INLINE _parseISO8601 #-}
-- * Date Formatting
newtype Date = Date { unDate :: TI.Day }
deriving (P.Enum,P.Eq,P.Data,P.Ord,P.Ix,NF.NFData,TI.ParseTime,TI.FormatTime)
instance A.FromJSON Date where
parseJSON = A.withText "Date" (_readDate . T.unpack)
instance A.ToJSON Date where
toJSON (Date t) = A.toJSON (_showDate t)
instance WH.FromHttpApiData Date where
parseUrlPiece = P.left T.pack . _readDate . T.unpack
instance WH.ToHttpApiData Date where
toUrlPiece (Date t) = T.pack (_showDate t)
instance P.Show Date where
show (Date t) = _showDate t
-- | @TI.parseTimeM True TI.defaultTimeLocale "{{{dateFormat}}}"@
_readDate :: (TI.ParseTime t, Monad m) => String -> m t
_readDate =
TI.parseTimeM True TI.defaultTimeLocale "{{{dateFormat}}}"
{-# INLINE _readDate #-}
-- | @TI.formatTime TI.defaultTimeLocale "{{{dateFormat}}}"@
_showDate :: TI.FormatTime t => t -> String
_showDate =
TI.formatTime TI.defaultTimeLocale "{{{dateFormat}}}"
{-# INLINE _showDate #-}
-- * Byte/Binary Formatting
-- | base64 encoded characters
newtype ByteArray = ByteArray { unByteArray :: BL.ByteString }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData)
instance A.FromJSON ByteArray where
parseJSON = A.withText "ByteArray" _readByteArray
instance A.ToJSON ByteArray where
toJSON = A.toJSON . _showByteArray
instance WH.FromHttpApiData ByteArray where
parseUrlPiece = P.left T.pack . _readByteArray
instance WH.ToHttpApiData ByteArray where
toUrlPiece = _showByteArray
instance P.Show ByteArray where
show = T.unpack . _showByteArray
-- | read base64 encoded characters
_readByteArray :: Monad m => Text -> m ByteArray
_readByteArray = P.either P.fail (pure . ByteArray) . BL64.decode . BL.fromStrict . T.encodeUtf8
{-# INLINE _readByteArray #-}
-- | show base64 encoded characters
_showByteArray :: ByteArray -> Text
_showByteArray = T.decodeUtf8 . BL.toStrict . BL64.encode . unByteArray
{-# INLINE _showByteArray #-}
-- | any sequence of octets
newtype Binary = Binary { unBinary :: BL.ByteString }
deriving (P.Eq,P.Data,P.Ord,P.Typeable,NF.NFData)
instance A.FromJSON Binary where
parseJSON = A.withText "Binary" _readBinaryBase64
instance A.ToJSON Binary where
toJSON = A.toJSON . _showBinaryBase64
instance WH.FromHttpApiData Binary where
parseUrlPiece = P.left T.pack . _readBinaryBase64
instance WH.ToHttpApiData Binary where
toUrlPiece = _showBinaryBase64
instance P.Show Binary where
show = T.unpack . _showBinaryBase64
_readBinaryBase64 :: Monad m => Text -> m Binary
_readBinaryBase64 = P.either P.fail (pure . Binary) . BL64.decode . BL.fromStrict . T.encodeUtf8
{-# INLINE _readBinaryBase64 #-}
_showBinaryBase64 :: Binary -> Text
_showBinaryBase64 = T.decodeUtf8 . BL.toStrict . BL64.encode . unBinary
{-# INLINE _showBinaryBase64 #-}

View File

@@ -9,7 +9,7 @@ Module : {{title}}.Lens
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-unused-matches -fno-warn-unused-binds -fno-warn-unused-imports #-}
module {{title}}.Lens where
module {{title}}.ModelLens where
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL
@@ -24,11 +24,7 @@ import Prelude (($), (.),(<$>),(<*>),(=<<),Maybe(..),Bool(..),Char,Double,FilePa
import qualified Prelude as P
import {{title}}.Model
-- * Type Aliases
type Lens_' s a = Lens_ s s a a
type Lens_ s t a b = forall (f :: * -> *). Functor f => (a -> f b) -> s -> f t
import {{title}}.Core
{{#models}}
{{#model}}

View File

@@ -70,6 +70,7 @@ These options allow some customization of the code generation process.
| generateFormUrlEncodedInstances | Generate FromForm/ToForm instances for models used by x-www-form-urlencoded operations (model fields must be primitive types) | true | {{{generateFormUrlEncodedInstances}}} |
| generateLenses | Generate Lens optics for Models | true | {{{generateLenses}}} |
| generateModelConstructors | Generate smart constructors (only supply required fields) for models | true | {{{generateModelConstructors}}} |
| inlineConsumesContentTypes | Inline (hardcode) the content-type on operations that do not have multiple content-types (Consumes) | false | {{{inlineConsumesContentTypes}}} |
| modelDeriving | Additional classes to include in the deriving() clause of Models | | {{{modelDeriving}}} |
| strictFields | Add strictness annotations to all model fields | true | {{{x-strictFields}}} |
| useMonadLogger | Use the monad-logger package to provide logging (if instead false, use the katip logging package) | false | {{{x-useMonadLogger}}} |
@@ -109,10 +110,11 @@ This library is intended to be imported qualified.
| MODULE | NOTES |
| ------------------- | --------------------------------------------------- |
| {{title}}.Client | use the "dispatch" functions to send requests |
| {{title}}.API | construct requetss |
| {{title}}.Model | describes models |
| {{title}}.Core | core funcions, config and request types |
| {{title}}.API | construct api requests |
| {{title}}.Model | describes api models |
| {{title}}.MimeTypes | encoding/decoding MIME types (content-types/accept) |
| {{title}}.Lens | lenses for model fields |
| {{title}}.ModelLens | lenses for model fields |
| {{title}}.Logging | logging functions and utils |

View File

@@ -4,17 +4,19 @@ Module : {{title}}
-}
module {{title}}
( module {{title}}.Client
, module {{title}}.API
, module {{title}}.Model
, module {{title}}.MimeTypes
, module {{title}}.Lens
( module {{title}}.API
, module {{title}}.Client
, module {{title}}.Core
, module {{title}}.Logging
, module {{title}}.MimeTypes
, module {{title}}.Model
, module {{title}}.ModelLens
) where
import {{title}}.API
import {{title}}.Client
import {{title}}.Model
import {{title}}.MimeTypes
import {{title}}.Lens
import {{title}}.Core
import {{title}}.Logging
import {{title}}.MimeTypes
import {{title}}.Model
import {{title}}.ModelLens

View File

@@ -0,0 +1 @@
{{^vendorExtensions.x-inlineContentType}}contentType{{/vendorExtensions.x-inlineContentType}}{{#vendorExtensions.x-inlineContentType}}{{{x-mediaDataType}}}{{/vendorExtensions.x-inlineContentType}}

View File

@@ -29,7 +29,7 @@ cabal-version: >= 1.10
extra-source-files:
README.md
swagger.json
swagger.yaml
library
hs-source-dirs:
@@ -65,10 +65,11 @@ library
{{title}}
{{title}}.API
{{title}}.Client
{{title}}.Model
{{title}}.MimeTypes
{{title}}.Lens
{{title}}.Core
{{title}}.Logging
{{title}}.MimeTypes
{{title}}.Model
{{title}}.ModelLens
other-modules:
Paths_{{pathsName}}
default-language: Haskell2010

View File

@@ -0,0 +1 @@
{{{swagger-yaml}}}

View File

@@ -2,10 +2,9 @@
module Instances where
import Control.Monad
import Data.Char (isSpace)
import Data.List (sort)
import Test.QuickCheck
import {{title}}.Model
import {{title}}.Core
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as HM
@@ -14,8 +13,12 @@ import qualified Data.Text as T
import qualified Data.Time as TI
import qualified Data.Vector as V
import Control.Monad
import Data.Char (isSpace)
import Data.List (sort)
import Test.QuickCheck
import ApproxEq
import {{title}}.Model
instance Arbitrary T.Text where
arbitrary = T.pack <$> arbitrary
@@ -90,9 +93,10 @@ instance ApproxEq TI.Day where
{{#model}}
instance Arbitrary {{classname}} where
arbitrary =
{{^hasVars}}pure {{/hasVars}}{{classname}}
{{#isAlias}}{{classname}} <$> arbitrary{{/isAlias}}{{^isAlias}}{{^hasVars}}
pure {{/hasVars}}{{classname}}
{{#hasVars}} <$>{{/hasVars}} {{#vars}}arbitrary -- {{name}} :: {{^required}}Maybe {{/required}}{{datatype}}
{{#hasMore}} <*> {{/hasMore}}{{/vars}}
{{#hasMore}} <*> {{/hasMore}}{{/vars}}{{/isAlias}}
{{/model}}
{{/models}}

View File

@@ -30,7 +30,6 @@ public class HaskellHttpClientOptionsTest extends AbstractOptionsTest {
times = 1;
clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(HaskellHttpClientOptionsProvider.SORT_PARAMS_VALUE));
times = 1;
clientCodegen.setAllowFromJsonNulls(Boolean.valueOf(HaskellHttpClientOptionsProvider.ALLOW_FROMJSON_NULLS));
times = 1;
clientCodegen.setAllowToJsonNulls(Boolean.valueOf(HaskellHttpClientOptionsProvider.ALLOW_TOJSON_NULLS));
@@ -41,6 +40,8 @@ public class HaskellHttpClientOptionsTest extends AbstractOptionsTest {
times = 1;
clientCodegen.setGenerateLenses(Boolean.valueOf(HaskellHttpClientOptionsProvider.GENERATE_LENSES));
times = 1;
clientCodegen.setInlineConsumesContentTypes(Boolean.valueOf(HaskellHttpClientOptionsProvider.INLINE_CONSUMES_CONTENT_TYPES));
times = 1;
clientCodegen.setModelDeriving(HaskellHttpClientOptionsProvider.MODEL_DERIVING);
times = 1;
clientCodegen.setDateTimeFormat(HaskellHttpClientOptionsProvider.DATETIME_FORMAT);

View File

@@ -23,6 +23,7 @@ public class HaskellHttpClientOptionsProvider implements OptionsProvider {
public static final String GENERATE_FORM_URLENCODED_INSTANCES = "true";
public static final String GENERATE_LENSES = "true";
public static final String GENERATE_MODEL_CONSTRUCTORS = "true";
public static final String INLINE_CONSUMES_CONTENT_TYPES = "false";
public static final String USE_MONAD_LOGGER = "false";
@Override
@@ -40,16 +41,17 @@ public class HaskellHttpClientOptionsProvider implements OptionsProvider {
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, HIDE_GENERATION_TIMESTAMP)
.put(HaskellHttpClientCodegen.ALLOW_FROMJSON_NULLS, ALLOW_FROMJSON_NULLS)
.put(HaskellHttpClientCodegen.ALLOW_TOJSON_NULLS, ALLOW_TOJSON_NULLS)
.put(HaskellHttpClientCodegen.DATETIME_FORMAT, DATETIME_FORMAT)
.put(HaskellHttpClientCodegen.DATE_FORMAT, DATE_FORMAT)
.put(HaskellHttpClientCodegen.MODEL_DERIVING, MODEL_DERIVING)
.put(HaskellHttpClientCodegen.GENERATE_FORM_URLENCODED_INSTANCES, GENERATE_FORM_URLENCODED_INSTANCES)
.put(HaskellHttpClientCodegen.GENERATE_LENSES, GENERATE_LENSES)
.put(HaskellHttpClientCodegen.GENERATE_MODEL_CONSTRUCTORS, GENERATE_MODEL_CONSTRUCTORS)
.put(HaskellHttpClientCodegen.STRICT_FIELDS, STRICT_FIELDS)
.put(HaskellHttpClientCodegen.USE_MONAD_LOGGER, USE_MONAD_LOGGER)
.put(HaskellHttpClientCodegen.PROP_ALLOW_FROMJSON_NULLS, ALLOW_FROMJSON_NULLS)
.put(HaskellHttpClientCodegen.PROP_ALLOW_TOJSON_NULLS, ALLOW_TOJSON_NULLS)
.put(HaskellHttpClientCodegen.PROP_DATETIME_FORMAT, DATETIME_FORMAT)
.put(HaskellHttpClientCodegen.PROP_DATE_FORMAT, DATE_FORMAT)
.put(HaskellHttpClientCodegen.PROP_MODEL_DERIVING, MODEL_DERIVING)
.put(HaskellHttpClientCodegen.PROP_GENERATE_FORM_URLENCODED_INSTANCES, GENERATE_FORM_URLENCODED_INSTANCES)
.put(HaskellHttpClientCodegen.PROP_GENERATE_LENSES, GENERATE_LENSES)
.put(HaskellHttpClientCodegen.PROP_GENERATE_MODEL_CONSTRUCTORS, GENERATE_MODEL_CONSTRUCTORS)
.put(HaskellHttpClientCodegen.PROP_INLINE_CONSUMES_CONTENT_TYPES, INLINE_CONSUMES_CONTENT_TYPES)
.put(HaskellHttpClientCodegen.PROP_STRICT_FIELDS, STRICT_FIELDS)
.put(HaskellHttpClientCodegen.PROP_USE_MONAD_LOGGER, USE_MONAD_LOGGER)
.build();
}