forked from loafle/openapi-generator-original
[haskell-http-client] handle */* mimetype correctly & split api modules & allowNonUniqueOperationIds (#7254)
* [haskell-http-client] handle */* mimetype correctly * [haskell-http-client] generate separate api modules, for each child api * [haskell-http-client] add cliOption "allowNonUniqueOperationIds" When cli option "allowNonUniqueOperationIds" is true, allows *different* API modules to contain the same operationId, and then each API must be imported qualified.
This commit is contained in:
committed by
William Cheng
parent
919f867eba
commit
bca2b9fb5b
@@ -8,6 +8,7 @@ import io.swagger.models.properties.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.io.File;
|
||||
|
||||
import io.swagger.models.auth.SecuritySchemeDefinition;
|
||||
import io.swagger.codegen.CliOption;
|
||||
@@ -28,7 +29,7 @@ import java.util.regex.Matcher;
|
||||
public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
|
||||
// source folder where to write the files
|
||||
protected String sourceFolder = "src";
|
||||
protected String sourceFolder = "lib";
|
||||
|
||||
protected String artifactId = "swagger-haskell-http-client";
|
||||
protected String artifactVersion = "1.0.0";
|
||||
@@ -36,11 +37,13 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
protected String defaultDateFormat = "%Y-%m-%d";
|
||||
|
||||
protected Boolean useMonadLogger = false;
|
||||
protected Boolean allowNonUniqueOperationIds = false;
|
||||
protected Boolean genEnums = true;
|
||||
|
||||
// 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_ALLOW_NONUNIQUE_OPERATION_IDS = "allowNonUniqueOperationIds";
|
||||
public static final String PROP_DATETIME_FORMAT = "dateTimeFormat";
|
||||
public static final String PROP_DATE_FORMAT = "dateFormat";
|
||||
public static final String PROP_GENERATE_ENUMS = "generateEnums";
|
||||
@@ -79,6 +82,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
static final String X_DATA_TYPE = "x-dataType";
|
||||
static final String X_ENUM_VALUES = "x-enumValues";
|
||||
static final String X_MEDIA_IS_JSON = "x-mediaIsJson";
|
||||
static final String X_MEDIA_IS_WILDCARD = "x-mediaIsWildcard";
|
||||
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";
|
||||
@@ -87,6 +91,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
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";
|
||||
static final String X_ALLOW_NONUNIQUE_OPERATION_IDS = "x-allowNonUniqueOperationIds";
|
||||
static final String X_NEWTYPE = "x-newtype";
|
||||
static final String X_ENUM = "x-enum";
|
||||
|
||||
@@ -96,6 +101,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
protected Map<String, Set<String>> modelMimeTypes = new HashMap<>();
|
||||
protected Map<String, String> knownMimeDataTypes = new HashMap<>();
|
||||
protected Set<String> typeNames = new HashSet<String>();
|
||||
protected Set<String> modelTypeNames = new HashSet<String>();
|
||||
|
||||
public CodegenType getTag() {
|
||||
return CodegenType.CLIENT;
|
||||
@@ -128,8 +134,8 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
outputFolder = "generated-code/haskell-http-client";
|
||||
|
||||
embeddedTemplateDir = templateDir = "haskell-http-client";
|
||||
apiPackage = "API";
|
||||
modelPackage = "Model";
|
||||
//apiPackage = "API";
|
||||
//modelPackage = "Model";
|
||||
|
||||
// Haskell keywords and reserved function names, taken mostly from https://wiki.haskell.org/Keywords
|
||||
setReservedWordsLowerCase(
|
||||
@@ -217,6 +223,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
|
||||
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_ALLOW_NONUNIQUE_OPERATION_IDS, "allow different API modules to contain the same operationId. Each API must be imported qualified").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_ENUMS, "Generate specific datatypes for swagger enums").defaultValue(Boolean.TRUE.toString()));
|
||||
@@ -235,6 +242,10 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
|
||||
}
|
||||
|
||||
public void setAllowNonUniqueOperationIds(Boolean value) {
|
||||
additionalProperties.put(X_ALLOW_NONUNIQUE_OPERATION_IDS, value);
|
||||
this.allowNonUniqueOperationIds = value;
|
||||
}
|
||||
public void setAllowFromJsonNulls(Boolean value) {
|
||||
additionalProperties.put(PROP_ALLOW_FROMJSON_NULLS, value);
|
||||
}
|
||||
@@ -317,6 +328,12 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
setAllowToJsonNulls(false);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(PROP_ALLOW_NONUNIQUE_OPERATION_IDS)) {
|
||||
setAllowNonUniqueOperationIds(convertPropertyToBoolean(PROP_ALLOW_NONUNIQUE_OPERATION_IDS));
|
||||
} else {
|
||||
setAllowNonUniqueOperationIds(false);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(PROP_GENERATE_MODEL_CONSTRUCTORS)) {
|
||||
setGenerateModelConstructors(convertPropertyToBoolean(PROP_GENERATE_MODEL_CONSTRUCTORS));
|
||||
} else {
|
||||
@@ -408,7 +425,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
for (String word : words) {
|
||||
wordsCaps.add(firstLetterToUpper(word));
|
||||
}
|
||||
String apiName = StringUtils.join(wordsCaps, "");
|
||||
apiPackage = StringUtils.join(wordsCaps, "");
|
||||
|
||||
// Set the filenames to write for the API
|
||||
|
||||
@@ -417,31 +434,34 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
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("TopLevel.mustache", sourceFolder + File.separator, apiPackage + ".hs"));
|
||||
supportingFiles.add(new SupportingFile("Client.mustache", sourceFolder + File.separator + apiPackage, "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"));
|
||||
|
||||
if(!allowNonUniqueOperationIds) {
|
||||
supportingFiles.add(new SupportingFile("APIS.mustache", sourceFolder + File.separator + apiPackage, "API.hs"));
|
||||
}
|
||||
supportingFiles.add(new SupportingFile("Core.mustache", sourceFolder + File.separator + apiPackage, "Core.hs"));
|
||||
supportingFiles.add(new SupportingFile("Model.mustache", sourceFolder + File.separator + apiPackage, "Model.hs"));
|
||||
supportingFiles.add(new SupportingFile("MimeTypes.mustache", sourceFolder + File.separator + apiPackage, "MimeTypes.hs"));
|
||||
|
||||
// logger
|
||||
supportingFiles.add(new SupportingFile(useMonadLogger ? "LoggingMonadLogger.mustache" : "LoggingKatip.mustache", "lib/" + apiName, "Logging.hs"));
|
||||
supportingFiles.add(new SupportingFile(useMonadLogger ? "LoggingMonadLogger.mustache" : "LoggingKatip.mustache", sourceFolder + File.separator + apiPackage, "Logging.hs"));
|
||||
|
||||
// modelTemplateFiles.put("API.mustache", ".hs");
|
||||
// apiTemplateFiles.put("Model.mustache", ".hs");
|
||||
apiTemplateFiles.put("API.mustache", ".hs");
|
||||
// modelTemplateFiles.put("Model.mustache", ".hs");
|
||||
|
||||
// lens
|
||||
if ((boolean)additionalProperties.get(PROP_GENERATE_LENSES)) {
|
||||
supportingFiles.add(new SupportingFile("ModelLens.mustache", "lib/" + apiName, "ModelLens.hs"));
|
||||
supportingFiles.add(new SupportingFile("ModelLens.mustache", sourceFolder + File.separator + apiPackage, "ModelLens.hs"));
|
||||
}
|
||||
|
||||
additionalProperties.put("title", apiName);
|
||||
additionalProperties.put("titleLower", firstLetterToLower(apiName));
|
||||
additionalProperties.put("title", apiPackage);
|
||||
additionalProperties.put("titleLower", firstLetterToLower(apiPackage));
|
||||
additionalProperties.put("package", cabalName);
|
||||
additionalProperties.put("pathsName", pathsName);
|
||||
additionalProperties.put("requestType", apiName + "Request");
|
||||
additionalProperties.put("configType", apiName + "Config");
|
||||
additionalProperties.put("requestType", apiPackage + "Request");
|
||||
additionalProperties.put("configType", apiPackage + "Config");
|
||||
additionalProperties.put("swaggerVersion", swagger.getSwagger());
|
||||
|
||||
super.preprocessSwagger(swagger);
|
||||
@@ -508,9 +528,41 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map<String, Model> definitions, Swagger swagger) {
|
||||
CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger);
|
||||
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation op, Map<String, List<CodegenOperation>> operations) {
|
||||
|
||||
List<CodegenOperation> opList = operations.get(tag);
|
||||
if (opList == null) {
|
||||
opList = new ArrayList<CodegenOperation>();
|
||||
operations.put(tag, opList);
|
||||
}
|
||||
// check for operationId uniqueness
|
||||
String uniqueName = op.operationId;
|
||||
String uniqueNameType = toTypeName("Op", uniqueName);
|
||||
int counter = 0;
|
||||
|
||||
HashSet<String> opIds = new HashSet<>();
|
||||
for (CodegenOperation o : opList) {
|
||||
opIds.add(o.operationId);
|
||||
}
|
||||
while (opIds.contains(uniqueName) ||
|
||||
(allowNonUniqueOperationIds
|
||||
? modelTypeNames.contains(uniqueNameType) // only check for model conflicts
|
||||
: typeNames.contains(uniqueNameType))) { // check globally across all types
|
||||
uniqueName = op.operationId + counter;
|
||||
uniqueNameType = toTypeName("Op", uniqueName);
|
||||
counter++;
|
||||
}
|
||||
if (!op.operationId.equals(uniqueName)) {
|
||||
LOGGER.warn("generated unique operationId `" + uniqueName + "`");
|
||||
}
|
||||
op.operationId = uniqueName;
|
||||
op.operationIdLowerCase = uniqueName.toLowerCase();
|
||||
op.operationIdCamelCase = DefaultCodegen.camelize(uniqueName);
|
||||
op.operationIdSnakeCase = DefaultCodegen.underscore(uniqueName);
|
||||
opList.add(op);
|
||||
op.baseName = tag;
|
||||
|
||||
// prevent aliasing/sharing of operation.vendorExtensions reference
|
||||
op.vendorExtensions = new LinkedHashMap();
|
||||
@@ -539,7 +591,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
|
||||
String dataType = genEnums && param.isEnum ? param.datatypeWithEnum : param.dataType;
|
||||
|
||||
String paramNameType = toDedupedName(toTypeName("Param", param.paramName), dataType, !param.isEnum);
|
||||
String paramNameType = toDedupedModelName(toTypeName("Param", param.paramName), dataType, !param.isEnum);
|
||||
param.vendorExtensions.put(X_PARAM_NAME_TYPE, paramNameType);
|
||||
|
||||
HashMap<String, Object> props = new HashMap<>();
|
||||
@@ -554,7 +606,6 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
|
||||
processReturnType(op);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -647,6 +698,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
model.classname = generateNextName(model.classname);
|
||||
}
|
||||
typeNames.add(model.classname);
|
||||
modelTypeNames.add(model.classname);
|
||||
|
||||
// From the model name, compute the prefix for the fields.
|
||||
String prefix = StringUtils.uncapitalize(model.classname);
|
||||
@@ -721,7 +773,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
}
|
||||
if (op.hasProduces) {
|
||||
for (Map<String, String> m : op.produces) {
|
||||
processMediaType(op,m);
|
||||
processMediaType(op, m);
|
||||
processInlineProducesContentType(op, m);
|
||||
}
|
||||
}
|
||||
@@ -753,7 +805,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
op.vendorExtensions.put(inlineExtentionName, m);
|
||||
}
|
||||
|
||||
private String toDedupedName(String paramNameType, String dataType, Boolean appendDataType) {
|
||||
private String toDedupedModelName(String paramNameType, String dataType, Boolean appendDataType) {
|
||||
if (appendDataType
|
||||
&& uniqueParamNameTypes.containsKey(paramNameType)
|
||||
&& !isDuplicate(paramNameType, dataType)) {
|
||||
@@ -768,6 +820,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
}
|
||||
|
||||
typeNames.add(paramNameType);
|
||||
modelTypeNames.add(paramNameType);
|
||||
return paramNameType;
|
||||
}
|
||||
|
||||
@@ -842,7 +895,9 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
if (isJsonMimeType(mediaType)) {
|
||||
m.put(X_MEDIA_IS_JSON, "true");
|
||||
}
|
||||
|
||||
if (isWildcardMimeType(mediaType)) {
|
||||
m.put(X_MEDIA_IS_WILDCARD, "true");
|
||||
}
|
||||
if (!knownMimeDataTypes.containsValue(mimeType) && !unknownMimeTypesContainsType(mimeType)) {
|
||||
unknownMimeTypes.add(m);
|
||||
}
|
||||
@@ -968,6 +1023,20 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
public String toModelFilename(String name) {
|
||||
return toTypeName("Model", name);
|
||||
}
|
||||
public String toApiName(String name) {
|
||||
if (name.length() == 0) {
|
||||
return "Default";
|
||||
}
|
||||
return toTypeName("Api", name);
|
||||
}
|
||||
@Override
|
||||
public String toApiFilename(String name) {
|
||||
return toTypeName("Api", name);
|
||||
}
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar) + File.separator + "API";
|
||||
}
|
||||
public String toTypeName(String prefix, String name) {
|
||||
name = escapeIdentifier(prefix, camelize(sanitizeName(name)));
|
||||
return name;
|
||||
@@ -978,17 +1047,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
throw new RuntimeException("Empty method/operation name (operationId) not allowed");
|
||||
}
|
||||
operationId = escapeIdentifier("op",camelize(sanitizeName(operationId), true));
|
||||
String uniqueName = operationId;
|
||||
String uniqueNameType = toTypeName("Op", operationId);
|
||||
while (typeNames.contains(uniqueNameType)) {
|
||||
uniqueName = generateNextName(uniqueName);
|
||||
uniqueNameType = toTypeName("Op", uniqueName);
|
||||
}
|
||||
typeNames.add(uniqueNameType);
|
||||
if(!operationId.equals(uniqueName)) {
|
||||
LOGGER.warn("generated unique operationId `" + uniqueName + "`");
|
||||
}
|
||||
return uniqueName;
|
||||
return operationId;
|
||||
}
|
||||
public String escapeIdentifier(String prefix, String name) {
|
||||
if(StringUtils.isBlank(prefix)) return name;
|
||||
@@ -1011,6 +1070,10 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
return mime != null && JSON_MIME_PATTERN.matcher(mime).matches();
|
||||
}
|
||||
|
||||
static boolean isWildcardMimeType(String mime) {
|
||||
return mime != null && mime.equals("*/*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Property p) {
|
||||
if (p instanceof StringProperty) {
|
||||
@@ -1100,7 +1163,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
|
||||
if (duplicateEnum.getLeft()) {
|
||||
paramNameType = duplicateEnum.getRight();
|
||||
} else {
|
||||
paramNameType = toDedupedName(paramNameType, enumValues, false);
|
||||
paramNameType = toDedupedModelName(paramNameType, enumValues, false);
|
||||
var.datatypeWithEnum = paramNameType;
|
||||
updateCodegenPropertyEnum(var);
|
||||
addEnumToUniques(paramNameType, var.datatype, enumValues, var.allowableValues, var.description);
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
{{>partial_header}}
|
||||
{-|
|
||||
Module : {{title}}.API
|
||||
Module : {{title}}.API.{{classname}}
|
||||
-}
|
||||
|
||||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE InstanceSigs #-}
|
||||
{-# LANGUAGE MonoLocalBinds #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-unused-binds -fno-warn-unused-imports #-}
|
||||
|
||||
module {{title}}.API where
|
||||
module {{title}}.API.{{classname}} where
|
||||
|
||||
import {{title}}.Core
|
||||
import {{title}}.MimeTypes
|
||||
@@ -24,8 +18,6 @@ import {{title}}.Model as M
|
||||
|
||||
import qualified Data.Aeson as A
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
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
|
||||
@@ -39,16 +31,12 @@ 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 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 ((&))
|
||||
import Data.Text (Text)
|
||||
import GHC.Base ((<|>))
|
||||
|
||||
@@ -56,7 +44,7 @@ import Prelude ((==),(/=),($), (.),(<$>),(<*>),(>>=),Maybe(..),Bool(..),Char,Dou
|
||||
import qualified Prelude as P
|
||||
|
||||
-- * Operations
|
||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#vendorExtensions.x-hasNewTag}}
|
||||
{{#operations}}{{#operation}}{{#vendorExtensions.x-hasNewTag}}
|
||||
|
||||
-- ** {{baseName}}{{/vendorExtensions.x-hasNewTag}}
|
||||
|
||||
@@ -100,75 +88,11 @@ instance HasOptionalParam {{{vendorExtensions.x-operationType}}} {{{vendorExtens
|
||||
applyOptionalParam req ({{{vendorExtensions.x-paramNameType}}} xs) =
|
||||
{{#isHeaderParam}}req `setHeader` {{>_headerColl}} ("{{{baseName}}}", xs){{/isHeaderParam}}{{#isQueryParam}}req `setQuery` {{>_queryColl}} ("{{{baseName}}}", Just xs){{/isQueryParam}}{{#isFormParam}}{{#isFile}}req `_addMultiFormPart` NH.partFileSource "{{{baseName}}}" xs{{/isFile}}{{^isFile}}{{#isMultipart}}req `_addMultiFormPart` NH.partLBS "{{{baseName}}}" (mimeRender' MimeMultipartFormData xs){{/isMultipart}}{{^isMultipart}}req `addForm` {{>_formColl}} ("{{{baseName}}}", xs){{/isMultipart}}{{/isFile}}{{/isFormParam}}{{/required}}{{/isBodyParam}}{{/allParams}}{{/vendorExtensions.x-hasOptionalParams}}{{#hasConsumes}}
|
||||
|
||||
{{#consumes}}-- | @{{{mediaType}}}@
|
||||
instance Consumes {{{vendorExtensions.x-operationType}}} {{{x-mediaDataType}}}
|
||||
{{#consumes}}-- | @{{{mediaType}}}@{{^x-mediaIsWildcard}}
|
||||
instance Consumes {{{vendorExtensions.x-operationType}}} {{{x-mediaDataType}}}{{/x-mediaIsWildcard}}{{#x-mediaIsWildcard}}
|
||||
instance MimeType mtype => Consumes {{{vendorExtensions.x-operationType}}} mtype{{/x-mediaIsWildcard}}
|
||||
{{/consumes}}{{/hasConsumes}}{{#hasProduces}}
|
||||
{{#produces}}-- | @{{{mediaType}}}@
|
||||
instance Produces {{{vendorExtensions.x-operationType}}} {{{x-mediaDataType}}}
|
||||
{{/produces}}{{/hasProduces}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||
|
||||
|
||||
-- * Parameter newtypes
|
||||
{{#x-allUniqueParams}}{{#x-newtype}}
|
||||
newtype {{{x-paramNameType}}} = {{{x-paramNameType}}} { un{{{x-paramNameType}}} :: {{{x-dataType}}} } deriving (P.Eq, P.Show{{#x-isBodyParam}}, A.ToJSON{{/x-isBodyParam}}){{/x-newtype}}{{/x-allUniqueParams}}
|
||||
|
||||
{{#authMethods}}{{#-first}}-- * Auth Methods
|
||||
|
||||
{{/-first}}{{#isBasic}}-- ** {{name}}
|
||||
data {{name}} =
|
||||
{{name}} B.ByteString B.ByteString -- ^ username password
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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 ])
|
||||
|
||||
{{/isBasic}}{{#isApiKey}}-- ** {{name}}
|
||||
data {{name}} =
|
||||
{{name}} Text -- ^ secret
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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}}
|
||||
data {{name}} =
|
||||
{{name}} Text -- ^ secret
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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}}
|
||||
{{#produces}}-- | @{{{mediaType}}}@{{^x-mediaIsWildcard}}
|
||||
instance Produces {{{vendorExtensions.x-operationType}}} {{{x-mediaDataType}}}{{/x-mediaIsWildcard}}{{#x-mediaIsWildcard}}
|
||||
instance MimeType mtype => Produces {{{vendorExtensions.x-operationType}}} mtype{{/x-mediaIsWildcard}}
|
||||
{{/produces}}{{/hasProduces}}{{/operation}}{{/operations}}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{{>partial_header}}
|
||||
{-|
|
||||
Module : {{title}}.API
|
||||
-}
|
||||
|
||||
module {{title}}.API
|
||||
( {{#apiInfo}}{{#apis}}module {{title}}.API.{{classname}}
|
||||
{{#hasMore}}, {{/hasMore}}{{/apis}}{{/apiInfo}}) where
|
||||
{{#apiInfo}}{{#apis}}
|
||||
import {{title}}.API.{{classname}}{{/apis}}{{/apiInfo}}
|
||||
@@ -190,3 +190,21 @@ instance MimeUnrender MimeOctetStream String where mimeUnrender _ = P.Right . BC
|
||||
|
||||
-- | @P.Right . P.const NoContent@
|
||||
instance MimeUnrender MimeNoContent NoContent where mimeUnrender _ = P.Right . P.const NoContent
|
||||
|
||||
|
||||
{{#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}}
|
||||
@@ -27,8 +27,10 @@ import Data.Aeson ((.:),(.:!),(.:?),(.=))
|
||||
import qualified Control.Arrow as P (left)
|
||||
import qualified Data.Aeson as A
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as BC
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import qualified Data.Data as P (Data, Typeable)
|
||||
import qualified Data.Data as P (Typeable, TypeRep, typeOf, typeRep)
|
||||
import qualified Data.Foldable as P
|
||||
import qualified Data.HashMap.Lazy as HM
|
||||
import qualified Data.Map as Map
|
||||
@@ -37,13 +39,16 @@ import qualified Data.Set as Set
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as T
|
||||
import qualified Data.Time as TI
|
||||
import qualified Lens.Micro as L
|
||||
import qualified Web.FormUrlEncoded as WH
|
||||
import qualified Web.HttpApiData as WH
|
||||
|
||||
import Control.Applicative ((<|>))
|
||||
import Control.Applicative (Alternative)
|
||||
import Data.Function ((&))
|
||||
import Data.Monoid ((<>))
|
||||
import Data.Text (Text)
|
||||
import Prelude (($), (.),(<$>),(<*>),(>>=),(=<<),Maybe(..),Bool(..),Char,Double,FilePath,Float,Int,Integer,String,fmap,undefined,mempty,maybe,pure,Monad,Applicative,Functor)
|
||||
import Prelude (($),(/=),(.),(<$>),(<*>),(>>=),(=<<),Maybe(..),Bool(..),Char,Double,FilePath,Float,Int,Integer,String,fmap,undefined,mempty,maybe,pure,Monad,Applicative,Functor)
|
||||
|
||||
import qualified Prelude as P
|
||||
|
||||
@@ -51,6 +56,12 @@ import qualified Prelude as P
|
||||
{{#imports}}import {{import}}
|
||||
{{/imports}}
|
||||
|
||||
-- * Parameter newtypes
|
||||
{{#x-allUniqueParams}}{{#x-newtype}}
|
||||
|
||||
-- ** {{{x-paramNameType}}}
|
||||
newtype {{{x-paramNameType}}} = {{{x-paramNameType}}} { un{{{x-paramNameType}}} :: {{{x-dataType}}} } deriving (P.Eq, P.Show{{#x-isBodyParam}}, A.ToJSON{{/x-isBodyParam}}){{/x-newtype}}{{/x-allUniqueParams}}
|
||||
|
||||
-- * Models
|
||||
|
||||
{{#models}}
|
||||
@@ -139,4 +150,48 @@ to{{{x-paramNameType}}} :: {{{x-dataType}}} -> P.Either String {{{x-paramNameTyp
|
||||
to{{{x-paramNameType}}} = \case{{#allowableValues}}{{#enumVars}}
|
||||
{{{value}}} -> P.Right {{{name}}}{{/enumVars}}{{/allowableValues}}
|
||||
s -> P.Left $ "to{{{x-paramNameType}}}: enum parse failure: " P.++ P.show s
|
||||
{{/x-enum}}{{/x-allUniqueParams}}{{/x-hasEnumSection}}
|
||||
{{/x-enum}}{{/x-allUniqueParams}}{{/x-hasEnumSection}}
|
||||
|
||||
{{#authMethods}}{{#-first}}-- * Auth Methods
|
||||
|
||||
{{/-first}}{{#isBasic}}-- ** {{name}}
|
||||
data {{name}} =
|
||||
{{name}} B.ByteString B.ByteString -- ^ username password
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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 ])
|
||||
|
||||
{{/isBasic}}{{#isApiKey}}-- ** {{name}}
|
||||
data {{name}} =
|
||||
{{name}} Text -- ^ secret
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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}}
|
||||
data {{name}} =
|
||||
{{name}} Text -- ^ secret
|
||||
deriving (P.Eq, P.Show, P.Typeable)
|
||||
|
||||
instance AuthMethod {{name}} where
|
||||
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}}
|
||||
@@ -58,6 +58,7 @@ These options allow some customization of the code generation process.
|
||||
|
||||
| OPTION | DESCRIPTION | DEFAULT | ACTUAL |
|
||||
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------- |
|
||||
| allowNonUniqueOperationIds | allow *different* API modules to contain the same operationId. Each API must be imported qualified | false | {{{x-allowNonUniqueOperationIds}}} |
|
||||
| allowFromJsonNulls | allow JSON Null during model decoding from JSON | true | {{{allowFromJsonNulls}}} |
|
||||
| allowToJsonNulls | allow emitting JSON Null during model encoding to JSON | false | {{{allowToJsonNulls}}} |
|
||||
| dateFormat | format string used to parse/render a date | %Y-%m-%d | {{{dateFormat}}} |
|
||||
|
||||
@@ -4,8 +4,8 @@ Module : {{title}}
|
||||
-}
|
||||
|
||||
module {{title}}
|
||||
( module {{title}}.API
|
||||
, module {{title}}.Client
|
||||
( {{^x-allowNonUniqueOperationIds}} module {{title}}.API
|
||||
,{{/x-allowNonUniqueOperationIds}} module {{title}}.Client
|
||||
, module {{title}}.Core
|
||||
, module {{title}}.Logging
|
||||
, module {{title}}.MimeTypes
|
||||
@@ -13,10 +13,10 @@ module {{title}}
|
||||
, module {{title}}.ModelLens
|
||||
) where
|
||||
|
||||
import {{title}}.API
|
||||
{{^x-allowNonUniqueOperationIds}}import {{title}}.API{{/x-allowNonUniqueOperationIds}}
|
||||
import {{title}}.Client
|
||||
import {{title}}.Core
|
||||
import {{title}}.Logging
|
||||
import {{title}}.MimeTypes
|
||||
import {{title}}.Model
|
||||
import {{title}}.ModelLens
|
||||
import {{title}}.ModelLens
|
||||
@@ -36,34 +36,35 @@ library
|
||||
lib
|
||||
ghc-options: -Wall -funbox-strict-fields
|
||||
build-depends:
|
||||
base >=4.7 && <5.0
|
||||
, transformers >=0.4.0.0
|
||||
, mtl >=2.2.1
|
||||
, unordered-containers
|
||||
, aeson >=1.0 && <2.0
|
||||
, bytestring >=0.10.0 && <0.11
|
||||
aeson >=1.0 && <2.0
|
||||
, base >=4.7 && <5.0
|
||||
, base64-bytestring >1.0 && <2.0
|
||||
, bytestring >=0.10.0 && <0.11
|
||||
, case-insensitive
|
||||
, containers >=0.5.0.0 && <0.6
|
||||
, http-types >=0.8 && <0.11
|
||||
, deepseq >= 1.4 && <1.6
|
||||
, exceptions >= 0.4
|
||||
, http-api-data >= 0.3.4 && <0.4
|
||||
, http-client >=0.5 && <0.6
|
||||
, http-client-tls
|
||||
, http-api-data >= 0.3.4 && <0.4
|
||||
, http-media >= 0.4 && < 0.8
|
||||
, text >=0.11 && <1.3
|
||||
, time >=1.5 && <1.9
|
||||
, http-types >=0.8 && <0.12
|
||||
, iso8601-time >=0.1.3 && <0.2.0
|
||||
, vector >=0.10.9 && <0.13
|
||||
, microlens >= 0.4.3 && <0.5
|
||||
, mtl >=2.2.1
|
||||
, network >=2.6.2 && <2.7
|
||||
, random >=1.1
|
||||
, exceptions >= 0.4
|
||||
, {{^x-useMonadLogger}}katip >=0.4 && < 0.6{{/x-useMonadLogger}}{{#x-useMonadLogger}}monad-logger >=0.3 && <0.4{{/x-useMonadLogger}}
|
||||
, safe-exceptions <0.2
|
||||
, case-insensitive
|
||||
, microlens >= 0.4.3 && <0.5
|
||||
, deepseq >= 1.4 && <1.6
|
||||
, text >=0.11 && <1.3
|
||||
, time >=1.5 && <1.9
|
||||
, transformers >=0.4.0.0
|
||||
, unordered-containers
|
||||
, vector >=0.10.9 && <0.13
|
||||
, {{^x-useMonadLogger}}katip >=0.4 && < 0.6{{/x-useMonadLogger}}{{#x-useMonadLogger}}monad-logger >=0.3 && <0.4{{/x-useMonadLogger}}
|
||||
exposed-modules:
|
||||
{{title}}
|
||||
{{title}}.API
|
||||
{{title}}{{^x-allowNonUniqueOperationIds}}
|
||||
{{title}}.API{{/x-allowNonUniqueOperationIds}}{{#apiInfo}}{{#apis}}
|
||||
{{title}}.API.{{classname}}{{/apis}}{{/apiInfo}}
|
||||
{{title}}.Client
|
||||
{{title}}.Core
|
||||
{{title}}.Logging
|
||||
@@ -81,21 +82,21 @@ test-suite tests
|
||||
tests
|
||||
ghc-options: -Wall -fno-warn-orphans
|
||||
build-depends:
|
||||
base >=4.7 && <5.0
|
||||
, transformers >=0.4.0.0
|
||||
, mtl >=2.2.1
|
||||
, unordered-containers
|
||||
, {{package}}
|
||||
{{package}}
|
||||
, QuickCheck
|
||||
, aeson
|
||||
, base >=4.7 && <5.0
|
||||
, bytestring >=0.10.0 && <0.11
|
||||
, containers
|
||||
, hspec >=1.8
|
||||
, iso8601-time
|
||||
, mtl >=2.2.1
|
||||
, semigroups
|
||||
, text
|
||||
, time
|
||||
, iso8601-time
|
||||
, aeson
|
||||
, transformers >=0.4.0.0
|
||||
, unordered-containers
|
||||
, vector
|
||||
, semigroups
|
||||
, QuickCheck
|
||||
other-modules:
|
||||
ApproxEq
|
||||
Instances
|
||||
|
||||
@@ -17,7 +17,7 @@ import {{title}}.MimeTypes
|
||||
|
||||
main :: IO ()
|
||||
main =
|
||||
hspec $ modifyMaxSize (const 10) $ do
|
||||
hspec $ modifyMaxSize (const 5) $ do
|
||||
describe "JSON instances" $ do
|
||||
pure ()
|
||||
{{#models}}{{#model}}propMimeEq MimeJSON (Proxy :: Proxy {{classname}})
|
||||
|
||||
@@ -30,6 +30,8 @@ public class HaskellHttpClientOptionsTest extends AbstractOptionsTest {
|
||||
times = 1;
|
||||
clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(HaskellHttpClientOptionsProvider.SORT_PARAMS_VALUE));
|
||||
times = 1;
|
||||
clientCodegen.setAllowNonUniqueOperationIds(Boolean.valueOf(HaskellHttpClientOptionsProvider.ALLOW_NONUNIQUE_OPERATION_IDS));
|
||||
times = 1;
|
||||
clientCodegen.setAllowFromJsonNulls(Boolean.valueOf(HaskellHttpClientOptionsProvider.ALLOW_FROMJSON_NULLS));
|
||||
times = 1;
|
||||
clientCodegen.setAllowToJsonNulls(Boolean.valueOf(HaskellHttpClientOptionsProvider.ALLOW_TOJSON_NULLS));
|
||||
|
||||
@@ -14,6 +14,7 @@ public class HaskellHttpClientOptionsProvider implements OptionsProvider {
|
||||
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
|
||||
public static final String HIDE_GENERATION_TIMESTAMP = "true";
|
||||
|
||||
public static final String ALLOW_NONUNIQUE_OPERATION_IDS = "false";
|
||||
public static final String ALLOW_FROMJSON_NULLS = "true";
|
||||
public static final String ALLOW_TOJSON_NULLS = "false";
|
||||
public static final String DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%Q%z";
|
||||
@@ -42,6 +43,7 @@ public class HaskellHttpClientOptionsProvider implements OptionsProvider {
|
||||
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
|
||||
.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, HIDE_GENERATION_TIMESTAMP)
|
||||
|
||||
.put(HaskellHttpClientCodegen.PROP_ALLOW_NONUNIQUE_OPERATION_IDS, ALLOW_NONUNIQUE_OPERATION_IDS)
|
||||
.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)
|
||||
|
||||
Reference in New Issue
Block a user