mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-12-10 21:02:42 +00:00
[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:
@@ -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) {
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 #-}
|
||||
|
||||
@@ -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}}
|
||||
@@ -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 |
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{^vendorExtensions.x-inlineContentType}}contentType{{/vendorExtensions.x-inlineContentType}}{{#vendorExtensions.x-inlineContentType}}{{{x-mediaDataType}}}{{/vendorExtensions.x-inlineContentType}}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{{swagger-yaml}}}
|
||||
@@ -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}}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user