Skip to content

Commit 5975e6c

Browse files
authored
Haskell: JSON Query parameters (#18047)
* Allow json encoded query paramters * Also fix haskell-http-client * Regenerate haskell samples
1 parent 6251aa1 commit 5975e6c

25 files changed

Lines changed: 2683 additions & 70 deletions

File tree

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

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -745,10 +745,15 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
745745
param.vendorExtensions.put(VENDOR_EXTENSION_X_IS_BODY_OR_FORM_PARAM, param.isBodyParam || param.isFormParam);
746746
if (!StringUtils.isBlank(param.collectionFormat)) {
747747
param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat));
748-
} else if (!param.isBodyParam && (param.isArray || param.dataType.startsWith("["))) { // param.isArray is sometimes false for list types
749-
// defaulting due to https://github.com/wing328/openapi-generator/issues/72
750-
param.collectionFormat = "csv";
751-
param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat));
748+
} else if (!param.isBodyParam) {
749+
if (param.isArray || param.dataType.startsWith("[")) { // param.isArray is sometimes false for list types
750+
// defaulting due to https://github.com/wing328/openapi-generator/issues/72
751+
param.collectionFormat = "csv";
752+
param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat));
753+
}
754+
}
755+
if (param.isQueryParam && (isJsonMimeType(param.contentType) || ContainsJsonMimeType(param.contentType))) {
756+
param.vendorExtensions.put(X_MEDIA_IS_JSON, "true");
752757
}
753758
if (!param.required) {
754759
op.vendorExtensions.put(VENDOR_EXTENSION_X_HAS_OPTIONAL_PARAMS, true);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,13 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op
514514
// Query parameters appended to routes
515515
for (CodegenParameter param : op.queryParams) {
516516
String paramType = param.dataType;
517+
if (param.contentType == "application/json") {
518+
if (param.isArray) {
519+
paramType = "[JSONQueryParam " + paramType.substring(1, paramType.length() - 1) + "]";
520+
} else {
521+
paramType = "(JSONQueryParam " + paramType + ")";
522+
}
523+
}
517524
if (param.isArray) {
518525
if (StringUtils.isEmpty(param.collectionFormat)) {
519526
param.collectionFormat = "csv";
@@ -549,6 +556,13 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op
549556
path.add("Header \"" + param.baseName + "\" " + param.dataType);
550557

551558
String paramType = param.dataType;
559+
if (param.contentType == "application/json") {
560+
if (param.isArray) {
561+
paramType = "(JSONQueryParam " + paramType.substring(1, paramType.length() - 1) + ")";
562+
} else {
563+
paramType = "(JSONQueryParam " + paramType + ")";
564+
}
565+
}
552566
if (param.isArray) {
553567
if (StringUtils.isEmpty(param.collectionFormat)) {
554568
param.collectionFormat = "csv";

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import qualified Data.Maybe as P
4141
import qualified Data.Proxy as P (Proxy(..))
4242
import qualified Data.Text as T
4343
import qualified Data.Text.Encoding as T
44+
import qualified Data.Text.Lazy.Encoding as TL
4445
import qualified Data.Time as TI
4546
import qualified Data.Time.ISO8601 as TI
4647
import qualified GHC.Base as P (Alternative)
@@ -330,6 +331,9 @@ toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
330331
toQuery x = [(fmap . fmap) toQueryParam x]
331332
where toQueryParam = T.encodeUtf8 . WH.toQueryParam
332333

334+
toJsonQuery :: A.ToJSON a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
335+
toJsonQuery = toQuery . (fmap . fmap) (TL.decodeUtf8 . A.encode)
336+
333337
toPartialEscapeQuery :: B.ByteString -> NH.Query -> NH.PartialEscapeQuery
334338
toPartialEscapeQuery extraUnreserved query = fmap (\(k, v) -> (k, maybe [] go v)) query
335339
where go :: B.ByteString -> [NH.EscapeItem]
@@ -362,6 +366,9 @@ toFormColl c xs = WH.toForm $ fmap unpack $ _toColl c toHeader $ pack xs
362366
toQueryColl :: WH.ToHttpApiData a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query
363367
toQueryColl c xs = _toCollA c toQuery xs
364368

369+
toJsonQueryColl :: A.ToJSON a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query
370+
toJsonQueryColl c xs = _toCollA c toJsonQuery xs
371+
365372
_toColl :: P.Traversable f => CollectionFormat -> (f a -> [(b, BC.ByteString)]) -> f [a] -> [(b, BC.ByteString)]
366373
_toColl c encode xs = fmap (fmap P.fromJust) (_toCollA' c fencode BC.singleton (fmap Just xs))
367374
where fencode = fmap (fmap Just) . encode . fmap P.fromJust

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Module : {{baseModule}}.Model
77
{-# LANGUAGE DeriveFoldable #-}
88
{-# LANGUAGE DeriveGeneric #-}
99
{-# LANGUAGE DeriveTraversable #-}
10+
{-# LANGUAGE DuplicateRecordFields #-}
1011
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
1112
{-# LANGUAGE LambdaCase #-}
1213
{-# LANGUAGE MultiParamTypeClasses #-}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
toQuery{{#collectionFormat}}Coll {{vendorExtensions.x-collection-format}}{{/collectionFormat}}
1+
to{{#vendorExtensions.x-mediaIsJson}}Json{{/vendorExtensions.x-mediaIsJson}}Query{{#collectionFormat}}Coll {{vendorExtensions.x-collection-format}}{{/collectionFormat}}

modules/openapi-generator/src/main/resources/haskell-servant/API.mustache

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import Control.Monad.Except (ExceptT, runExceptT)
4747
import Control.Monad.IO.Class
4848
import Control.Monad.Trans.Reader (ReaderT (..))
4949
import Data.Aeson (Value)
50+
import qualified Data.Aeson as Aeson
5051
{{#authMethods}}
5152
{{#isApiKey}}
5253
import Data.ByteString (ByteString)
@@ -55,6 +56,7 @@ import Data.ByteString (ByteString)
5556
import Data.ByteString (ByteString)
5657
{{/isBasicBearer}}
5758
{{/authMethods}}
59+
import qualified Data.ByteString.Lazy as BSL
5860
import Data.Coerce (coerce)
5961
import Data.Data (Data)
6062
import Data.Function ((&))
@@ -64,6 +66,7 @@ import Data.Proxy (Proxy (..))
6466
import Data.Set (Set)
6567
import Data.Text (Text)
6668
import qualified Data.Text as T
69+
import qualified Data.Text.Encoding as T
6770
import Data.Time
6871
import Data.UUID (UUID)
6972
import GHC.Exts (IsString (..))
@@ -166,6 +169,16 @@ instance ToHttpApiData a => ToHttpApiData (QueryList 'MultiParamArray a) where
166169
formatSeparatedQueryList :: ToHttpApiData a => Char -> QueryList p a -> Text
167170
formatSeparatedQueryList char = T.intercalate (T.singleton char) . map toQueryParam . fromQueryList
168171

172+
newtype JSONQueryParam a = JSONQueryParam
173+
{ fromJsonQueryParam :: a
174+
} deriving (Functor, Foldable, Traversable)
175+
176+
instance Aeson.ToJSON a => ToHttpApiData (JSONQueryParam a) where
177+
toQueryParam = T.decodeUtf8 . BSL.toStrict . Aeson.encode . fromJsonQueryParam
178+
179+
instance Aeson.FromJSON a => FromHttpApiData (JSONQueryParam a) where
180+
parseQueryParam = either (Left . T.pack) (Right . JSONQueryParam) . Aeson.eitherDecodeStrict . T.encodeUtf8
181+
169182

170183
{{#apiInfo}}
171184
-- | Servant type-level API, generated from the OpenAPI spec for {{title}}.

modules/openapi-generator/src/main/resources/haskell-servant/Types.mustache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
22
{-# LANGUAGE DeriveDataTypeable #-}
33
{-# LANGUAGE DeriveGeneric #-}
4+
{-# LANGUAGE DuplicateRecordFields #-}
45
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-unused-imports #-}
56

67
module {{title}}.Types (

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
resolver: lts-19.2
1+
resolver: lts-22.12
22
extra-deps: []
33
packages:
44
- '.'

0 commit comments

Comments
 (0)