Skip to content

Commit 1247e77

Browse files
authored
[haskell-http-client] add ability to choose additional characters in querystring which should not be encoded (e.g. "+" or ":") (fixes #3459) (#10424)
1 parent f2cc234 commit 1247e77

33 files changed

+4690
-4541
lines changed

CI/.drone.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ steps:
4848
- dune build --build-dir=./_build
4949
# test haskell client
5050
- name: haskell-client-test
51-
image: haskell:8.6.5
51+
image: haskell:8.10.4
5252
commands:
5353
- (cd samples/client/petstore/haskell-http-client/ && stack --allow-different-user --install-ghc --no-haddock-deps haddock --fast && stack --allow-different-user test --fast)
5454
# test erlang client and server

bin/configs/haskell-http-client.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ generatorName: haskell-http-client
22
outputDir: samples/client/petstore/haskell-http-client
33
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml
44
templateDir: modules/openapi-generator/src/main/resources/haskell-http-client
5+
additionalProperties:
6+
queryExtraUnreserved: ''

docs/generators/haskell-http-client.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3030
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C#have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
3131
|modelDeriving|Additional classes to include in the deriving() clause of Models| |null|
3232
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
33+
|queryExtraUnreserved|Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':'| |null|
3334
|requestType|Set the name of the type used to generate requests| |null|
3435
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
3536
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
5151
protected String defaultDateFormat = "%Y-%m-%d";
5252
protected String defaultCabalVersion = "0.1.0.0";
5353
protected String modulePath = null;
54+
protected String defaultQueryExtraUnreserved = null;
5455

5556
protected Boolean useKatip = true;
5657
protected Boolean allowNonUniqueOperationIds = false;
@@ -74,6 +75,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
7475
public static final String PROP_GENERATE_MODEL_CONSTRUCTORS = "generateModelConstructors";
7576
public static final String PROP_INLINE_MIME_TYPES = "inlineMimeTypes";
7677
public static final String PROP_MODEL_DERIVING = "modelDeriving";
78+
public static final String PROP_QUERY_EXTRA_UNRESERVED = "queryExtraUnreserved";
7779
public static final String PROP_REQUEST_TYPE = "requestType";
7880
public static final String PROP_STRICT_FIELDS = "strictFields";
7981
public static final String PROP_USE_KATIP = "useKatip";
@@ -125,6 +127,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
125127
static final String X_MEDIA_IS_JSON = "x-mediaIsJson";
126128
static final String X_MEDIA_IS_WILDCARD = "x-mediaIsWildcard";
127129
static final String X_STRICT_FIELDS = "x-strictFields";
130+
static final String X_PROP_QUERY_EXTRA_UNRESERVED = "x-queryExtraUnreserved";
128131
static final String X_ALL_UNIQUE_IMPORT_PATHS = "x-allUniqueImportPaths";
129132
static final String X_USE_KATIP = "x-useKatip";
130133
static final String X_ALLOW_NONUNIQUE_OPERATION_IDS = "x-allowNonUniqueOperationIds";
@@ -308,6 +311,7 @@ public HaskellHttpClientCodegen() {
308311
cliOptions.add(CliOption.newString(PROP_DATETIME_FORMAT, "format string used to parse/render a datetime"));
309312
cliOptions.add(CliOption.newString(PROP_DATETIME_PARSE_FORMAT, "overrides the format string used to parse a datetime"));
310313
cliOptions.add(CliOption.newString(PROP_DATE_FORMAT, "format string used to parse/render a date").defaultValue(defaultDateFormat));
314+
cliOptions.add(CliOption.newString(PROP_QUERY_EXTRA_UNRESERVED, "Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':'"));
311315

312316
cliOptions.add(CliOption.newString(PROP_CUSTOM_TEST_INSTANCE_MODULE, "test module used to provide typeclass instances for types not known by the generator"));
313317

@@ -371,6 +375,10 @@ public void setCabalVersion(String value) {
371375
setStringProp(PROP_CABAL_VERSION, value);
372376
}
373377

378+
public void setQueryExtraUnreserved(String value) {
379+
additionalProperties.put(X_PROP_QUERY_EXTRA_UNRESERVED, value);
380+
}
381+
374382
public void setBaseModule(String value) {
375383
setStringProp(PROP_BASE_MODULE, value);
376384
}
@@ -505,6 +513,11 @@ public void processOpts() {
505513
} else {
506514
setCabalVersion(defaultCabalVersion);
507515
}
516+
if (additionalProperties.containsKey(PROP_QUERY_EXTRA_UNRESERVED)) {
517+
setQueryExtraUnreserved(additionalProperties.get(PROP_QUERY_EXTRA_UNRESERVED).toString());
518+
} else {
519+
setQueryExtraUnreserved(defaultQueryExtraUnreserved);
520+
}
508521
if (additionalProperties.containsKey(PROP_BASE_MODULE)) {
509522
setBaseModule(additionalProperties.get(PROP_BASE_MODULE).toString());
510523
}

modules/openapi-generator/src/main/resources/haskell-http-client/Client.mustache

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import qualified Control.Exception.Safe as E
2323
import qualified Control.Monad.IO.Class as P
2424
import qualified Control.Monad as P
2525
import qualified Data.Aeson.Types as A
26+
import qualified Data.ByteString as B
2627
import qualified Data.ByteString.Char8 as BC
2728
import qualified Data.ByteString.Lazy as BL
2829
import qualified Data.ByteString.Lazy.Char8 as BCL
@@ -170,13 +171,18 @@ _toInitRequest config req0 =
170171
(configValidateAuthMethods config && (not . null . rAuthTypes) req1)
171172
(E.throw $ AuthMethodException $ "AuthMethod not configured: " <> (show . head . rAuthTypes) req1)
172173
let req2 = req1 & _setContentTypeHeader & _setAcceptHeader
173-
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders (rParams req2)
174-
reqQuery = NH.renderQuery True (paramsQuery (rParams req2))
175-
pReq = parsedReq { NH.method = (rMethod req2)
174+
params = rParams req2
175+
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders params
176+
reqQuery = let query = paramsQuery params
177+
queryExtraUnreserved = configQueryExtraUnreserved config
178+
in if B.null queryExtraUnreserved
179+
then NH.renderQuery True query
180+
else NH.renderQueryPartialEscape True (toPartialEscapeQuery queryExtraUnreserved query)
181+
pReq = parsedReq { NH.method = rMethod req2
176182
, NH.requestHeaders = reqHeaders
177183
, NH.queryString = reqQuery
178184
}
179-
outReq <- case paramsBody (rParams req2) of
185+
outReq <- case paramsBody params of
180186
ParamBodyNone -> pure (pReq { NH.requestBody = mempty })
181187
ParamBodyB bs -> pure (pReq { NH.requestBody = NH.RequestBodyBS bs })
182188
ParamBodyBL bl -> pure (pReq { NH.requestBody = NH.RequestBodyLBS bl })

modules/openapi-generator/src/main/resources/haskell-http-client/Core.mustache

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import Data.Function ((&))
5858
import Data.Foldable(foldlM)
5959
import Data.Monoid ((<>))
6060
import Data.Text (Text)
61-
import Prelude (($), (.), (<$>), (<*>), Maybe(..), Bool(..), Char, String, fmap, mempty, pure, return, show, IO, Monad, Functor)
61+
import Prelude (($), (.), (&&), (<$>), (<*>), Maybe(..), Bool(..), Char, String, fmap, mempty, pure, return, show, IO, Monad, Functor, maybe)
6262

6363
-- * {{configType}}
6464

@@ -70,6 +70,7 @@ data {{configType}} = {{configType}}
7070
, configLogContext :: LogContext -- ^ Configures the logger
7171
, configAuthMethods :: [AnyAuthMethod] -- ^ List of configured auth methods
7272
, configValidateAuthMethods :: Bool -- ^ throw exceptions if auth methods are not configured
73+
, configQueryExtraUnreserved :: B.ByteString -- ^ Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':'
7374
}
7475

7576
-- | display the config
@@ -100,6 +101,7 @@ newConfig = do
100101
, configLogContext = logCxt
101102
, configAuthMethods = []
102103
, configValidateAuthMethods = True
104+
, configQueryExtraUnreserved = "{{x-queryExtraUnreserved}}"
103105
}
104106

105107
-- | updates config use AuthMethod on matching requests
@@ -327,6 +329,16 @@ toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
327329
toQuery x = [(fmap . fmap) toQueryParam x]
328330
where toQueryParam = T.encodeUtf8 . WH.toQueryParam
329331

332+
toPartialEscapeQuery :: B.ByteString -> NH.Query -> NH.PartialEscapeQuery
333+
toPartialEscapeQuery extraUnreserved query = fmap (\(k, v) -> (k, maybe [] go v)) query
334+
where go :: B.ByteString -> [NH.EscapeItem]
335+
go v = v & B.groupBy (\a b -> a `B.notElem` extraUnreserved && b `B.notElem` extraUnreserved)
336+
& fmap (\xs -> if B.null xs then NH.QN xs
337+
else if B.head xs `B.elem` extraUnreserved
338+
then NH.QN xs -- Not Encoded
339+
else NH.QE xs -- Encoded
340+
)
341+
330342
-- *** OpenAPI `CollectionFormat` Utils
331343

332344
-- | Determines the format of the array if type array is used.

modules/openapi-generator/src/main/resources/haskell-http-client/README.mustache

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ These options allow some customization of the code generation process.
5555
**haskell-http-client additional properties:**
5656

5757
| OPTION | DESCRIPTION | DEFAULT | ACTUAL |
58-
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------- |
58+
|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|
5959
| allowFromJsonNulls | allow JSON Null during model decoding from JSON | true | {{{allowFromJsonNulls}}} |
6060
| allowNonUniqueOperationIds | allow *different* API modules to contain the same operationId. Each API must be imported qualified | false | {{{x-allowNonUniqueOperationIds}}} |
6161
| allowToJsonNulls | allow emitting JSON Null during model encoding to JSON | false | {{{allowToJsonNulls}}} |
@@ -76,6 +76,7 @@ These options allow some customization of the code generation process.
7676
| requestType | Set the name of the type used to generate requests | | {{{requestType}}} |
7777
| strictFields | Add strictness annotations to all model fields | true | {{{x-strictFields}}} |
7878
| useKatip | Sets the default value for the UseKatip cabal flag. If true, the katip package provides logging instead of monad-logger | true | {{{x-useKatip}}} |
79+
| queryExtraUnreserved | Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':' | | {{{x-queryExtraUnreserved}}} |
7980

8081
[1]: https://www.stackage.org/haddock/lts-9.0/iso8601-time-0.1.4/Data-Time-ISO8601.html#v:formatISO8601Millis
8182

modules/openapi-generator/src/main/resources/haskell-http-client/stack.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
resolver: lts-18.10
1+
resolver: lts-18.6
22
build:
33
haddock-arguments:
44
haddock-args:

samples/client/petstore/haskell-http-client/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ These options allow some customization of the code generation process.
5555
**haskell-http-client additional properties:**
5656

5757
| OPTION | DESCRIPTION | DEFAULT | ACTUAL |
58-
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------- |
58+
|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|
5959
| allowFromJsonNulls | allow JSON Null during model decoding from JSON | true | true |
6060
| allowNonUniqueOperationIds | allow *different* API modules to contain the same operationId. Each API must be imported qualified | false | false |
6161
| allowToJsonNulls | allow emitting JSON Null during model encoding to JSON | false | false |
@@ -76,6 +76,7 @@ These options allow some customization of the code generation process.
7676
| requestType | Set the name of the type used to generate requests | | OpenAPIPetstoreRequest |
7777
| strictFields | Add strictness annotations to all model fields | true | true |
7878
| useKatip | Sets the default value for the UseKatip cabal flag. If true, the katip package provides logging instead of monad-logger | true | true |
79+
| queryExtraUnreserved | Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':' | | |
7980

8081
[1]: https://www.stackage.org/haddock/lts-9.0/iso8601-time-0.1.4/Data-Time-ISO8601.html#v:formatISO8601Millis
8182

samples/client/petstore/haskell-http-client/docs/OpenAPIPetstore-Client.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

samples/client/petstore/haskell-http-client/docs/OpenAPIPetstore-Core.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

samples/client/petstore/haskell-http-client/docs/OpenAPIPetstore-MimeTypes.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

samples/client/petstore/haskell-http-client/docs/doc-index-All.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)