swagger2 2.0.1 → 2.0.2
raw patch · 6 files changed
+324/−310 lines, 6 files
Files
- CHANGELOG.md +7/−0
- src/Data/Swagger/Internal.hs +7/−8
- src/Data/Swagger/Internal/Schema.hs +1/−1
- src/Data/Swagger/Internal/Schema/Validation.hs +1/−1
- swagger2.cabal +1/−1
- test/Data/SwaggerSpec.hs +307/−299
CHANGELOG.md view
@@ -1,3 +1,10 @@+2.0.2+---++* Fixes:+ * Fix `additionalProperties` to allow references;+ * Fix `ToSchema` instances for `Map` and `HashMap` (prevent infinite recursion for recursive values).+ 2.0.1 ---
src/Data/Swagger/Internal.hs view
@@ -483,7 +483,7 @@ , _schemaAllOf :: Maybe [Schema] , _schemaProperties :: HashMap Text (Referenced Schema)- , _schemaAdditionalProperties :: Maybe Schema+ , _schemaAdditionalProperties :: Maybe (Referenced Schema) , _schemaDiscriminator :: Maybe Text , _schemaReadOnly :: Maybe Bool@@ -1088,13 +1088,12 @@ parseJSON _ = empty instance FromJSON Host where- parseJSON (String s) =- case fromInteger <$> readMaybe portStr of- Nothing | not (null portStr) -> fail $ "Invalid port `" ++ portStr ++ "'"- mport -> pure $ Host host mport- where- (hostText, portText) = Text.breakOn ":" s- [host, portStr] = map Text.unpack [hostText, portText]+ parseJSON (String s) = case map Text.unpack $ Text.split (== ':') s of+ [host] -> return $ Host host Nothing+ [host, port] -> case readMaybe port of+ Nothing -> fail $ "Invalid port `" ++ port ++ "'"+ Just p -> return $ Host host (Just (fromInteger p))+ _ -> fail $ "Invalid host `" ++ Text.unpack s ++ "'" parseJSON _ = empty instance FromJSON MimeList where
src/Data/Swagger/Internal/Schema.hs view
@@ -471,7 +471,7 @@ instance ToSchema a => ToSchema (Map String a) where declareNamedSchema _ = do- schema <- declareSchema (Proxy :: Proxy a)+ schema <- declareSchemaRef (Proxy :: Proxy a) return $ unnamed $ mempty & type_ .~ SwaggerObject & additionalProperties ?~ schema
src/Data/Swagger/Internal/Schema/Validation.hs view
@@ -291,7 +291,7 @@ Null | not (k `elem` (schema ^. required)) -> valid -- null is fine for non-required property _ -> case HashMap.lookup k (schema ^. properties) of- Nothing -> check additionalProperties $ \s -> sub s $ validateWithSchema v+ Nothing -> check additionalProperties $ \s -> validateWithSchemaRef s v Just s -> validateWithSchemaRef s v validateEnum :: Value -> Validation (ParamSchema t) ()
swagger2.cabal view
@@ -1,5 +1,5 @@ name: swagger2-version: 2.0.1+version: 2.0.2 synopsis: Swagger 2.0 data model description: Please see README.md homepage: https://github.com/GetShopTV/swagger2
test/Data/SwaggerSpec.hs view
@@ -21,6 +21,14 @@ spec :: Spec spec = do+ describe "host" $ do+ it "can decode the host port" $ do+ let h = Just $ Host "localhost" (Just (fromInteger 8000))+ swagger :: Swagger+ swagger = swaggerExample+ & host .~ h+ parsed :: Swagger = either error id $ eitherDecode' $ encode swagger+ parsed ^. host `shouldBe` h describe "License Object" $ licenseExample <=> licenseExampleJSON describe "Contact Object" $ contactExample <=> contactExampleJSON describe "Info Object" $ infoExample <=> infoExampleJSON@@ -263,7 +271,7 @@ schemaModelDictExample :: Schema schemaModelDictExample = mempty & type_ .~ SwaggerObject- & additionalProperties ?~ (mempty & type_ .~ SwaggerString)+ & additionalProperties ?~ Inline (mempty & type_ .~ SwaggerString) schemaModelDictExampleJSON :: Value schemaModelDictExampleJSON = [aesonQQ|@@ -493,7 +501,7 @@ "created": 100, "description": "get milk" } |]- & description ?~ "This is some real Todo right here" + & description ?~ "This is some real Todo right here" & properties .~ [ ("created", Inline $ mempty & type_ .~ SwaggerInteger@@ -575,130 +583,130 @@ petstoreExampleJSON :: Value petstoreExampleJSON = [aesonQQ|-{ +{ "swagger":"2.0",- "info":{ + "info":{ "description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", "version":"1.0.0", "title":"Swagger Petstore", "termsOfService":"http://swagger.io/terms/",- "contact":{ + "contact":{ "email":"apiteam@swagger.io" },- "license":{ + "license":{ "name":"Apache 2.0", "url":"http://www.apache.org/licenses/LICENSE-2.0.html" } }, "host":"petstore.swagger.io", "basePath":"/v2",- "tags":[ - { + "tags":[+ { "name":"pet", "description":"Everything about your Pets",- "externalDocs":{ + "externalDocs":{ "description":"Find out more", "url":"http://swagger.io" } },- { + { "name":"store", "description":"Access to Petstore orders" },- { + { "name":"user", "description":"Operations about user",- "externalDocs":{ + "externalDocs":{ "description":"Find out more about our store", "url":"http://swagger.io" } } ],- "schemes":[ + "schemes":[ "http" ],- "paths":{ - "/pet":{ - "post":{ - "tags":[ + "paths":{+ "/pet":{+ "post":{+ "tags":[ "pet" ], "summary":"Add a new pet to the store", "description":"", "operationId":"addPet",- "consumes":[ + "consumes":[ "application/json", "application/xml" ],- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"Pet object that needs to be added to the store", "required":true,- "schema":{ + "schema":{ "$ref":"#/definitions/Pet" } } ],- "responses":{ - "405":{ + "responses":{+ "405":{ "description":"Invalid input" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ] } ] },- "put":{ - "tags":[ + "put":{+ "tags":[ "pet" ], "summary":"Update an existing pet", "description":"", "operationId":"updatePet",- "consumes":[ + "consumes":[ "application/json", "application/xml" ],- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"Pet object that needs to be added to the store", "required":true,- "schema":{ + "schema":{ "$ref":"#/definitions/Pet" } } ],- "responses":{ - "400":{ + "responses":{+ "400":{ "description":"Invalid ID supplied" },- "404":{ + "404":{ "description":"Pet not found" },- "405":{ + "405":{ "description":"Validation exception" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ]@@ -706,28 +714,28 @@ ] } },- "/pet/findByStatus":{ - "get":{ - "tags":[ + "/pet/findByStatus":{+ "get":{+ "tags":[ "pet" ], "summary":"Finds Pets by status", "description":"Multiple status values can be provided with comma seperated strings", "operationId":"findPetsByStatus",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"status", "in":"query", "description":"Status values that need to be considered for filter", "required":true, "type":"array",- "items":{ + "items":{ "type":"string",- "enum":[ + "enum":[ "available", "pending", "sold"@@ -737,23 +745,23 @@ "collectionFormat":"csv" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "type":"array",- "items":{ + "items":{ "$ref":"#/definitions/Pet" } } },- "400":{ + "400":{ "description":"Invalid status value" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ]@@ -761,48 +769,48 @@ ] } },- "/pet/findByTags":{ - "get":{ - "tags":[ + "/pet/findByTags":{+ "get":{+ "tags":[ "pet" ], "summary":"Finds Pets by tags", "description":"Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.", "operationId":"findPetsByTags",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"tags", "in":"query", "description":"Tags to filter by", "required":true, "type":"array",- "items":{ + "items":{ "type":"string" }, "collectionFormat":"csv" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "type":"array",- "items":{ + "items":{ "$ref":"#/definitions/Pet" } } },- "400":{ + "400":{ "description":"Invalid tag value" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ]@@ -810,20 +818,20 @@ ] } },- "/pet/{petId}":{ - "get":{ - "tags":[ + "/pet/{petId}":{+ "get":{+ "tags":[ "pet" ], "summary":"Find pet by ID", "description":"Returns a single pet", "operationId":"getPetById",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"petId", "in":"path", "description":"ID of pet to return",@@ -832,42 +840,42 @@ "format":"int64" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "$ref":"#/definitions/Pet" } },- "400":{ + "400":{ "description":"Invalid ID supplied" },- "404":{ + "404":{ "description":"Pet not found" } },- "security":[ - { + "security":[+ { "api_key": [] } ] },- "post":{ - "tags":[ + "post":{+ "tags":[ "pet" ], "summary":"Updates a pet in the store with form data", "description":"", "operationId":"updatePetWithForm",- "consumes":[ + "consumes":[ "application/x-www-form-urlencoded" ],- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"petId", "in":"path", "description":"ID of pet that needs to be updated",@@ -875,14 +883,14 @@ "type":"integer", "format":"int64" },- { + { "name":"name", "in":"formData", "description":"Updated name of the pet", "required":false, "type":"string" },- { + { "name":"status", "in":"formData", "description":"Updated status of the pet",@@ -890,39 +898,39 @@ "type":"string" } ],- "responses":{ - "405":{ + "responses":{+ "405":{ "description":"Invalid input" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ] } ] },- "delete":{ - "tags":[ + "delete":{+ "tags":[ "pet" ], "summary":"Deletes a pet", "description":"", "operationId":"deletePet",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"api_key", "in":"header", "required":false, "type":"string" },- { + { "name":"petId", "in":"path", "description":"Pet id to delete",@@ -931,14 +939,14 @@ "format":"int64" } ],- "responses":{ - "400":{ + "responses":{+ "400":{ "description":"Invalid pet value" } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ]@@ -946,22 +954,22 @@ ] } },- "/pet/{petId}/uploadImage":{ - "post":{ - "tags":[ + "/pet/{petId}/uploadImage":{+ "post":{+ "tags":[ "pet" ], "summary":"uploads an image", "description":"", "operationId":"uploadFile",- "consumes":[ + "consumes":[ "multipart/form-data" ],- "produces":[ + "produces":[ "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"petId", "in":"path", "description":"ID of pet to update",@@ -969,14 +977,14 @@ "type":"integer", "format":"int64" },- { + { "name":"additionalMetadata", "in":"formData", "description":"Additional data to pass to server", "required":false, "type":"string" },- { + { "name":"file", "in":"formData", "description":"file to upload",@@ -984,17 +992,17 @@ "type":"file" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "$ref":"#/definitions/ApiResponse" } } },- "security":[ - { - "petstore_auth":[ + "security":[+ {+ "petstore_auth":[ "write:pets", "read:pets" ]@@ -1002,87 +1010,87 @@ ] } },- "/store/inventory":{ - "get":{ - "tags":[ + "/store/inventory":{+ "get":{+ "tags":[ "store" ], "summary":"Returns pet inventories by status", "description":"Returns a map of status codes to quantities", "operationId":"getInventory",- "produces":[ + "produces":[ "application/json" ], "parameters": [],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "type":"object",- "additionalProperties":{ + "additionalProperties":{ "type":"integer", "format":"int32" } } } },- "security":[ - { + "security":[+ { "api_key": [] } ] } },- "/store/order":{ - "post":{ - "tags":[ + "/store/order":{+ "post":{+ "tags":[ "store" ], "summary":"Place an order for a pet", "description":"", "operationId":"placeOrder",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"order placed for purchasing the pet", "required":true,- "schema":{ + "schema":{ "$ref":"#/definitions/Order" } } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "$ref":"#/definitions/Order" } },- "400":{ + "400":{ "description":"Invalid Order" } } } },- "/store/order/{orderId}":{ - "get":{ - "tags":[ + "/store/order/{orderId}":{+ "get":{+ "tags":[ "store" ], "summary":"Find purchase order by ID", "description":"For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", "operationId":"getOrderById",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"orderId", "in":"path", "description":"ID of pet that needs to be fetched",@@ -1093,34 +1101,34 @@ "format":"int64" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "$ref":"#/definitions/Order" } },- "400":{ + "400":{ "description":"Invalid ID supplied" },- "404":{ + "404":{ "description":"Order not found" } } },- "delete":{ - "tags":[ + "delete":{+ "tags":[ "store" ], "summary":"Delete purchase order by ID", "description":"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", "operationId":"deleteOrder",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"orderId", "in":"path", "description":"ID of the order that needs to be deleted",@@ -1129,133 +1137,133 @@ "minimum":1.0 } ],- "responses":{ - "400":{ + "responses":{+ "400":{ "description":"Invalid ID supplied" },- "404":{ + "404":{ "description":"Order not found" } } } },- "/user":{ - "post":{ - "tags":[ + "/user":{+ "post":{+ "tags":[ "user" ], "summary":"Create user", "description":"This can only be done by the logged in user.", "operationId":"createUser",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"Created user object", "required":true,- "schema":{ + "schema":{ "$ref":"#/definitions/User" } } ],- "responses":{ - "default":{ + "responses":{+ "default":{ "description":"successful operation" } } } },- "/user/createWithArray":{ - "post":{ - "tags":[ + "/user/createWithArray":{+ "post":{+ "tags":[ "user" ], "summary":"Creates list of users with given input array", "description":"", "operationId":"createUsersWithArrayInput",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"List of user object", "required":true,- "schema":{ + "schema":{ "type":"array",- "items":{ + "items":{ "$ref":"#/definitions/User" } } } ],- "responses":{ - "default":{ + "responses":{+ "default":{ "description":"successful operation" } } } },- "/user/createWithList":{ - "post":{ - "tags":[ + "/user/createWithList":{+ "post":{+ "tags":[ "user" ], "summary":"Creates list of users with given input array", "description":"", "operationId":"createUsersWithListInput",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "in":"body", "name":"body", "description":"List of user object", "required":true,- "schema":{ + "schema":{ "type":"array",- "items":{ + "items":{ "$ref":"#/definitions/User" } } } ],- "responses":{ - "default":{ + "responses":{+ "default":{ "description":"successful operation" } } } },- "/user/login":{ - "get":{ - "tags":[ + "/user/login":{+ "get":{+ "tags":[ "user" ], "summary":"Logs user into the system", "description":"", "operationId":"loginUser",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"username", "in":"query", "description":"The user name for login", "required":true, "type":"string" },- { + { "name":"password", "in":"query", "description":"The password for login in clear text",@@ -1263,65 +1271,65 @@ "type":"string" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "type":"string" },- "headers":{ - "X-Rate-Limit":{ + "headers":{+ "X-Rate-Limit":{ "type":"integer", "format":"int32", "description":"calls per hour allowed by the user" },- "X-Expires-After":{ + "X-Expires-After":{ "type":"string", "format":"date-time", "description":"date in UTC when toekn expires" } } },- "400":{ + "400":{ "description":"Invalid username/password supplied" } } } },- "/user/logout":{ - "get":{ - "tags":[ + "/user/logout":{+ "get":{+ "tags":[ "user" ], "summary":"Logs out current logged in user session", "description":"", "operationId":"logoutUser",- "produces":[ + "produces":[ "application/xml", "application/json" ], "parameters": [],- "responses":{ - "default":{ + "responses":{+ "default":{ "description":"successful operation" } } } },- "/user/{username}":{ - "get":{ - "tags":[ + "/user/{username}":{+ "get":{+ "tags":[ "user" ], "summary":"Get user by user name", "description":"", "operationId":"getUserByName",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"username", "in":"path", "description":"The name that needs to be fetched. Use user1 for testing. ",@@ -1329,72 +1337,72 @@ "type":"string" } ],- "responses":{ - "200":{ + "responses":{+ "200":{ "description":"successful operation",- "schema":{ + "schema":{ "$ref":"#/definitions/User" } },- "400":{ + "400":{ "description":"Invalid username supplied" },- "404":{ + "404":{ "description":"User not found" } } },- "put":{ - "tags":[ + "put":{+ "tags":[ "user" ], "summary":"Updated user", "description":"This can only be done by the logged in user.", "operationId":"updateUser",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"username", "in":"path", "description":"name that need to be deleted", "required":true, "type":"string" },- { + { "in":"body", "name":"body", "description":"Updated user object", "required":true,- "schema":{ + "schema":{ "$ref":"#/definitions/User" } } ],- "responses":{ - "400":{ + "responses":{+ "400":{ "description":"Invalid user supplied" },- "404":{ + "404":{ "description":"User not found" } } },- "delete":{ - "tags":[ + "delete":{+ "tags":[ "user" ], "summary":"Delete user", "description":"This can only be done by the logged in user.", "operationId":"deleteUser",- "produces":[ + "produces":[ "application/xml", "application/json" ],- "parameters":[ - { + "parameters":[+ { "name":"username", "in":"path", "description":"The name that needs to be deleted",@@ -1402,205 +1410,205 @@ "type":"string" } ],- "responses":{ - "400":{ + "responses":{+ "400":{ "description":"Invalid username supplied" },- "404":{ + "404":{ "description":"User not found" } } } } },- "securityDefinitions":{ - "petstore_auth":{ + "securityDefinitions":{+ "petstore_auth":{ "type":"oauth2", "authorizationUrl":"http://petstore.swagger.io/api/oauth/dialog", "flow":"implicit",- "scopes":{ + "scopes":{ "write:pets":"modify pets in your account", "read:pets":"read your pets" } },- "api_key":{ + "api_key":{ "type":"apiKey", "name":"api_key", "in":"header" } },- "definitions":{ - "Order":{ + "definitions":{+ "Order":{ "type":"object",- "properties":{ - "id":{ + "properties":{+ "id":{ "type":"integer", "format":"int64" },- "petId":{ + "petId":{ "type":"integer", "format":"int64" },- "quantity":{ + "quantity":{ "type":"integer", "format":"int32" },- "shipDate":{ + "shipDate":{ "type":"string", "format":"date-time" },- "status":{ + "status":{ "type":"string", "description":"Order Status",- "enum":[ + "enum":[ "placed", "approved", "delivered" ] },- "complete":{ + "complete":{ "type":"boolean", "default":false } },- "xml":{ + "xml":{ "name":"Order" } },- "Category":{ + "Category":{ "type":"object",- "properties":{ - "id":{ + "properties":{+ "id":{ "type":"integer", "format":"int64" },- "name":{ + "name":{ "type":"string" } },- "xml":{ + "xml":{ "name":"Category" } },- "User":{ + "User":{ "type":"object",- "properties":{ - "id":{ + "properties":{+ "id":{ "type":"integer", "format":"int64" },- "username":{ + "username":{ "type":"string" },- "firstName":{ + "firstName":{ "type":"string" },- "lastName":{ + "lastName":{ "type":"string" },- "email":{ + "email":{ "type":"string" },- "password":{ + "password":{ "type":"string" },- "phone":{ + "phone":{ "type":"string" },- "userStatus":{ + "userStatus":{ "type":"integer", "format":"int32", "description":"User Status" } },- "xml":{ + "xml":{ "name":"User" } },- "Tag":{ + "Tag":{ "type":"object",- "properties":{ - "id":{ + "properties":{+ "id":{ "type":"integer", "format":"int64" },- "name":{ + "name":{ "type":"string" } },- "xml":{ + "xml":{ "name":"Tag" } },- "Pet":{ + "Pet":{ "type":"object",- "required":[ + "required":[ "name", "photoUrls" ],- "properties":{ - "id":{ + "properties":{+ "id":{ "type":"integer", "format":"int64" },- "category":{ + "category":{ "$ref":"#/definitions/Category" },- "name":{ + "name":{ "type":"string", "example":"doggie" },- "photoUrls":{ + "photoUrls":{ "type":"array",- "xml":{ + "xml":{ "name":"photoUrl", "wrapped":true },- "items":{ + "items":{ "type":"string" } },- "tags":{ + "tags":{ "type":"array",- "xml":{ + "xml":{ "name":"tag", "wrapped":true },- "items":{ + "items":{ "$ref":"#/definitions/Tag" } },- "status":{ + "status":{ "type":"string", "description":"pet status in the store",- "enum":[ + "enum":[ "available", "pending", "sold" ] } },- "xml":{ + "xml":{ "name":"Pet" } },- "ApiResponse":{ + "ApiResponse":{ "type":"object",- "properties":{ - "code":{ + "properties":{+ "code":{ "type":"integer", "format":"int32" },- "type":{ + "type":{ "type":"string" },- "message":{ + "message":{ "type":"string" } } } },- "externalDocs":{ + "externalDocs":{ "description":"Find out more about Swagger", "url":"http://swagger.io" }