diff --git a/bin/configs/haskell-http-client.yaml b/bin/configs/haskell-http-client.yaml index 1f2dc62fe60..6222a4a6dc0 100644 --- a/bin/configs/haskell-http-client.yaml +++ b/bin/configs/haskell-http-client.yaml @@ -1,6 +1,6 @@ generatorName: haskell-http-client outputDir: samples/client/petstore/haskell-http-client -inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml +inputSpec: modules/openapi-generator/src/test/resources/3_0/haskell-http-client/petstore-with-fake-endpoints-models-for-testing.yaml templateDir: modules/openapi-generator/src/main/resources/haskell-http-client additionalProperties: queryExtraUnreserved: '' diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java index efcb7ab97b8..0e79940195c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java @@ -745,10 +745,15 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC param.vendorExtensions.put(VENDOR_EXTENSION_X_IS_BODY_OR_FORM_PARAM, param.isBodyParam || param.isFormParam); if (!StringUtils.isBlank(param.collectionFormat)) { param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat)); - } else if (!param.isBodyParam && (param.isArray || param.dataType.startsWith("["))) { // param.isArray is sometimes false for list types - // defaulting due to https://github.com/wing328/openapi-generator/issues/72 - param.collectionFormat = "csv"; - param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat)); + } else if (!param.isBodyParam) { + if (param.isArray || param.dataType.startsWith("[")) { // param.isArray is sometimes false for list types + // defaulting due to https://github.com/wing328/openapi-generator/issues/72 + param.collectionFormat = "csv"; + param.vendorExtensions.put(VENDOR_EXTENSION_X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat)); + } + } + if (param.isQueryParam && (isJsonMimeType(param.contentType) || ContainsJsonMimeType(param.contentType))) { + param.vendorExtensions.put(X_MEDIA_IS_JSON, "true"); } if (!param.required) { op.vendorExtensions.put(VENDOR_EXTENSION_X_HAS_OPTIONAL_PARAMS, true); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java index 4b26ba11d11..8a4a191afa6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java @@ -514,6 +514,13 @@ public class HaskellServantCodegen extends DefaultCodegen implements CodegenConf // Query parameters appended to routes for (CodegenParameter param : op.queryParams) { String paramType = param.dataType; + if (param.contentType == "application/json") { + if (param.isArray) { + paramType = "[JSONQueryParam " + paramType.substring(1, paramType.length() - 1) + "]"; + } else { + paramType = "(JSONQueryParam " + paramType + ")"; + } + } if (param.isArray) { if (StringUtils.isEmpty(param.collectionFormat)) { param.collectionFormat = "csv"; @@ -549,6 +556,13 @@ public class HaskellServantCodegen extends DefaultCodegen implements CodegenConf path.add("Header \"" + param.baseName + "\" " + param.dataType); String paramType = param.dataType; + if (param.contentType == "application/json") { + if (param.isArray) { + paramType = "(JSONQueryParam " + paramType.substring(1, paramType.length() - 1) + ")"; + } else { + paramType = "(JSONQueryParam " + paramType + ")"; + } + } if (param.isArray) { if (StringUtils.isEmpty(param.collectionFormat)) { param.collectionFormat = "csv"; diff --git a/modules/openapi-generator/src/main/resources/haskell-http-client/Core.mustache b/modules/openapi-generator/src/main/resources/haskell-http-client/Core.mustache index f88ff04016e..e910264bdd0 100644 --- a/modules/openapi-generator/src/main/resources/haskell-http-client/Core.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-http-client/Core.mustache @@ -41,6 +41,7 @@ import qualified Data.Maybe as P import qualified Data.Proxy as P (Proxy(..)) import qualified Data.Text as T import qualified Data.Text.Encoding as T +import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Time as TI import qualified Data.Time.ISO8601 as TI import qualified GHC.Base as P (Alternative) @@ -330,6 +331,9 @@ toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem] toQuery x = [(fmap . fmap) toQueryParam x] where toQueryParam = T.encodeUtf8 . WH.toQueryParam +toJsonQuery :: A.ToJSON a => (BC.ByteString, Maybe a) -> [NH.QueryItem] +toJsonQuery = toQuery . (fmap . fmap) (TL.decodeUtf8 . A.encode) + toPartialEscapeQuery :: B.ByteString -> NH.Query -> NH.PartialEscapeQuery toPartialEscapeQuery extraUnreserved query = fmap (\(k, v) -> (k, maybe [] go v)) query where go :: B.ByteString -> [NH.EscapeItem] @@ -362,6 +366,9 @@ toFormColl c xs = WH.toForm $ fmap unpack $ _toColl c toHeader $ pack xs toQueryColl :: WH.ToHttpApiData a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query toQueryColl c xs = _toCollA c toQuery xs +toJsonQueryColl :: A.ToJSON a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query +toJsonQueryColl c xs = _toCollA c toJsonQuery xs + _toColl :: P.Traversable f => CollectionFormat -> (f a -> [(b, BC.ByteString)]) -> f [a] -> [(b, BC.ByteString)] _toColl c encode xs = fmap (fmap P.fromJust) (_toCollA' c fencode BC.singleton (fmap Just xs)) where fencode = fmap (fmap Just) . encode . fmap P.fromJust diff --git a/modules/openapi-generator/src/main/resources/haskell-http-client/Model.mustache b/modules/openapi-generator/src/main/resources/haskell-http-client/Model.mustache index 595d8cd9fdc..7538e4e6439 100644 --- a/modules/openapi-generator/src/main/resources/haskell-http-client/Model.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-http-client/Model.mustache @@ -7,6 +7,7 @@ Module : {{baseModule}}.Model {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} diff --git a/modules/openapi-generator/src/main/resources/haskell-http-client/_queryColl.mustache b/modules/openapi-generator/src/main/resources/haskell-http-client/_queryColl.mustache index 83c6fb16908..e64d285c4c3 100644 --- a/modules/openapi-generator/src/main/resources/haskell-http-client/_queryColl.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-http-client/_queryColl.mustache @@ -1 +1 @@ -toQuery{{#collectionFormat}}Coll {{vendorExtensions.x-collection-format}}{{/collectionFormat}} \ No newline at end of file +to{{#vendorExtensions.x-mediaIsJson}}Json{{/vendorExtensions.x-mediaIsJson}}Query{{#collectionFormat}}Coll {{vendorExtensions.x-collection-format}}{{/collectionFormat}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/haskell-servant/API.mustache b/modules/openapi-generator/src/main/resources/haskell-servant/API.mustache index 67ae8f4d8a4..fd7120279bf 100644 --- a/modules/openapi-generator/src/main/resources/haskell-servant/API.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-servant/API.mustache @@ -47,6 +47,7 @@ import Control.Monad.Except (ExceptT, runExceptT) import Control.Monad.IO.Class import Control.Monad.Trans.Reader (ReaderT (..)) import Data.Aeson (Value) +import qualified Data.Aeson as Aeson {{#authMethods}} {{#isApiKey}} import Data.ByteString (ByteString) @@ -55,6 +56,7 @@ import Data.ByteString (ByteString) import Data.ByteString (ByteString) {{/isBasicBearer}} {{/authMethods}} +import qualified Data.ByteString.Lazy as BSL import Data.Coerce (coerce) import Data.Data (Data) import Data.Function ((&)) @@ -64,6 +66,7 @@ import Data.Proxy (Proxy (..)) import Data.Set (Set) import Data.Text (Text) import qualified Data.Text as T +import qualified Data.Text.Encoding as T import Data.Time import Data.UUID (UUID) import GHC.Exts (IsString (..)) @@ -166,6 +169,16 @@ instance ToHttpApiData a => ToHttpApiData (QueryList 'MultiParamArray a) where formatSeparatedQueryList :: ToHttpApiData a => Char -> QueryList p a -> Text formatSeparatedQueryList char = T.intercalate (T.singleton char) . map toQueryParam . fromQueryList +newtype JSONQueryParam a = JSONQueryParam + { fromJsonQueryParam :: a + } deriving (Functor, Foldable, Traversable) + +instance Aeson.ToJSON a => ToHttpApiData (JSONQueryParam a) where + toQueryParam = T.decodeUtf8 . BSL.toStrict . Aeson.encode . fromJsonQueryParam + +instance Aeson.FromJSON a => FromHttpApiData (JSONQueryParam a) where + parseQueryParam = either (Left . T.pack) (Right . JSONQueryParam) . Aeson.eitherDecodeStrict . T.encodeUtf8 + {{#apiInfo}} -- | Servant type-level API, generated from the OpenAPI spec for {{title}}. diff --git a/modules/openapi-generator/src/main/resources/haskell-servant/Types.mustache b/modules/openapi-generator/src/main/resources/haskell-servant/Types.mustache index a8f444d3585..99934dcbcf9 100644 --- a/modules/openapi-generator/src/main/resources/haskell-servant/Types.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-servant/Types.mustache @@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-unused-imports #-} module {{title}}.Types ( diff --git a/modules/openapi-generator/src/main/resources/haskell-servant/stack.mustache b/modules/openapi-generator/src/main/resources/haskell-servant/stack.mustache index 1f1821220e2..7b4767b0ebf 100644 --- a/modules/openapi-generator/src/main/resources/haskell-servant/stack.mustache +++ b/modules/openapi-generator/src/main/resources/haskell-servant/stack.mustache @@ -1,4 +1,4 @@ -resolver: lts-19.2 +resolver: lts-22.12 extra-deps: [] packages: - '.' diff --git a/modules/openapi-generator/src/test/resources/3_0/haskell-http-client/petstore-with-fake-endpoints-models-for-testing.yaml b/modules/openapi-generator/src/test/resources/3_0/haskell-http-client/petstore-with-fake-endpoints-models-for-testing.yaml new file mode 100644 index 00000000000..2e32dd9a38b --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/haskell-http-client/petstore-with-fake-endpoints-models-for-testing.yaml @@ -0,0 +1,2200 @@ +openapi: 3.0.1 +info: + description: "This spec is mainly for testing Petstore server and contains fake\ + \ endpoints, models. Please do not use this for any other purpose. Special characters:\ + \ \" \\" + license: + name: Apache-2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + title: OpenAPI Petstore + version: 1.0.0 +servers: +- url: http://petstore.swagger.io:80/v2 +tags: +- description: Everything about your Pets + name: pet +- description: Access to Petstore orders + name: store +- description: Operations about user + name: user +paths: + /pet: + post: + operationId: addPet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + responses: + "200": + content: {} + description: successful operation + "405": + content: {} + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + summary: Add a new pet to the store + tags: + - pet + x-codegen-request-body-name: body + put: + operationId: updatePet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + responses: + "200": + content: {} + description: successful operation + "400": + content: {} + description: Invalid ID supplied + "404": + content: {} + description: Pet not found + "405": + content: {} + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + summary: Update an existing pet + tags: + - pet + x-codegen-request-body-name: body + /pet/findByStatus: + get: + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + responses: + "200": + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + description: successful operation + "400": + content: {} + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Finds Pets by status + tags: + - pet + /pet/findByTags: + get: + deprecated: true + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + uniqueItems: true + style: form + responses: + "200": + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + uniqueItems: true + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + uniqueItems: true + description: successful operation + "400": + content: {} + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Finds Pets by tags + tags: + - pet + /pet/find: + get: + operationId: findPets + parameters: + - content: + application/json: + schema: + $ref: '#/components/schemas/PetFilter' + in: query + name: filter + required: false + - content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOrder' + in: query + name: order_by + required: false + responses: + "200": + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + description: successful operation + "400": + description: Invalid status value + security: + - petstore_auth: + - read:pets + summary: Finds Pets + tags: + - pet + /pet/{petId}: + delete: + operationId: deletePet + parameters: + - in: header + name: api_key + schema: + type: string + - description: Pet id to delete + in: path + name: petId + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: {} + description: successful operation + "400": + content: {} + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Deletes a pet + tags: + - pet + get: + description: Returns a single pet + operationId: getPetById + parameters: + - description: ID of pet to return + in: path + name: petId + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: successful operation + "400": + content: {} + description: Invalid ID supplied + "404": + content: {} + description: Pet not found + security: + - api_key: [] + summary: Find pet by ID + tags: + - pet + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + in: path + name: petId + required: true + schema: + format: int64 + type: integer + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/updatePetWithForm_request' + responses: + "405": + content: {} + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + summary: Updates a pet in the store with form data + tags: + - pet + /pet/{petId}/uploadImage: + post: + operationId: uploadFile + parameters: + - description: ID of pet to update + in: path + name: petId + required: true + schema: + format: int64 + type: integer + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/uploadFile_request' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + description: successful operation + security: + - petstore_auth: + - write:pets + - read:pets + summary: uploads an image + tags: + - pet + /store/inventory: + get: + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + "200": + content: + application/json: + schema: + additionalProperties: + format: int32 + type: integer + type: object + description: successful operation + security: + - api_key: [] + summary: Returns pet inventories by status + tags: + - store + /store/order: + post: + operationId: placeOrder + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + responses: + "200": + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + description: successful operation + "400": + content: {} + description: Invalid Order + summary: Place an order for a pet + tags: + - store + x-codegen-request-body-name: body + /store/order/{order_id}: + delete: + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - description: ID of the order that needs to be deleted + in: path + name: order_id + required: true + schema: + type: string + responses: + "400": + content: {} + description: Invalid ID supplied + "404": + content: {} + description: Order not found + summary: Delete purchase order by ID + tags: + - store + get: + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions + operationId: getOrderById + parameters: + - description: ID of pet that needs to be fetched + in: path + name: order_id + required: true + schema: + format: int64 + maximum: 5 + minimum: 1 + type: integer + responses: + "200": + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + description: successful operation + "400": + content: {} + description: Invalid ID supplied + "404": + content: {} + description: Order not found + summary: Find purchase order by ID + tags: + - store + /user: + post: + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + responses: + default: + content: {} + description: successful operation + summary: Create user + tags: + - user + x-codegen-request-body-name: body + /user/createWithArray: + post: + operationId: createUsersWithArrayInput + requestBody: + content: + '*/*': + schema: + items: + $ref: '#/components/schemas/User' + type: array + description: List of user object + required: true + responses: + default: + content: {} + description: successful operation + summary: Creates list of users with given input array + tags: + - user + x-codegen-request-body-name: body + /user/createWithList: + post: + operationId: createUsersWithListInput + requestBody: + content: + '*/*': + schema: + items: + $ref: '#/components/schemas/User' + type: array + description: List of user object + required: true + responses: + default: + content: {} + description: successful operation + summary: Creates list of users with given input array + tags: + - user + x-codegen-request-body-name: body + /user/login: + get: + operationId: loginUser + parameters: + - description: The user name for login + in: query + name: username + required: true + schema: + type: string + - description: The password for login in clear text + in: query + name: password + required: true + schema: + type: string + responses: + "200": + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + format: int32 + type: integer + X-Expires-After: + description: date in UTC when token expires + schema: + format: date-time + type: string + "400": + content: {} + description: Invalid username/password supplied + summary: Logs user into the system + tags: + - user + /user/logout: + get: + operationId: logoutUser + responses: + default: + content: {} + description: successful operation + summary: Logs out current logged in user session + tags: + - user + /user/{username}: + delete: + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - description: The name that needs to be deleted + in: path + name: username + required: true + schema: + type: string + responses: + "400": + content: {} + description: Invalid username supplied + "404": + content: {} + description: User not found + summary: Delete user + tags: + - user + get: + operationId: getUserByName + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + in: path + name: username + required: true + schema: + type: string + responses: + "200": + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + description: successful operation + "400": + content: {} + description: Invalid username supplied + "404": + content: {} + description: User not found + summary: Get user by user name + tags: + - user + put: + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - description: name that need to be deleted + in: path + name: username + required: true + schema: + type: string + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + responses: + "400": + content: {} + description: Invalid user supplied + "404": + content: {} + description: User not found + summary: Updated user + tags: + - user + x-codegen-request-body-name: body + /fake_classname_test: + patch: + description: To test class name in snake case + operationId: testClassname + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: client model + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: successful operation + security: + - api_key_query: [] + summary: To test class name in snake case + tags: + - fake_classname_tags 123#$%^ + x-codegen-request-body-name: body + /fake: + delete: + description: Fake endpoint to test group parameters (optional) + operationId: testGroupParameters + parameters: + - description: Required String in group parameters + in: query + name: required_string_group + required: true + schema: + type: integer + - description: Required Boolean in group parameters + in: header + name: required_boolean_group + required: true + schema: + type: boolean + - description: Required Integer in group parameters + in: query + name: required_int64_group + required: true + schema: + format: int64 + type: integer + - description: String in group parameters + in: query + name: string_group + schema: + type: integer + - description: Boolean in group parameters + in: header + name: boolean_group + schema: + type: boolean + - description: Integer in group parameters + in: query + name: int64_group + schema: + format: int64 + type: integer + responses: + "400": + content: {} + description: Something wrong + summary: Fake endpoint to test group parameters (optional) + tags: + - fake + x-group-parameters: true + get: + description: To test enum parameters + operationId: testEnumParameters + parameters: + - description: Header parameter enum test (string array) + explode: false + in: header + name: enum_header_string_array + schema: + items: + default: $ + enum: + - '>' + - $ + type: string + type: array + style: simple + - description: Header parameter enum test (string) + in: header + name: enum_header_string + schema: + default: -efg + enum: + - _abc + - -efg + - (xyz) + type: string + - description: Query parameter enum test (string array) + explode: false + in: query + name: enum_query_string_array + schema: + items: + default: $ + enum: + - '>' + - $ + type: string + type: array + style: form + - description: Query parameter enum test (string) + in: query + name: enum_query_string + schema: + default: -efg + enum: + - _abc + - -efg + - (xyz) + type: string + - description: Query parameter enum test (double) + in: query + name: enum_query_integer + schema: + enum: + - 1 + - -2 + format: int32 + type: integer + - description: Query parameter enum test (double) + in: query + name: enum_query_double + schema: + enum: + - 1.1 + - -1.2 + format: double + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/testEnumParameters_request' + responses: + "400": + content: {} + description: Invalid request + "404": + content: {} + description: Not found + summary: To test enum parameters + tags: + - fake + patch: + description: To test "client" model + operationId: testClientModel + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: client model + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: successful operation + summary: To test "client" model + tags: + - fake + x-codegen-request-body-name: body + post: + description: |- + Fake endpoint for testing various parameters + 假端點 + 偽のエンドポイント + 가짜 엔드 포인트 + operationId: testEndpointParameters + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/testEndpointParameters_request' + required: true + responses: + "400": + content: {} + description: Invalid username supplied + "404": + content: {} + description: User not found + security: + - http_basic_test: [] + summary: |- + Fake endpoint for testing various parameters + 假端點 + 偽のエンドポイント + 가짜 엔드 포인트 + tags: + - fake + /fake/outer/number: + post: + description: Test serialization of outer number types + operationId: fakeOuterNumberSerialize + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterNumber' + description: Input number as post body + required: false + responses: + "200": + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterNumber' + description: Output number + tags: + - fake + x-codegen-request-body-name: body + /fake/outer/string: + post: + description: Test serialization of outer string types + operationId: fakeOuterStringSerialize + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterString' + description: Input string as post body + required: false + responses: + "200": + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterString' + description: Output string + tags: + - fake + x-codegen-request-body-name: body + /fake/outer/boolean: + post: + description: Test serialization of outer boolean types + operationId: fakeOuterBooleanSerialize + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterBoolean' + description: Input boolean as post body + required: false + responses: + "200": + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterBoolean' + description: Output boolean + tags: + - fake + x-codegen-request-body-name: body + /fake/outer/composite: + post: + description: Test serialization of object with outer number type + operationId: fakeOuterCompositeSerialize + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterComposite' + description: Input composite as post body + required: false + responses: + "200": + content: + '*/*': + schema: + $ref: '#/components/schemas/OuterComposite' + description: Output composite + tags: + - fake + x-codegen-request-body-name: body + /fake/jsonFormData: + get: + operationId: testJsonFormData + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/testJsonFormData_request' + required: true + responses: + "200": + content: {} + description: successful operation + summary: test json serialization of form data + tags: + - fake + /fake/inline-additionalProperties: + post: + operationId: testInlineAdditionalProperties + requestBody: + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: request body + required: true + responses: + "200": + content: {} + description: successful operation + summary: test inline additionalProperties + tags: + - fake + x-codegen-request-body-name: param + /fake/body-with-query-params: + put: + operationId: testBodyWithQueryParams + parameters: + - in: query + name: query + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + "200": + content: {} + description: Success + tags: + - fake + x-codegen-request-body-name: body + /fake/create_xml_item: + post: + description: this route creates an XmlItem + operationId: createXmlItem + requestBody: + content: + application/xml: + schema: + $ref: '#/components/schemas/XmlItem' + application/xml; charset=utf-8: + schema: + $ref: '#/components/schemas/XmlItem' + application/xml; charset=utf-16: + schema: + $ref: '#/components/schemas/XmlItem' + text/xml: + schema: + $ref: '#/components/schemas/XmlItem' + text/xml; charset=utf-8: + schema: + $ref: '#/components/schemas/XmlItem' + text/xml; charset=utf-16: + schema: + $ref: '#/components/schemas/XmlItem' + description: XmlItem Body + required: true + responses: + "200": + content: {} + description: successful operation + summary: creates an XmlItem + tags: + - fake + x-codegen-request-body-name: XmlItem + /another-fake/dummy: + patch: + description: To test special tags and operation ID starting with number + operationId: 123_test_@#$%_special_tags + parameters: + - description: to test uuid example value + in: header + name: uuid_test + required: true + schema: + format: uuid + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: client model + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: successful operation + summary: To test special tags + tags: + - $another-fake? + x-codegen-request-body-name: body + /fake/body-with-file-schema: + put: + description: "For this test, the body for this request much reference a schema\ + \ named `File`." + operationId: testBodyWithFileSchema + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FileSchemaTestClass' + required: true + responses: + "200": + content: {} + description: Success + tags: + - fake + x-codegen-request-body-name: body + /fake/test-query-parameters: + put: + description: To test the collection format in query parameters + operationId: testQueryParameterCollectionFormat + parameters: + - explode: false + in: query + name: pipe + required: true + schema: + items: + type: string + type: array + style: pipeDelimited + - in: query + name: ioutil + required: true + schema: + items: + type: string + type: array + - in: query + name: http + required: true + schema: + items: + type: string + type: array + style: spaceDelimited + - explode: false + in: query + name: url + required: true + schema: + items: + type: string + type: array + style: form + - explode: true + in: query + name: context + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: {} + description: Success + tags: + - fake + /fake/{petId}/uploadImageWithRequiredFile: + post: + operationId: uploadFileWithRequiredFile + parameters: + - description: ID of pet to update + in: path + name: petId + required: true + schema: + format: int64 + type: integer + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/uploadFileWithRequiredFile_request' + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + description: successful operation + security: + - petstore_auth: + - write:pets + - read:pets + summary: uploads an image (required) + tags: + - pet +components: + schemas: + Order: + example: + petId: 6 + quantity: 1 + id: 0 + shipDate: 2000-01-23T04:56:07.000+00:00 + complete: false + status: placed + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + type: object + xml: + name: Order + Category: + example: + name: default-name + id: 6 + properties: + id: + format: int64 + type: integer + name: + default: default-name + type: string + required: + - name + type: object + xml: + name: Category + User: + example: + firstName: firstName + lastName: lastName + password: password + userStatus: 6 + phone: phone + id: 0 + email: email + username: username + properties: + id: + format: int64 + type: integer + x-is-unique: true + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + type: object + xml: + name: User + Tag: + example: + name: name + id: 1 + properties: + id: + format: int64 + type: integer + name: + type: string + type: object + xml: + name: Tag + Pet: + example: + photoUrls: + - photoUrls + - photoUrls + name: doggie + id: 0 + category: + name: default-name + id: 6 + tags: + - name: name + id: 1 + - name: name + id: 1 + status: available + properties: + id: + format: int64 + type: integer + x-is-unique: true + category: + $ref: '#/components/schemas/Category' + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + uniqueItems: true + xml: + name: photoUrl + wrapped: true + tags: + items: + $ref: '#/components/schemas/Tag' + type: array + xml: + name: tag + wrapped: true + status: + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + type: object + xml: + name: Pet + PetFilter: + type: object + properties: + tags: + type: array + items: + type: string + status: + type: array + items: + type: string + PetOrder: + type: object + properties: + name: + enum: + - asc + - desc + ApiResponse: + example: + code: 0 + type: type + message: message + properties: + code: + format: int32 + type: integer + type: + type: string + message: + type: string + type: object + Return: + description: Model for testing reserved words + properties: + return: + format: int32 + type: integer + type: object + xml: + name: Return + Name: + description: Model for testing model name same as property name + properties: + name: + format: int32 + type: integer + snake_case: + format: int32 + readOnly: true + type: integer + property: + type: string + "123Number": + readOnly: true + type: integer + required: + - name + type: object + xml: + name: Name + "200_response": + description: Model for testing model name starting with number + properties: + name: + format: int32 + type: integer + class: + type: string + type: object + xml: + name: Name + ClassModel: + description: Model for testing model with "_class" property + properties: + _class: + type: string + type: object + Dog: + allOf: + - $ref: '#/components/schemas/Animal' + - properties: + breed: + type: string + type: object + Cat: + allOf: + - $ref: '#/components/schemas/Animal' + - properties: + declawed: + type: boolean + type: object + BigCat: + allOf: + - $ref: '#/components/schemas/Cat' + - properties: + kind: + enum: + - lions + - tigers + - leopards + - jaguars + type: string + type: object + Animal: + discriminator: + propertyName: className + properties: + className: + type: string + color: + default: red + type: string + required: + - className + type: object + AnimalFarm: + items: + $ref: '#/components/schemas/Animal' + type: array + format_test: + properties: + integer: + maximum: 100 + minimum: 10 + type: integer + int32: + format: int32 + maximum: 200 + minimum: 20 + type: integer + int64: + format: int64 + type: integer + number: + maximum: 543.2 + minimum: 32.1 + type: number + float: + format: float + maximum: 987.6 + minimum: 54.3 + type: number + double: + format: double + maximum: 123.4 + minimum: 67.8 + type: number + string: + pattern: "/[a-z]/i" + type: string + byte: + format: byte + pattern: "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" + type: string + binary: + format: binary + type: string + date: + format: date + type: string + dateTime: + format: date-time + type: string + uuid: + example: 72f98069-206d-4f12-9f12-3d1e525a8e84 + format: uuid + type: string + password: + format: password + maxLength: 64 + minLength: 10 + type: string + BigDecimal: + format: number + type: string + required: + - byte + - date + - number + - password + type: object + EnumClass: + default: -efg + enum: + - _abc + - -efg + - (xyz) + type: string + Enum_Test: + properties: + enum_string: + enum: + - UPPER + - lower + - "" + type: string + enum_string_required: + enum: + - UPPER + - lower + - "" + type: string + enum_integer: + enum: + - 1 + - -1 + format: int32 + type: integer + enum_number: + enum: + - 1.1 + - -1.2 + format: double + type: number + outerEnum: + $ref: '#/components/schemas/OuterEnum' + required: + - enum_string_required + type: object + AdditionalPropertiesClass: + properties: + map_string: + additionalProperties: + type: string + type: object + map_number: + additionalProperties: + type: number + type: object + map_integer: + additionalProperties: + type: integer + type: object + map_boolean: + additionalProperties: + type: boolean + type: object + map_array_integer: + additionalProperties: + items: + type: integer + type: array + type: object + map_array_anytype: + additionalProperties: + items: + properties: {} + type: object + type: array + type: object + map_map_string: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + map_map_anytype: + additionalProperties: + additionalProperties: + properties: {} + type: object + type: object + type: object + anytype_1: + properties: {} + type: object + anytype_2: + type: object + anytype_3: + properties: {} + type: object + type: object + AdditionalPropertiesString: + additionalProperties: + type: string + properties: + name: + type: string + type: object + AdditionalPropertiesInteger: + additionalProperties: + type: integer + properties: + name: + type: string + type: object + AdditionalPropertiesNumber: + additionalProperties: + type: number + properties: + name: + type: string + type: object + AdditionalPropertiesBoolean: + additionalProperties: + type: boolean + properties: + name: + type: string + type: object + AdditionalPropertiesArray: + additionalProperties: + items: + properties: {} + type: object + type: array + properties: + name: + type: string + type: object + AdditionalPropertiesObject: + additionalProperties: + additionalProperties: + properties: {} + type: object + type: object + properties: + name: + type: string + type: object + AdditionalPropertiesAnyType: + additionalProperties: + properties: {} + type: object + properties: + name: + type: string + type: object + MixedPropertiesAndAdditionalPropertiesClass: + properties: + uuid: + format: uuid + type: string + dateTime: + format: date-time + type: string + map: + additionalProperties: + $ref: '#/components/schemas/Animal' + type: object + type: object + List: + properties: + "123-list": + type: string + type: object + Client: + example: + client: client + properties: + client: + type: string + type: object + ReadOnlyFirst: + properties: + bar: + readOnly: true + type: string + baz: + type: string + type: object + hasOnlyReadOnly: + properties: + bar: + readOnly: true + type: string + foo: + readOnly: true + type: string + type: object + Capitalization: + properties: + smallCamel: + type: string + CapitalCamel: + type: string + small_Snake: + type: string + Capital_Snake: + type: string + SCA_ETH_Flow_Points: + type: string + ATT_NAME: + description: | + Name of the pet + type: string + type: object + MapTest: + properties: + map_map_of_string: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + map_of_enum_string: + additionalProperties: + enum: + - UPPER + - lower + type: string + type: object + direct_map: + additionalProperties: + type: boolean + type: object + indirect_map: + additionalProperties: + type: boolean + type: object + type: object + ArrayTest: + properties: + array_of_string: + items: + type: string + type: array + array_array_of_integer: + items: + items: + format: int64 + type: integer + type: array + type: array + array_array_of_model: + items: + items: + $ref: '#/components/schemas/ReadOnlyFirst' + type: array + type: array + type: object + NumberOnly: + properties: + JustNumber: + type: number + type: object + ArrayOfNumberOnly: + properties: + ArrayNumber: + items: + type: number + type: array + type: object + ArrayOfArrayOfNumberOnly: + properties: + ArrayArrayNumber: + items: + items: + type: number + type: array + type: array + type: object + EnumArrays: + properties: + just_symbol: + enum: + - '>=' + - $ + type: string + array_enum: + items: + enum: + - fish + - crab + type: string + type: array + type: object + OuterEnum: + enum: + - placed + - approved + - delivered + type: string + OuterComposite: + example: + my_string: my_string + my_number: 0.8008281904610115 + my_boolean: true + properties: + my_number: + type: number + my_string: + type: string + my_boolean: + type: boolean + x-codegen-body-parameter-name: boolean_post_body + type: object + OuterNumber: + type: number + OuterString: + type: string + OuterBoolean: + type: boolean + x-codegen-body-parameter-name: boolean_post_body + StringBooleanMap: + additionalProperties: + type: boolean + type: object + FileSchemaTestClass: + example: + file: + sourceURI: sourceURI + files: + - sourceURI: sourceURI + - sourceURI: sourceURI + properties: + file: + $ref: '#/components/schemas/File' + files: + items: + $ref: '#/components/schemas/File' + type: array + type: object + File: + description: Must be named `File` for test. + example: + sourceURI: sourceURI + properties: + sourceURI: + description: Test capitalization + type: string + type: object + TypeHolderDefault: + properties: + string_item: + default: what + type: string + number_item: + type: number + integer_item: + type: integer + bool_item: + default: true + type: boolean + array_item: + items: + type: integer + type: array + required: + - array_item + - bool_item + - integer_item + - number_item + - string_item + type: object + TypeHolderExample: + properties: + string_item: + example: what + type: string + number_item: + example: 1.234 + type: number + float_item: + example: 1.234 + format: float + type: number + integer_item: + example: -2 + type: integer + bool_item: + example: true + type: boolean + array_item: + example: + - 0 + - 1 + - 2 + - 3 + items: + type: integer + type: array + required: + - array_item + - bool_item + - float_item + - integer_item + - number_item + - string_item + type: object + XmlItem: + properties: + attribute_string: + example: string + type: string + xml: + attribute: true + attribute_number: + example: 1.234 + type: number + xml: + attribute: true + attribute_integer: + example: -2 + type: integer + xml: + attribute: true + attribute_boolean: + example: true + type: boolean + xml: + attribute: true + wrapped_array: + items: + type: integer + type: array + xml: + wrapped: true + name_string: + example: string + type: string + xml: + name: xml_name_string + name_number: + example: 1.234 + type: number + xml: + name: xml_name_number + name_integer: + example: -2 + type: integer + xml: + name: xml_name_integer + name_boolean: + example: true + type: boolean + xml: + name: xml_name_boolean + name_array: + items: + type: integer + xml: + name: xml_name_array_item + type: array + name_wrapped_array: + items: + type: integer + xml: + name: xml_name_wrapped_array_item + type: array + xml: + name: xml_name_wrapped_array + wrapped: true + prefix_string: + example: string + type: string + xml: + prefix: ab + prefix_number: + example: 1.234 + type: number + xml: + prefix: cd + prefix_integer: + example: -2 + type: integer + xml: + prefix: ef + prefix_boolean: + example: true + type: boolean + xml: + prefix: gh + prefix_array: + items: + type: integer + xml: + prefix: ij + type: array + prefix_wrapped_array: + items: + type: integer + xml: + prefix: mn + type: array + xml: + prefix: kl + wrapped: true + namespace_string: + example: string + type: string + xml: + namespace: http://a.com/schema + namespace_number: + example: 1.234 + type: number + xml: + namespace: http://b.com/schema + namespace_integer: + example: -2 + type: integer + xml: + namespace: http://c.com/schema + namespace_boolean: + example: true + type: boolean + xml: + namespace: http://d.com/schema + namespace_array: + items: + type: integer + xml: + namespace: http://e.com/schema + type: array + namespace_wrapped_array: + items: + type: integer + xml: + namespace: http://g.com/schema + type: array + xml: + namespace: http://f.com/schema + wrapped: true + prefix_ns_string: + example: string + type: string + xml: + namespace: http://a.com/schema + prefix: a + prefix_ns_number: + example: 1.234 + type: number + xml: + namespace: http://b.com/schema + prefix: b + prefix_ns_integer: + example: -2 + type: integer + xml: + namespace: http://c.com/schema + prefix: c + prefix_ns_boolean: + example: true + type: boolean + xml: + namespace: http://d.com/schema + prefix: d + prefix_ns_array: + items: + type: integer + xml: + namespace: http://e.com/schema + prefix: e + type: array + prefix_ns_wrapped_array: + items: + type: integer + xml: + namespace: http://g.com/schema + prefix: g + type: array + xml: + namespace: http://f.com/schema + prefix: f + wrapped: true + type: object + xml: + namespace: http://a.com/schema + prefix: pre + updatePetWithForm_request: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + uploadFile_request: + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + format: binary + type: string + type: object + testEnumParameters_request: + properties: + enum_form_string_array: + description: Form parameter enum test (string array) + items: + default: $ + enum: + - '>' + - $ + type: string + type: array + enum_form_string: + default: -efg + description: Form parameter enum test (string) + enum: + - _abc + - -efg + - (xyz) + type: string + type: object + testEndpointParameters_request: + properties: + integer: + description: None + format: int32 + maximum: 100 + minimum: 10 + type: integer + int32: + description: None + format: int32 + maximum: 200 + minimum: 20 + type: integer + int64: + description: None + format: int64 + type: integer + number: + description: None + maximum: 543.2 + minimum: 32.1 + type: number + float: + description: None + format: float + maximum: 987.6 + type: number + double: + description: None + format: double + maximum: 123.4 + minimum: 67.8 + type: number + string: + description: None + pattern: "/[a-z]/i" + type: string + pattern_without_delimiter: + description: None + pattern: "^[A-Z].*" + type: string + byte: + description: None + format: byte + type: string + binary: + description: None + format: binary + type: string + date: + description: None + format: date + type: string + dateTime: + description: None + format: date-time + type: string + password: + description: None + format: password + maxLength: 64 + minLength: 10 + type: string + callback: + description: None + type: string + required: + - byte + - double + - number + - pattern_without_delimiter + type: object + testJsonFormData_request: + properties: + param: + description: field1 + type: string + param2: + description: field2 + type: string + required: + - param + - param2 + type: object + uploadFileWithRequiredFile_request: + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + requiredFile: + description: file to upload + format: binary + type: string + required: + - requiredFile + type: object + securitySchemes: + petstore_auth: + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + type: oauth2 + api_key: + in: header + name: api_key + type: apiKey + api_key_query: + in: query + name: api_key_query + type: apiKey + http_basic_test: + scheme: basic + type: http diff --git a/modules/openapi-generator/src/test/resources/3_0/haskell/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/haskell/petstore.yaml index 0c3985e5782..00d4c319273 100644 --- a/modules/openapi-generator/src/test/resources/3_0/haskell/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/haskell/petstore.yaml @@ -157,6 +157,40 @@ paths: - petstore_auth: - 'read:pets' deprecated: true + /pet/find: + get: + tags: + - pet + summary: Finds Pets + operationId: findPets + parameters: + - name: filter + in: query + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/PetFilter' + + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' '/pet/{petId}': get: tags: @@ -752,3 +786,14 @@ components: - "\"" - "\\" description: description + PetFilter: + type: object + properties: + tags: + type: array + items: + type: string + status: + type: array + items: + type: string \ No newline at end of file diff --git a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Fake.hs b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Fake.hs index cc44baac924..e12f7dc61e6 100644 --- a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Fake.hs +++ b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Fake.hs @@ -520,8 +520,8 @@ testQueryParameterCollectionFormat -> OpenAPIPetstoreRequest TestQueryParameterCollectionFormat MimeNoContent NoContent MimeNoContent testQueryParameterCollectionFormat (Pipe pipe) (Ioutil ioutil) (Http http) (Url url) (Context context) = _mkRequest "PUT" ["/fake/test-query-parameters"] - `addQuery` toQueryColl CommaSeparated ("pipe", Just pipe) - `addQuery` toQueryColl CommaSeparated ("ioutil", Just ioutil) + `addQuery` toQueryColl PipeSeparated ("pipe", Just pipe) + `addQuery` toQueryColl MultiParamArray ("ioutil", Just ioutil) `addQuery` toQueryColl SpaceSeparated ("http", Just http) `addQuery` toQueryColl CommaSeparated ("url", Just url) `addQuery` toQueryColl MultiParamArray ("context", Just context) diff --git a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Pet.hs b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Pet.hs index 4e0d64c91d7..c28b9c194f5 100644 --- a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Pet.hs +++ b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/API/Pet.hs @@ -110,6 +110,34 @@ instance HasOptionalParam DeletePet ApiKey where instance Produces DeletePet MimeNoContent +-- *** findPets + +-- | @GET \/pet\/find@ +-- +-- Finds Pets +-- +-- AuthMethod: 'AuthOAuthPetstoreAuth' +-- +findPets + :: Accept accept -- ^ request accept ('MimeType') + -> OpenAPIPetstoreRequest FindPets MimeNoContent [Pet] accept +findPets _ = + _mkRequest "GET" ["/pet/find"] + `_hasAuthType` (P.Proxy :: P.Proxy AuthOAuthPetstoreAuth) + +data FindPets +instance HasOptionalParam FindPets Filter where + applyOptionalParam req (Filter xs) = + req `addQuery` toJsonQuery ("filter", Just xs) +instance HasOptionalParam FindPets OrderBy where + applyOptionalParam req (OrderBy xs) = + req `addQuery` toJsonQueryColl CommaSeparated ("order_by", Just xs) +-- | @application/xml@ +instance Produces FindPets MimeXML +-- | @application/json@ +instance Produces FindPets MimeJSON + + -- *** findPetsByStatus -- | @GET \/pet\/findByStatus@ diff --git a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Core.hs b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Core.hs index 50e8f41427e..9062b81abfc 100644 --- a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Core.hs +++ b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Core.hs @@ -50,6 +50,7 @@ import qualified Data.Maybe as P import qualified Data.Proxy as P (Proxy(..)) import qualified Data.Text as T import qualified Data.Text.Encoding as T +import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Time as TI import qualified Data.Time.ISO8601 as TI import qualified GHC.Base as P (Alternative) @@ -339,6 +340,9 @@ toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem] toQuery x = [(fmap . fmap) toQueryParam x] where toQueryParam = T.encodeUtf8 . WH.toQueryParam +toJsonQuery :: A.ToJSON a => (BC.ByteString, Maybe a) -> [NH.QueryItem] +toJsonQuery = toQuery . (fmap . fmap) (TL.decodeUtf8 . A.encode) + toPartialEscapeQuery :: B.ByteString -> NH.Query -> NH.PartialEscapeQuery toPartialEscapeQuery extraUnreserved query = fmap (\(k, v) -> (k, maybe [] go v)) query where go :: B.ByteString -> [NH.EscapeItem] @@ -371,6 +375,9 @@ toFormColl c xs = WH.toForm $ fmap unpack $ _toColl c toHeader $ pack xs toQueryColl :: WH.ToHttpApiData a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query toQueryColl c xs = _toCollA c toQuery xs +toJsonQueryColl :: A.ToJSON a => CollectionFormat -> (BC.ByteString, Maybe [a]) -> NH.Query +toJsonQueryColl c xs = _toCollA c toJsonQuery xs + _toColl :: P.Traversable f => CollectionFormat -> (f a -> [(b, BC.ByteString)]) -> f [a] -> [(b, BC.ByteString)] _toColl c encode xs = fmap (fmap P.fromJust) (_toCollA' c fencode BC.singleton (fmap Just xs)) where fencode = fmap (fmap Just) . encode . fmap P.fromJust diff --git a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Model.hs b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Model.hs index 38774062047..1f120a7c2fa 100644 --- a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Model.hs +++ b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/Model.hs @@ -16,6 +16,7 @@ Module : OpenAPIPetstore.Model {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -123,6 +124,9 @@ newtype EnumQueryStringArray = EnumQueryStringArray { unEnumQueryStringArray :: -- ** File2 newtype File2 = File2 { unFile2 :: FilePath } deriving (P.Eq, P.Show) +-- ** Filter +newtype Filter = Filter { unFilter :: PetFilter } deriving (P.Eq, P.Show) + -- ** Http newtype Http = Http { unHttp :: [Text] } deriving (P.Eq, P.Show) @@ -144,6 +148,9 @@ newtype Name2 = Name2 { unName2 :: Text } deriving (P.Eq, P.Show) -- ** Number newtype Number = Number { unNumber :: Double } deriving (P.Eq, P.Show) +-- ** OrderBy +newtype OrderBy = OrderBy { unOrderBy :: [PetOrder] } deriving (P.Eq, P.Show) + -- ** OrderId newtype OrderId = OrderId { unOrderId :: Integer } deriving (P.Eq, P.Show) @@ -1533,6 +1540,66 @@ mkPet petName petPhotoUrls = , petStatus = Nothing } +-- ** PetFilter +-- | PetFilter +data PetFilter = PetFilter + { petFilterTags :: !(Maybe [Text]) -- ^ "tags" + , petFilterStatus :: !(Maybe [Text]) -- ^ "status" + } deriving (P.Show, P.Eq, P.Typeable) + +-- | FromJSON PetFilter +instance A.FromJSON PetFilter where + parseJSON = A.withObject "PetFilter" $ \o -> + PetFilter + <$> (o .:? "tags") + <*> (o .:? "status") + +-- | ToJSON PetFilter +instance A.ToJSON PetFilter where + toJSON PetFilter {..} = + _omitNulls + [ "tags" .= petFilterTags + , "status" .= petFilterStatus + ] + + +-- | Construct a value of type 'PetFilter' (by applying it's required fields, if any) +mkPetFilter + :: PetFilter +mkPetFilter = + PetFilter + { petFilterTags = Nothing + , petFilterStatus = Nothing + } + +-- ** PetOrder +-- | PetOrder +data PetOrder = PetOrder + { petOrderName :: !(Maybe E'Name) -- ^ "name" + } deriving (P.Show, P.Eq, P.Typeable) + +-- | FromJSON PetOrder +instance A.FromJSON PetOrder where + parseJSON = A.withObject "PetOrder" $ \o -> + PetOrder + <$> (o .:? "name") + +-- | ToJSON PetOrder +instance A.ToJSON PetOrder where + toJSON PetOrder {..} = + _omitNulls + [ "name" .= petOrderName + ] + + +-- | Construct a value of type 'PetOrder' (by applying it's required fields, if any) +mkPetOrder + :: PetOrder +mkPetOrder = + PetOrder + { petOrderName = Nothing + } + -- ** ReadOnlyFirst -- | ReadOnlyFirst data ReadOnlyFirst = ReadOnlyFirst @@ -1565,34 +1632,6 @@ mkReadOnlyFirst = , readOnlyFirstBaz = Nothing } --- ** SpecialModelName --- | SpecialModelName -data SpecialModelName = SpecialModelName - { specialModelNameSpecialPropertyName :: !(Maybe Integer) -- ^ "$special[property.name]" - } deriving (P.Show, P.Eq, P.Typeable) - --- | FromJSON SpecialModelName -instance A.FromJSON SpecialModelName where - parseJSON = A.withObject "SpecialModelName" $ \o -> - SpecialModelName - <$> (o .:? "$special[property.name]") - --- | ToJSON SpecialModelName -instance A.ToJSON SpecialModelName where - toJSON SpecialModelName {..} = - _omitNulls - [ "$special[property.name]" .= specialModelNameSpecialPropertyName - ] - - --- | Construct a value of type 'SpecialModelName' (by applying it's required fields, if any) -mkSpecialModelName - :: SpecialModelName -mkSpecialModelName = - SpecialModelName - { specialModelNameSpecialPropertyName = Nothing - } - -- ** Tag -- | Tag data Tag = Tag @@ -2221,6 +2260,34 @@ toE'Kind = \case s -> P.Left $ "toE'Kind: enum parse failure: " P.++ P.show s +-- ** E'Name + +-- | Enum of 'Text' +data E'Name + = E'Name'Asc -- ^ @"asc"@ + | E'Name'Desc -- ^ @"desc"@ + deriving (P.Show, P.Eq, P.Typeable, P.Ord, P.Bounded, P.Enum) + +instance A.ToJSON E'Name where toJSON = A.toJSON . fromE'Name +instance A.FromJSON E'Name where parseJSON o = P.either P.fail (pure . P.id) . toE'Name =<< A.parseJSON o +instance WH.ToHttpApiData E'Name where toQueryParam = WH.toQueryParam . fromE'Name +instance WH.FromHttpApiData E'Name where parseQueryParam o = WH.parseQueryParam o >>= P.left T.pack . toE'Name +instance MimeRender MimeMultipartFormData E'Name where mimeRender _ = mimeRenderDefaultMultipartFormData + +-- | unwrap 'E'Name' enum +fromE'Name :: E'Name -> Text +fromE'Name = \case + E'Name'Asc -> "asc" + E'Name'Desc -> "desc" + +-- | parse 'E'Name' enum +toE'Name :: Text -> P.Either String E'Name +toE'Name = \case + "asc" -> P.Right E'Name'Asc + "desc" -> P.Right E'Name'Desc + s -> P.Left $ "toE'Name: enum parse failure: " P.++ P.show s + + -- ** E'Status -- | Enum of 'Text' . diff --git a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/ModelLens.hs b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/ModelLens.hs index 79f2838cdc3..c74da786a69 100644 --- a/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/ModelLens.hs +++ b/samples/client/petstore/haskell-http-client/lib/OpenAPIPetstore/ModelLens.hs @@ -713,6 +713,29 @@ petStatusL f Pet{..} = (\petStatus -> Pet { petStatus, ..} ) <$> f petStatus +-- * PetFilter + +-- | 'petFilterTags' Lens +petFilterTagsL :: Lens_' PetFilter (Maybe [Text]) +petFilterTagsL f PetFilter{..} = (\petFilterTags -> PetFilter { petFilterTags, ..} ) <$> f petFilterTags +{-# INLINE petFilterTagsL #-} + +-- | 'petFilterStatus' Lens +petFilterStatusL :: Lens_' PetFilter (Maybe [Text]) +petFilterStatusL f PetFilter{..} = (\petFilterStatus -> PetFilter { petFilterStatus, ..} ) <$> f petFilterStatus +{-# INLINE petFilterStatusL #-} + + + +-- * PetOrder + +-- | 'petOrderName' Lens +petOrderNameL :: Lens_' PetOrder (Maybe E'Name) +petOrderNameL f PetOrder{..} = (\petOrderName -> PetOrder { petOrderName, ..} ) <$> f petOrderName +{-# INLINE petOrderNameL #-} + + + -- * ReadOnlyFirst -- | 'readOnlyFirstBar' Lens @@ -727,15 +750,6 @@ readOnlyFirstBazL f ReadOnlyFirst{..} = (\readOnlyFirstBaz -> ReadOnlyFirst { re --- * SpecialModelName - --- | 'specialModelNameSpecialPropertyName' Lens -specialModelNameSpecialPropertyNameL :: Lens_' SpecialModelName (Maybe Integer) -specialModelNameSpecialPropertyNameL f SpecialModelName{..} = (\specialModelNameSpecialPropertyName -> SpecialModelName { specialModelNameSpecialPropertyName, ..} ) <$> f specialModelNameSpecialPropertyName -{-# INLINE specialModelNameSpecialPropertyNameL #-} - - - -- * Tag -- | 'tagId' Lens diff --git a/samples/client/petstore/haskell-http-client/openapi.yaml b/samples/client/petstore/haskell-http-client/openapi.yaml index 8d9c8d5ff5e..3447e0a4c1a 100644 --- a/samples/client/petstore/haskell-http-client/openapi.yaml +++ b/samples/client/petstore/haskell-http-client/openapi.yaml @@ -167,21 +167,68 @@ paths: summary: Finds Pets by tags tags: - pet + /pet/find: + get: + operationId: findPets + parameters: + - content: + application/json: + schema: + $ref: '#/components/schemas/PetFilter' + in: query + name: filter + required: false + - content: + application/json: + schema: + items: + $ref: '#/components/schemas/PetOrder' + type: array + in: query + name: order_by + required: false + responses: + "200": + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + description: successful operation + "400": + description: Invalid status value + security: + - petstore_auth: + - read:pets + summary: Finds Pets + tags: + - pet /pet/{petId}: delete: operationId: deletePet parameters: - - in: header + - explode: false + in: header name: api_key + required: false schema: type: string + style: simple - description: Pet id to delete + explode: false in: path name: petId required: true schema: format: int64 type: integer + style: simple responses: "200": content: {} @@ -201,12 +248,14 @@ paths: operationId: getPetById parameters: - description: ID of pet to return + explode: false in: path name: petId required: true schema: format: int64 type: integer + style: simple responses: "200": content: @@ -232,12 +281,14 @@ paths: operationId: updatePetWithForm parameters: - description: ID of pet that needs to be updated + explode: false in: path name: petId required: true schema: format: int64 type: integer + style: simple requestBody: content: application/x-www-form-urlencoded: @@ -259,12 +310,14 @@ paths: operationId: uploadFile parameters: - description: ID of pet to update + explode: false in: path name: petId required: true schema: format: int64 type: integer + style: simple requestBody: content: multipart/form-data: @@ -337,11 +390,13 @@ paths: operationId: deleteOrder parameters: - description: ID of the order that needs to be deleted + explode: false in: path name: order_id required: true schema: type: string + style: simple responses: "400": content: {} @@ -358,6 +413,7 @@ paths: operationId: getOrderById parameters: - description: ID of pet that needs to be fetched + explode: false in: path name: order_id required: true @@ -366,6 +422,7 @@ paths: maximum: 5 minimum: 1 type: integer + style: simple responses: "200": content: @@ -449,17 +506,21 @@ paths: operationId: loginUser parameters: - description: The user name for login + explode: true in: query name: username required: true schema: type: string + style: form - description: The password for login in clear text + explode: true in: query name: password required: true schema: type: string + style: form responses: "200": content: @@ -473,14 +534,18 @@ paths: headers: X-Rate-Limit: description: calls per hour allowed by the user + explode: false schema: format: int32 type: integer + style: simple X-Expires-After: description: date in UTC when token expires + explode: false schema: format: date-time type: string + style: simple "400": content: {} description: Invalid username/password supplied @@ -503,11 +568,13 @@ paths: operationId: deleteUser parameters: - description: The name that needs to be deleted + explode: false in: path name: username required: true schema: type: string + style: simple responses: "400": content: {} @@ -522,11 +589,13 @@ paths: operationId: getUserByName parameters: - description: The name that needs to be fetched. Use user1 for testing. + explode: false in: path name: username required: true schema: type: string + style: simple responses: "200": content: @@ -551,11 +620,13 @@ paths: operationId: updateUser parameters: - description: name that need to be deleted + explode: false in: path name: username required: true schema: type: string + style: simple requestBody: content: '*/*': @@ -604,40 +675,55 @@ paths: operationId: testGroupParameters parameters: - description: Required String in group parameters + explode: true in: query name: required_string_group required: true schema: type: integer + style: form - description: Required Boolean in group parameters + explode: false in: header name: required_boolean_group required: true schema: type: boolean + style: simple - description: Required Integer in group parameters + explode: true in: query name: required_int64_group required: true schema: format: int64 type: integer + style: form - description: String in group parameters + explode: true in: query name: string_group + required: false schema: type: integer + style: form - description: Boolean in group parameters + explode: false in: header name: boolean_group + required: false schema: type: boolean + style: simple - description: Integer in group parameters + explode: true in: query name: int64_group + required: false schema: format: int64 type: integer + style: form responses: "400": content: {} @@ -654,6 +740,7 @@ paths: explode: false in: header name: enum_header_string_array + required: false schema: items: default: $ @@ -664,8 +751,10 @@ paths: type: array style: simple - description: Header parameter enum test (string) + explode: false in: header name: enum_header_string + required: false schema: default: -efg enum: @@ -673,10 +762,12 @@ paths: - -efg - (xyz) type: string + style: simple - description: Query parameter enum test (string array) explode: false in: query name: enum_query_string_array + required: false schema: items: default: $ @@ -687,8 +778,10 @@ paths: type: array style: form - description: Query parameter enum test (string) + explode: true in: query name: enum_query_string + required: false schema: default: -efg enum: @@ -696,24 +789,31 @@ paths: - -efg - (xyz) type: string + style: form - description: Query parameter enum test (double) + explode: true in: query name: enum_query_integer + required: false schema: enum: - 1 - -2 format: int32 type: integer + style: form - description: Query parameter enum test (double) + explode: true in: query name: enum_query_double + required: false schema: enum: - 1.1 - -1.2 format: double type: number + style: form requestBody: content: application/x-www-form-urlencoded: @@ -903,11 +1003,13 @@ paths: put: operationId: testBodyWithQueryParams parameters: - - in: query + - explode: true + in: query name: query required: true schema: type: string + style: form requestBody: content: application/json: @@ -961,12 +1063,14 @@ paths: operationId: 123_test_@#$%_special_tags parameters: - description: to test uuid example value + explode: false in: header name: uuid_test required: true schema: format: uuid type: string + style: simple requestBody: content: application/json: @@ -1016,15 +1120,18 @@ paths: items: type: string type: array - style: form - - in: query + style: pipeDelimited + - explode: true + in: query name: ioutil required: true schema: items: type: string type: array - - in: query + style: form + - explode: false + in: query name: http required: true schema: @@ -1061,12 +1168,14 @@ paths: operationId: uploadFileWithRequiredFile parameters: - description: ID of pet to update + explode: false in: path name: petId required: true schema: format: int64 type: integer + style: simple requestBody: content: multipart/form-data: @@ -1240,6 +1349,25 @@ components: type: object xml: name: Pet + PetFilter: + properties: + tags: + items: + type: string + type: array + status: + items: + type: string + type: array + type: object + PetOrder: + properties: + name: + enum: + - asc + - desc + type: string + type: object ApiResponse: example: code: 0 @@ -1254,14 +1382,6 @@ components: message: type: string type: object - $special[model.name]: - properties: - $special[property.name]: - format: int64 - type: integer - type: object - xml: - name: "$special[model.name]" Return: description: Model for testing reserved words properties: @@ -2146,4 +2266,3 @@ components: http_basic_test: scheme: basic type: http -x-original-swagger-version: "2.0" diff --git a/samples/client/petstore/haskell-http-client/tests/Instances.hs b/samples/client/petstore/haskell-http-client/tests/Instances.hs index adac0d49e16..713272b2f7b 100644 --- a/samples/client/petstore/haskell-http-client/tests/Instances.hs +++ b/samples/client/petstore/haskell-http-client/tests/Instances.hs @@ -470,6 +470,23 @@ genPet n = <*> arbitraryReducedMaybe n -- petTags :: Maybe [Tag] <*> arbitraryReducedMaybe n -- petStatus :: Maybe E'Status2 +instance Arbitrary PetFilter where + arbitrary = sized genPetFilter + +genPetFilter :: Int -> Gen PetFilter +genPetFilter n = + PetFilter + <$> arbitraryReducedMaybe n -- petFilterTags :: Maybe [Text] + <*> arbitraryReducedMaybe n -- petFilterStatus :: Maybe [Text] + +instance Arbitrary PetOrder where + arbitrary = sized genPetOrder + +genPetOrder :: Int -> Gen PetOrder +genPetOrder n = + PetOrder + <$> arbitraryReducedMaybe n -- petOrderName :: Maybe E'Name + instance Arbitrary ReadOnlyFirst where arbitrary = sized genReadOnlyFirst @@ -479,14 +496,6 @@ genReadOnlyFirst n = <$> arbitraryReducedMaybe n -- readOnlyFirstBar :: Maybe Text <*> arbitraryReducedMaybe n -- readOnlyFirstBaz :: Maybe Text -instance Arbitrary SpecialModelName where - arbitrary = sized genSpecialModelName - -genSpecialModelName :: Int -> Gen SpecialModelName -genSpecialModelName n = - SpecialModelName - <$> arbitraryReducedMaybe n -- specialModelNameSpecialPropertyName :: Maybe Integer - instance Arbitrary Tag where arbitrary = sized genTag @@ -605,6 +614,9 @@ instance Arbitrary E'JustSymbol where instance Arbitrary E'Kind where arbitrary = arbitraryBoundedEnum +instance Arbitrary E'Name where + arbitrary = arbitraryBoundedEnum + instance Arbitrary E'Status where arbitrary = arbitraryBoundedEnum diff --git a/samples/client/petstore/haskell-http-client/tests/Test.hs b/samples/client/petstore/haskell-http-client/tests/Test.hs index f7bcc784059..7aea02099b5 100644 --- a/samples/client/petstore/haskell-http-client/tests/Test.hs +++ b/samples/client/petstore/haskell-http-client/tests/Test.hs @@ -58,8 +58,9 @@ main = propMimeEq MimeJSON (Proxy :: Proxy OuterComposite) propMimeEq MimeJSON (Proxy :: Proxy OuterEnum) propMimeEq MimeJSON (Proxy :: Proxy Pet) + propMimeEq MimeJSON (Proxy :: Proxy PetFilter) + propMimeEq MimeJSON (Proxy :: Proxy PetOrder) propMimeEq MimeJSON (Proxy :: Proxy ReadOnlyFirst) - propMimeEq MimeJSON (Proxy :: Proxy SpecialModelName) propMimeEq MimeJSON (Proxy :: Proxy Tag) propMimeEq MimeJSON (Proxy :: Proxy TypeHolderDefault) propMimeEq MimeJSON (Proxy :: Proxy TypeHolderExample) diff --git a/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/API.hs b/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/API.hs index 12a83e1a110..f8c4b566db9 100644 --- a/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/API.hs +++ b/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/API.hs @@ -42,7 +42,9 @@ import Control.Monad.Except (ExceptT, runExceptT) import Control.Monad.IO.Class import Control.Monad.Trans.Reader (ReaderT (..)) import Data.Aeson (Value) +import qualified Data.Aeson as Aeson import Data.ByteString (ByteString) +import qualified Data.ByteString.Lazy as BSL import Data.Coerce (coerce) import Data.Data (Data) import Data.Function ((&)) @@ -52,6 +54,7 @@ import Data.Proxy (Proxy (..)) import Data.Set (Set) import Data.Text (Text) import qualified Data.Text as T +import qualified Data.Text.Encoding as T import Data.Time import Data.UUID (UUID) import GHC.Exts (IsString (..)) @@ -143,11 +146,22 @@ instance ToHttpApiData a => ToHttpApiData (QueryList 'MultiParamArray a) where formatSeparatedQueryList :: ToHttpApiData a => Char -> QueryList p a -> Text formatSeparatedQueryList char = T.intercalate (T.singleton char) . map toQueryParam . fromQueryList +newtype JSONQueryParam a = JSONQueryParam + { fromJsonQueryParam :: a + } deriving (Functor, Foldable, Traversable) + +instance Aeson.ToJSON a => ToHttpApiData (JSONQueryParam a) where + toQueryParam = T.decodeUtf8 . BSL.toStrict . Aeson.encode . fromJsonQueryParam + +instance Aeson.FromJSON a => FromHttpApiData (JSONQueryParam a) where + parseQueryParam = either (Left . T.pack) (Right . JSONQueryParam) . Aeson.eitherDecodeStrict . T.encodeUtf8 + -- | Servant type-level API, generated from the OpenAPI spec for OpenAPIPetstore. type OpenAPIPetstoreAPI = Protected :> "pet" :> ReqBody '[JSON] Pet :> Verb 'POST 200 '[JSON] Pet -- 'addPet' route :<|> Protected :> "pet" :> Capture "petId" Integer :> Header "api_key" Text :> Verb 'DELETE 200 '[JSON] NoContent -- 'deletePet' route + :<|> Protected :> "pet" :> "find" :> QueryParam "filter" (JSONQueryParam PetFilter) :> Verb 'GET 200 '[JSON] [Pet] -- 'findPets' route :<|> Protected :> "pet" :> "findByStatus" :> QueryParam "status" (QueryList 'CommaSeparated (Text)) :> Verb 'GET 200 '[JSON] [Pet] -- 'findPetsByStatus' route :<|> Protected :> "pet" :> "findByTags" :> QueryParam "tags" (QueryList 'CommaSeparated (Text)) :> Verb 'GET 200 '[JSON] [Pet] -- 'findPetsByTags' route :<|> Protected :> "pet" :> Capture "petId" Integer :> Verb 'GET 200 '[JSON] Pet -- 'getPetById' route @@ -188,6 +202,7 @@ newtype OpenAPIPetstoreClientError = OpenAPIPetstoreClientError ClientError data OpenAPIPetstoreBackend a m = OpenAPIPetstoreBackend { addPet :: a -> Pet -> m Pet{- ^ -} , deletePet :: a -> Integer -> Maybe Text -> m NoContent{- ^ -} + , findPets :: a -> Maybe PetFilter -> m [Pet]{- ^ -} , findPetsByStatus :: a -> Maybe [Text] -> m [Pet]{- ^ Multiple status values can be provided with comma separated strings -} , findPetsByTags :: a -> Maybe [Text] -> m [Pet]{- ^ Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. -} , getPetById :: a -> Integer -> m Pet{- ^ Returns a single pet -} @@ -240,6 +255,7 @@ createOpenAPIPetstoreClient = OpenAPIPetstoreBackend{..} where ((coerce -> addPet) :<|> (coerce -> deletePet) :<|> + (coerce -> findPets) :<|> (coerce -> findPetsByStatus) :<|> (coerce -> findPetsByTags) :<|> (coerce -> getPetById) :<|> @@ -314,6 +330,7 @@ serverWaiApplicationOpenAPIPetstore auth backend = serveWithContextT (Proxy :: P serverFromBackend OpenAPIPetstoreBackend{..} = (coerce addPet :<|> coerce deletePet :<|> + coerce findPets :<|> coerce findPetsByStatus :<|> coerce findPetsByTags :<|> coerce getPetById :<|> diff --git a/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/Types.hs b/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/Types.hs index 5b392137005..c11a6b8f812 100644 --- a/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/Types.hs +++ b/samples/server/petstore/haskell-servant/lib/OpenAPIPetstore/Types.hs @@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-unused-imports #-} module OpenAPIPetstore.Types ( @@ -8,6 +9,7 @@ module OpenAPIPetstore.Types ( Category (..), Order (..), Pet (..), + PetFilter (..), SpecialCharacters (..), Tag (..), User (..), @@ -160,6 +162,34 @@ optionsPet = ] +-- | +data PetFilter = PetFilter + { petFilterTags :: Maybe [Text] -- ^ + , petFilterStatus :: Maybe [Text] -- ^ + } deriving (Show, Eq, Generic, Data) + +instance FromJSON PetFilter where + parseJSON = genericParseJSON optionsPetFilter +instance ToJSON PetFilter where + toJSON = genericToJSON optionsPetFilter +instance ToSchema PetFilter where + declareNamedSchema = Swagger.genericDeclareNamedSchema + $ Swagger.fromAesonOptions + $ optionsPetFilter + +optionsPetFilter :: Options +optionsPetFilter = + defaultOptions + { omitNothingFields = True + , fieldLabelModifier = \s -> fromMaybe ("did not find JSON field name for " ++ show s) $ lookup s table + } + where + table = + [ ("petFilterTags", "tags") + , ("petFilterStatus", "status") + ] + + -- | description data SpecialCharacters = SpecialCharacters { specialCharactersDoubleQuote :: Text -- ^ double quote diff --git a/samples/server/petstore/haskell-servant/stack.yaml b/samples/server/petstore/haskell-servant/stack.yaml index 1f1821220e2..7b4767b0ebf 100644 --- a/samples/server/petstore/haskell-servant/stack.yaml +++ b/samples/server/petstore/haskell-servant/stack.yaml @@ -1,4 +1,4 @@ -resolver: lts-19.2 +resolver: lts-22.12 extra-deps: [] packages: - '.' diff --git a/samples/server/petstore/haskell-yesod/config/routes.yesodroutes b/samples/server/petstore/haskell-yesod/config/routes.yesodroutes index e040781920f..2c4407fdefb 100644 --- a/samples/server/petstore/haskell-yesod/config/routes.yesodroutes +++ b/samples/server/petstore/haskell-yesod/config/routes.yesodroutes @@ -6,6 +6,7 @@ /pet PetR PUT POST /pet/findByStatus PetFindByStatusR GET /pet/findByTags PetFindByTagsR GET +/pet/find PetFindR GET !/pet/#Int64 PetByInt64R GET POST DELETE /pet/#Int64/uploadImage PetByInt64UploadImageR POST /store/inventory StoreInventoryR GET diff --git a/samples/server/petstore/haskell-yesod/src/Handler/Pet.hs b/samples/server/petstore/haskell-yesod/src/Handler/Pet.hs index 75dbdf31a10..05ef55c4b0b 100644 --- a/samples/server/petstore/haskell-yesod/src/Handler/Pet.hs +++ b/samples/server/petstore/haskell-yesod/src/Handler/Pet.hs @@ -20,6 +20,12 @@ deletePetByInt64R :: Int64 -- ^ Pet id to delete -> Handler Value deletePetByInt64R petId = notImplemented +-- | Finds Pets +-- +-- operationId: findPets +getPetFindR :: Handler Value +getPetFindR = notImplemented + -- | Finds Pets by status -- -- Multiple status values can be provided with comma separated strings diff --git a/samples/server/petstore/haskell-yesod/src/OpenAPIPetstore/Types.hs b/samples/server/petstore/haskell-yesod/src/OpenAPIPetstore/Types.hs index 2f296b0ea10..98efe492fe3 100644 --- a/samples/server/petstore/haskell-yesod/src/OpenAPIPetstore/Types.hs +++ b/samples/server/petstore/haskell-yesod/src/OpenAPIPetstore/Types.hs @@ -8,6 +8,7 @@ module OpenAPIPetstore.Types ( Category (..), Order (..), Pet (..), + PetFilter (..), SpecialCharacters (..), Tag (..), User (..), @@ -139,6 +140,30 @@ optionsPet = ] +-- | +data PetFilter = PetFilter + { petFilterTags :: Maybe [Text] -- ^ + , petFilterStatus :: Maybe [Text] -- ^ + } deriving (Show, Eq, Generic) + +instance FromJSON PetFilter where + parseJSON = genericParseJSON optionsPetFilter +instance ToJSON PetFilter where + toJSON = genericToJSON optionsPetFilter + +optionsPetFilter :: Options +optionsPetFilter = + defaultOptions + { omitNothingFields = True + , fieldLabelModifier = \s -> fromMaybe ("did not find JSON field name for " ++ show s) $ List.lookup s table + } + where + table = + [ ("petFilterTags", "tags") + , ("petFilterStatus", "status") + ] + + -- | description data SpecialCharacters = SpecialCharacters { specialCharactersDoubleQuote :: Text -- ^ double quote