[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:
Jon Schoning
2018-01-06 21:59:55 -06:00
committed by William Cheng
parent 919f867eba
commit bca2b9fb5b
77 changed files with 6006 additions and 5151 deletions

View File

@@ -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);

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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}}} |

View File

@@ -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

View File

@@ -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

View File

@@ -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}})

View File

@@ -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));

View File

@@ -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)