diff --git a/CHANGELOG.md b/CHANGELOG.md index 114a9b1b7..7259e9bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added +- Ability to generate an OpenAPI spec - @mainx07, @hudayou, @ruslantalpa, @begriffs +- Ability to set addresses to listen on - @hudayou - Output names of used-defined types (instead of 'USER-DEFINED') - @martingms diff --git a/circle.yml b/circle.yml index 532d44050..1e6da4d1e 100644 --- a/circle.yml +++ b/circle.yml @@ -18,7 +18,7 @@ dependencies: test: override: - stack test - - git ls-files | grep '\.l\?hs$' | xargs stack exec -- hlint -X QuasiQuotes "$@" + - git ls-files | grep '\.l\?hs$' | xargs stack exec -- hlint -X QuasiQuotes -X NoPatternSynonyms "$@" - stack exec -- cabal update - stack exec --no-ghc-package-path -- cabal install --only-d --dry-run - stack exec -- packdeps *.cabal || true diff --git a/main/Main.hs b/main/Main.hs index f0fe92de1..9bac6b36e 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -13,6 +13,7 @@ import PostgREST.DbStructure import Control.Monad import Data.Monoid ((<>)) import Data.String.Conversions (cs) +import Data.String (IsString (..)) import qualified Hasql.Query as H import qualified Hasql.Session as H import qualified Hasql.Decoders as HD @@ -47,9 +48,11 @@ main = do hSetBuffering stderr NoBuffering conf <- readOptions - let port = configPort conf + let host = configHost conf + port = configPort conf pgSettings = cs (configDatabase conf) - appSettings = setPort port + appSettings = setHost (fromString host) + . setPort port . setServerName (cs $ "postgrest/" <> prettyVersion) $ defaultSettings diff --git a/postgrest.cabal b/postgrest.cabal index d70ae754e..011ad2f1b 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -44,8 +44,8 @@ executable postgrest , http-types , interpolatedstring-perl6 , jwt - , microlens >= 0.4.2 && < 0.5 - , microlens-aeson >= 2.1.1 && < 2.2 + , lens >=3.8 && < 5.0 + , lens-aeson >= 1.0.0.0 && < 1.1.0.0 , mtl , optparse-applicative >= 0.11 && < 0.13 , parsec @@ -65,6 +65,8 @@ executable postgrest , wai-extra , wai-middleware-static >= 0.6.0 , warp >= 3.1.0 + , insert-ordered-containers >= 0.1.0.1 + , swagger2 >= 2.1 , HTTP , Ranged-sets if !os(windows) @@ -89,8 +91,8 @@ library , http-types , interpolatedstring-perl6 , jwt - , microlens - , microlens-aeson + , lens + , lens-aeson , mtl , optparse-applicative , parsec @@ -109,6 +111,8 @@ library , wai-extra , wai-middleware-static >= 0.6.0 , warp >= 3.1.0 + , insert-ordered-containers >= 0.1.0.1 + , swagger2 >= 2.1 Other-Modules: Paths_postgrest Exposed-Modules: PostgREST.App @@ -122,6 +126,7 @@ library , PostgREST.RangeQuery , PostgREST.ApiRequest , PostgREST.Types + , PostgREST.OpenAPI hs-source-dirs: src Test-Suite spec @@ -163,8 +168,8 @@ Test-Suite spec , http-types , interpolatedstring-perl6 , jwt - , microlens - , microlens-aeson + , lens + , lens-aeson , monad-control , mtl , optparse-applicative @@ -186,5 +191,9 @@ Test-Suite spec , wai-extra , wai-middleware-static , warp + , insert-ordered-containers + , hjsonpointer + , hjsonschema + , swagger2 , HTTP , Ranged-sets diff --git a/src/PostgREST/ApiRequest.hs b/src/PostgREST/ApiRequest.hs index 02fa5da37..5002454fe 100644 --- a/src/PostgREST/ApiRequest.hs +++ b/src/PostgREST/ApiRequest.hs @@ -45,10 +45,11 @@ data Target = TargetIdent QualifiedIdentifier data PreferRepresentation = Full | HeadersOnly | None deriving Eq -- | Enumeration of currently supported content types for -- route responses and upload payloads -data ContentType = ApplicationJSON | TextCSV deriving Eq +data ContentType = ApplicationJSON | TextCSV | OpenAPI deriving Eq instance Show ContentType where show ApplicationJSON = "application/json; charset=utf-8" show TextCSV = "text/csv; charset=utf-8" + show OpenAPI = "application/openapi+json; charset=utf-8" {-| Describes what the user wants to do. This data type is a @@ -123,6 +124,8 @@ userApiRequest schema req reqBody = Nothing -> PayloadParseError "All lines must have same number of fields" Just json -> PayloadJSON json) (CSV.decodeByName reqBody) + Right oa@OpenAPI -> + PayloadParseError $ "Content-type not acceptable: " <> cs (show oa) -- This is a Left value because form-urlencoded is not a content -- type which we ever use for responses, only something we handle -- just this once for requests @@ -208,11 +211,13 @@ pickContentType :: Maybe BS.ByteString -> Either BS.ByteString ContentType pickContentType accept | isNothing accept || has ctAll || has ctJson = Right ApplicationJSON | has ctCsv = Right TextCSV + | has ctOpenAPI = Right OpenAPI | otherwise = Left accept' where ctAll = "*/*" ctCsv = "text/csv" ctJson = "application/json" + ctOpenAPI = "application/openapi+json" Just accept' = accept findInAccept = flip find $ parseHttpAccept accept' has = isJust . findInAccept . BS.isPrefixOf diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 0a3c4722a..25c6f090f 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -60,6 +60,7 @@ import PostgREST.QueryBuilder ( callProc , ResultsWithCount ) import PostgREST.Types +import PostgREST.OpenAPI import Prelude @@ -180,9 +181,6 @@ app dbStructure conf apiRequest = let cols = filter (filterCol tSchema tTable) $ dbColumns dbStructure pkeys = map pkName $ filter (filterPk tSchema tTable) allPrKeys body = encode (TableOptions cols pkeys) - filterCol :: Schema -> TableName -> Column -> Bool - filterCol sc tb Column{colTable=Table{tableSchema=s, tableName=t}} = s==sc && t==tb - filterCol _ _ _ = False acceptH = (hAllow, if tableInsertable table then "GET,POST,PATCH,DELETE" else "GET") in return $ responseLBS status200 [jsonH, allOrigins, acceptH] $ cs body @@ -206,8 +204,13 @@ app dbStructure conf apiRequest = else return notFound (ActionRead, TargetRoot, Nothing) -> do - body <- encode <$> H.query schema accessibleTables - return $ responseLBS status200 [jsonH] $ cs body + let encodeApi ti = encodeOpenAPI ti host port + host = configHost conf + port = toInteger $ configPort conf + encodeFn = if contentType == OpenAPI then encodeApi . toTableInfo else encode + header = if contentType == OpenAPI then openapiH else jsonH + body <- encodeFn <$> H.query schema accessibleTables + return $ responseLBS status200 [header] $ cs body (ActionInappropriate, _, _) -> return $ responseLBS status405 [] "" @@ -220,8 +223,19 @@ app dbStructure conf apiRequest = (_, _, _) -> return notFound where + toTableInfo :: [Table] -> [(Table, [Column], [Text])] + toTableInfo = map (\t -> + let tSchema = tableSchema t + tTable = tableName t + cols = filter (filterCol tSchema tTable) $ dbColumns dbStructure + pkeys = map pkName $ filter (filterPk tSchema tTable) allPrKeys + in + (t, cols, pkeys)) notFound = responseLBS status404 [] "" filterPk sc table pk = sc == (tableSchema . pkTable) pk && table == (tableName . pkTable) pk + filterCol :: Schema -> TableName -> Column -> Bool + filterCol sc tb Column{colTable=Table{tableSchema=s, tableName=t}} = s==sc && t==tb + filterCol _ _ _ = False allPrKeys = dbPrimaryKeys dbStructure allOrigins = ("Access-Control-Allow-Origin", "*") :: Header schema = cs $ configSchema conf @@ -273,6 +287,9 @@ contentRangeH frm to total = jsonH :: Header jsonH = (hContentType, "application/json; charset=utf-8") +openapiH :: Header +openapiH = (hContentType, "application/openapi+json; charset=utf-8") + formatRelationError :: Text -> Text formatRelationError = formatGeneralError "could not find foreign keys between these entities" diff --git a/src/PostgREST/Auth.hs b/src/PostgREST/Auth.hs index 701ba5f23..ed6591f4f 100644 --- a/src/PostgREST/Auth.hs +++ b/src/PostgREST/Auth.hs @@ -18,9 +18,9 @@ module PostgREST.Auth ( , tokenJWT ) where -import Lens.Micro -import Lens.Micro.Aeson +import Control.Lens import Data.Aeson (Value (..), parseJSON, toJSON) +import Data.Aeson.Lens import Data.Aeson.Types (parseMaybe, emptyObject, emptyArray) import qualified Data.ByteString as BS import qualified Data.Vector as V diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 1f317c1bb..3835b342a 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -39,6 +39,7 @@ data AppConfig = AppConfig { configDatabase :: String , configAnonRole :: String , configSchema :: String + , configHost :: String , configPort :: Int , configJwtSecret :: Secret , configPool :: Int @@ -51,6 +52,7 @@ argParser = AppConfig <$> argument str (help "(REQUIRED) database connection string, e.g. postgres://user:pass@host:port/db" <> metavar "DB_URL") <*> strOption (long "anonymous" <> short 'a' <> help "(REQUIRED) postgres role to use for non-authenticated requests" <> metavar "ROLE") <*> strOption (long "schema" <> short 's' <> help "schema to use for API routes" <> metavar "NAME" <> value "public" <> showDefault) + <*> strOption (long "host" <> short 'l' <> help "hostname or ip on which to run HTTP server" <> metavar "HOST" <> value "*4" <> showDefault) <*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault) <*> (secret . cs <$> strOption (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET" <> value "secret" <> showDefault)) diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 969b20c09..6682fdb35 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -6,6 +6,7 @@ module PostgREST.Middleware where import Data.Aeson (Value (..)) import qualified Data.HashMap.Strict as M import Data.String.Conversions (cs) +import Data.Maybe (fromMaybe, listToMaybe) import Data.Text import qualified Hasql.Transaction as H @@ -17,7 +18,7 @@ import Network.Wai.Middleware.Cors (cors) import Network.Wai.Middleware.Gzip (def, gzip) import Network.Wai.Middleware.Static (only, staticPolicy) -import PostgREST.ApiRequest (ApiRequest(..), pickContentType) +import PostgREST.ApiRequest (ApiRequest(..), ContentType(..), pickContentType) import PostgREST.Auth (claimsToSQL) import PostgREST.Config (AppConfig (..), corsPolicy) import PostgREST.Error (errResponse) @@ -40,10 +41,14 @@ runWithClaims conf eClaims app req = unsupportedAccept :: Application -> Application unsupportedAccept app req respond = - case accept of - Left _ -> respond $ errResponse status415 "Unsupported Accept header, try: application/json" - Right _ -> app req respond + case (isTargetRoot, accept) of + (_, Left _) -> unsupportedAcceptRespond + (False, Right OpenAPI) -> unsupportedAcceptRespond + (_, Right _) -> app req respond where accept = pickContentType $ lookup hAccept $ requestHeaders req + path = pathInfo req + isTargetRoot = fromMaybe True $ (== "") <$> listToMaybe path + unsupportedAcceptRespond = respond $ errResponse status415 "Unsupported Accept header, try: application/json" defaultMiddle :: Application -> Application defaultMiddle = diff --git a/src/PostgREST/OpenAPI.hs b/src/PostgREST/OpenAPI.hs new file mode 100644 index 000000000..dcea58909 --- /dev/null +++ b/src/PostgREST/OpenAPI.hs @@ -0,0 +1,241 @@ +{-# LANGUAGE OverloadedStrings #-} + +module PostgREST.OpenAPI ( + encodeOpenAPI + ) where + +import Control.Lens +import Data.Aeson (decode, encode) +import Data.ByteString.Lazy (ByteString) +import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList) +import Data.String (IsString (..)) +import Data.Text (Text, unpack, pack, concat, intercalate) +import qualified Data.Set as Set + +import Prelude hiding (concat) + +import Data.Swagger + +import PostgREST.ApiRequest (ContentType(..)) +import PostgREST.Config (prettyVersion) +import PostgREST.QueryBuilder (operators) +import PostgREST.Types (Table(..), Column(..)) + +makeMimeList :: [ContentType] -> MimeList +makeMimeList cs = MimeList $ map (fromString . show) cs + +toSwaggerType :: Text -> SwaggerType t +toSwaggerType "text" = SwaggerString +toSwaggerType "integer" = SwaggerInteger +toSwaggerType "boolean" = SwaggerBoolean +toSwaggerType "numeric" = SwaggerNumber +toSwaggerType _ = SwaggerString + +makeProperty :: Column -> (Text, Referenced Schema) +makeProperty c = (colName c, Inline u) + where + r = mempty :: Schema + s = if null $ colEnum c + then r + else r & enum_ .~ decode (encode (colEnum c)) + t = s & type_ .~ toSwaggerType (colType c) + u = t & format ?~ colType c + +makeProperties :: [Column] -> InsOrdHashMap Text (Referenced Schema) +makeProperties cs = fromList $ map makeProperty cs + +makeDefinition :: (Table, [Column], [Text]) -> (Text, Schema) +makeDefinition (t, cs, _) = + let tn = tableName t in + (tn, (mempty :: Schema) + & type_ .~ SwaggerObject + & properties .~ makeProperties cs) + +makeDefinitions :: [(Table, [Column], [Text])] -> InsOrdHashMap Text Schema +makeDefinitions ti = fromList $ map makeDefinition ti + +makeOperatorPattern :: Text +makeOperatorPattern = + intercalate "|" + [ concat ["^", x, y, "[.]"] | + x <- ["not[.]", ""], + y <- map fst operators ] + +makeRowFilter :: Column -> Param +makeRowFilter c = + (mempty :: Param) + & name .~ colName c + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamQuery + & type_ .~ SwaggerString + & format ?~ colType c + & pattern ?~ makeOperatorPattern) + +makeRowFilters :: [Column] -> [Param] +makeRowFilters = map makeRowFilter + +makeOrderItems :: [Column] -> [Text] +makeOrderItems cs = + [ concat [x, y, z] | + x <- map colName cs, + y <- [".asc", ".desc", ""], + z <- [".nullsfirst", ".nulllast", ""] + ] + +makeRangeParams :: [Param] +makeRangeParams = + [ (mempty :: Param) + & name .~ "Range" + & description ?~ "Limiting and Pagination" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamHeader + & type_ .~ SwaggerString) + , (mempty :: Param) + & name .~ "Range-Unit" + & description ?~ "Limiting and Pagination" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamHeader + & type_ .~ SwaggerString + & default_ .~ decode "\"items\"") + , (mempty :: Param) + & name .~ "offset" + & description ?~ "Limiting and Pagination" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamQuery + & type_ .~ SwaggerString) + , (mempty :: Param) + & name .~ "limit" + & description ?~ "Limiting and Pagination" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamQuery + & type_ .~ SwaggerString) + ] + +makePreferParam :: [Text] -> Param +makePreferParam ts = + (mempty :: Param) + & name .~ "Prefer" + & description ?~ "Preference" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamHeader + & type_ .~ SwaggerString + & enum_ .~ decode (encode ts)) + +makeSelectParam :: Param +makeSelectParam = + (mempty :: Param) + & name .~ "select" + & description ?~ "Filtering Columns" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamQuery + & type_ .~ SwaggerString) + +makeGetParams :: [Column] -> [Param] +makeGetParams [] = + makeRangeParams ++ + [ makeSelectParam + , makePreferParam ["plurality=singular", "count=none"] + ] +makeGetParams cs = + makeRangeParams ++ + [ makeSelectParam + , (mempty :: Param) + & name .~ "order" + & description ?~ "Ordering" + & required ?~ False + & schema .~ ParamOther ((mempty :: ParamOtherSchema) + & in_ .~ ParamQuery + & type_ .~ SwaggerString + & enum_ .~ decode (encode $ makeOrderItems cs)) + , makePreferParam ["plurality=singular", "count=none"] + ] + +makeReturnPreferenceParam :: Param +makeReturnPreferenceParam = + makePreferParam ["return=representation", "return=minimal", "return=none"] + +makePostParams :: Text -> [Param] +makePostParams tn = + [ makeReturnPreferenceParam + , (mempty :: Param) + & name .~ "body" + & description ?~ tn + & required ?~ False + & schema .~ ParamBody (Ref (Reference tn)) + ] + +makeDeleteParams :: [Param] +makeDeleteParams = + [ makeReturnPreferenceParam ] + +makePathItem :: (Table, [Column], [Text]) -> (FilePath, PathItem) +makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t) + where + tOp = (mempty :: Operation) + & tags .~ Set.fromList [tn] + & produces ?~ makeMimeList [ApplicationJSON, TextCSV] + & at 200 ?~ "OK" + getOp = tOp + & parameters .~ map Inline (makeGetParams cs ++ rs) + & at 206 ?~ "Partial Content" + postOp = tOp + & consumes ?~ makeMimeList [ApplicationJSON, TextCSV] + & parameters .~ map Inline (makePostParams tn) + & at 201 ?~ "Created" + patchOp = tOp + & consumes ?~ makeMimeList [ApplicationJSON, TextCSV] + & parameters .~ map Inline (makePostParams tn ++ rs) + & at 204 ?~ "No Content" + deletOp = tOp + & parameters .~ map Inline (makeDeleteParams ++ rs) + pr = (mempty :: PathItem) & get ?~ getOp + pw = pr & post ?~ postOp & patch ?~ patchOp & delete ?~ deletOp + p False = pr + p True = pw + rs = makeRowFilters cs + tn = tableName t + +makeRootPathItem :: (FilePath, PathItem) +makeRootPathItem = ("/", p) + where + getOp = (mempty :: Operation) + & tags .~ Set.fromList ["/"] + & produces ?~ makeMimeList [ApplicationJSON, OpenAPI] + & at 200 ?~ "OK" + pr = (mempty :: PathItem) & get ?~ getOp + p = pr + +makePathItems :: [(Table, [Column], [Text])] -> InsOrdHashMap FilePath PathItem +makePathItems ti = fromList $ makeRootPathItem : map makePathItem ti + +escapeHostName :: String -> String +escapeHostName "*" = "0.0.0.0" +escapeHostName "*4" = "0.0.0.0" +escapeHostName "!4" = "0.0.0.0" +escapeHostName "*6" = "0.0.0.0" +escapeHostName "!6" = "0.0.0.0" +escapeHostName h = h + +postgrestSpec:: [(Table, [Column], [Text])] -> String -> Integer -> Swagger +postgrestSpec ti h p = (mempty :: Swagger) + & basePath ?~ "/" + & schemes ?~ [Http] + & info .~ ((mempty :: Info) + & version .~ pack prettyVersion + & title .~ "PostgREST API" + & description ?~ "This is a dynamic API generated by PostgREST") + & host .~ h' + & definitions .~ makeDefinitions ti + & paths .~ makePathItems ti + where + h' = Just $ Host (escapeHostName h) (Just (fromInteger p)) + +encodeOpenAPI :: [(Table, [Column], [Text])] -> String -> Integer -> ByteString +encodeOpenAPI ti h p = encode $ postgrestSpec ti h p diff --git a/stack.yaml b/stack.yaml index cfe320b1b..6419ec45f 100644 --- a/stack.yaml +++ b/stack.yaml @@ -16,9 +16,17 @@ extra-deps: - wai-cors-0.2.5 - cryptohash-sha256-0.11.100.0 - hackage-security-0.5.2.1 - + - unordered-containers-0.2.7.1 + - insert-ordered-containers-0.1.0.1 + - swagger2-2.1 + # - hjsonschema-0.10.0.2 + - hjsonpointer-0.3.0.1 ghc-options: postgrest: -O2 -Werror -Wall -fwarn-identities packages: - . +- location: + git: https://github.com/seagreen/hjsonschema + commit: 075da33626d9d5cf20645a998b86518222d6b2d6 + extra-dep: true diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index f5313b69c..f3e75cf45 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -8,7 +8,11 @@ import SpecHelper import Network.HTTP.Types import Network.Wai (Application) -import Network.Wai.Test (SResponse(simpleHeaders)) +import Network.Wai.Test (SResponse(simpleStatus, simpleHeaders, simpleBody)) + +import Data.Maybe (fromJust) +import Data.Aeson (decode) +import qualified Data.JsonSchema.Draft4 as D4 spec :: SpecWith Application spec = do @@ -61,6 +65,33 @@ spec = do ] |] {matchStatus = 200} + it "returns a valid openapi spec" $ do + r <- request methodGet "/" [("Accept", "application/openapi+json")] "" + liftIO $ + let respStatus = simpleStatus r in + respStatus `shouldSatisfy` + \s -> s == Status { statusCode = 200, statusMessage="OK" } + liftIO $ + let respHeaders = simpleHeaders r in + respHeaders `shouldSatisfy` + \hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs + liftIO $ + let respBody = simpleBody r + schema :: D4.Schema + schema = D4.emptySchema { D4._schemaRef = Just "openapi.json" } + schemaContext :: D4.SchemaWithURI D4.Schema + schemaContext = D4.SchemaWithURI + { D4._swSchema = schema + , D4._swURI = Just "test/fixtures/openapi.json" + } + in + D4.fetchFilesystemAndValidate schemaContext ((fromJust . decode) respBody) `shouldReturn` Right () + + it "should respond to openapi request on none root path with 415" $ + request methodGet "/none_root_path" + (acceptHdrs "application/openapi+json") "" + `shouldRespondWith` 415 + describe "Table info" $ do it "The structure of complex views is correctly detected" $ request methodOptions "/filtered_tasks" [] "" `shouldRespondWith` diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index e837887fb..9e80f9e26 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -19,15 +19,15 @@ testDbConn = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_t testCfg :: AppConfig testCfg = - AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" "test" "localhost" 3000 (secret "safe") 10 Nothing True testUnicodeCfg :: AppConfig testUnicodeCfg = - AppConfig testDbConn "postgrest_test_anonymous" "تست" 3000 (secret "safe") 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" "تست" "localhost" 3000 (secret "safe") 10 Nothing True testLtdRowsCfg :: AppConfig testLtdRowsCfg = - AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 (Just 2) True + AppConfig testDbConn "postgrest_test_anonymous" "test" "localhost" 3000 (secret "safe") 10 (Just 2) True setupDb :: IO () setupDb = do diff --git a/test/fixtures/draft04.json b/test/fixtures/draft04.json new file mode 100644 index 000000000..fabd35473 --- /dev/null +++ b/test/fixtures/draft04.json @@ -0,0 +1,151 @@ +{ + "id": "draft04.json", + "$schema": "draft04.json", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/test/fixtures/openapi.json b/test/fixtures/openapi.json new file mode 100644 index 000000000..376db12e5 --- /dev/null +++ b/test/fixtures/openapi.json @@ -0,0 +1,1607 @@ +{ + "title": "A JSON Schema for Swagger 2.0 API.", + "id": "openapi.json", + "$schema": "draft04.json", + "type": "object", + "required": [ + "swagger", + "info", + "paths" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "swagger": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "The Swagger version of this document." + }, + "info": { + "$ref": "#/definitions/info" + }, + "host": { + "type": "string", + "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", + "description": "The host (name or ip) of the API. Example: 'swagger.io'" + }, + "basePath": { + "type": "string", + "pattern": "^/", + "description": "The base path to the API. Example: '/api'." + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "consumes": { + "description": "A list of MIME types accepted by the API.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "paths": { + "$ref": "#/definitions/paths" + }, + "definitions": { + "$ref": "#/definitions/definitions" + }, + "parameters": { + "$ref": "#/definitions/parameterDefinitions" + }, + "responses": { + "$ref": "#/definitions/responseDefinitions" + }, + "security": { + "$ref": "#/definitions/security" + }, + "securityDefinitions": { + "$ref": "#/definitions/securityDefinitions" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/tag" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "definitions": { + "info": { + "type": "object", + "description": "General information about the API.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." + }, + "termsOfService": { + "type": "string", + "description": "The terms of service for the API." + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + } + } + }, + "contact": { + "type": "object", + "description": "Contact information for the owners of the API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "license": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "paths": { + "type": "object", + "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + }, + "^/": { + "$ref": "#/definitions/pathItem" + } + }, + "additionalProperties": false + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "description": "One or more JSON objects describing the schemas being consumed and produced by the API." + }, + "parameterDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/parameter" + }, + "description": "One or more JSON representations for parameters" + }, + "responseDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/response" + }, + "description": "One or more JSON representations for parameters" + }, + "externalDocs": { + "type": "object", + "additionalProperties": false, + "description": "information about external documentation", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "examples": { + "type": "object", + "additionalProperties": true + }, + "mimeType": { + "type": "string", + "description": "The MIME type of the HTTP message." + }, + "operation": { + "type": "object", + "required": [ + "responses" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "operationId": { + "type": "string", + "description": "A unique identifier of the operation." + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "consumes": { + "description": "A list of MIME types the API can consume.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "parameters": { + "$ref": "#/definitions/parametersList" + }, + "responses": { + "$ref": "#/definitions/responses" + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "$ref": "#/definitions/security" + } + } + }, + "pathItem": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/operation" + }, + "put": { + "$ref": "#/definitions/operation" + }, + "post": { + "$ref": "#/definitions/operation" + }, + "delete": { + "$ref": "#/definitions/operation" + }, + "options": { + "$ref": "#/definitions/operation" + }, + "head": { + "$ref": "#/definitions/operation" + }, + "patch": { + "$ref": "#/definitions/operation" + }, + "parameters": { + "$ref": "#/definitions/parametersList" + } + } + }, + "responses": { + "type": "object", + "description": "Response objects names can either be any valid HTTP status code or 'default'.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^([0-9]{3})$|^(default)$": { + "$ref": "#/definitions/responseValue" + }, + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "not": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + } + }, + "responseValue": { + "oneOf": [ + { + "$ref": "#/definitions/response" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/fileSchema" + } + ] + }, + "headers": { + "$ref": "#/definitions/headers" + }, + "examples": { + "$ref": "#/definitions/examples" + } + }, + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/header" + } + }, + "header": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "vendorExtension": { + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "bodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "body" + ] + }, + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "schema": { + "$ref": "#/definitions/schema" + } + }, + "additionalProperties": false + }, + "headerParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "header" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "queryParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "query" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "formDataParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "formData" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array", + "file" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "pathParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ], + "description": "Determines whether or not this parameter is required or optional." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "path" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "nonBodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "type" + ], + "oneOf": [ + { + "$ref": "#/definitions/headerParameterSubSchema" + }, + { + "$ref": "#/definitions/formDataParameterSubSchema" + }, + { + "$ref": "#/definitions/queryParameterSubSchema" + }, + { + "$ref": "#/definitions/pathParameterSubSchema" + } + ] + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/bodyParameter" + }, + { + "$ref": "#/definitions/nonBodyParameter" + } + ] + }, + "schema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "format": { + "type": "string" + }, + "title": { + "$ref": "draft04.json#/properties/title" + }, + "description": { + "$ref": "draft04.json#/properties/description" + }, + "default": { + "$ref": "draft04.json#/properties/default" + }, + "multipleOf": { + "$ref": "draft04.json#/properties/multipleOf" + }, + "maximum": { + "$ref": "draft04.json#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "draft04.json#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "draft04.json#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "draft04.json#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "draft04.json#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "draft04.json#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "draft04.json#/properties/pattern" + }, + "maxItems": { + "$ref": "draft04.json#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "draft04.json#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "draft04.json#/properties/uniqueItems" + }, + "maxProperties": { + "$ref": "draft04.json#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "draft04.json#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "draft04.json#/definitions/stringArray" + }, + "enum": { + "$ref": "draft04.json#/properties/enum" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "type": { + "$ref": "draft04.json#/properties/type" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "default": {} + }, + "discriminator": { + "type": "string" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/xml" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "fileSchema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "type" + ], + "properties": { + "format": { + "type": "string" + }, + "title": { + "$ref": "draft04.json#/properties/title" + }, + "description": { + "$ref": "draft04.json#/properties/description" + }, + "default": { + "$ref": "draft04.json#/properties/default" + }, + "required": { + "$ref": "draft04.json#/definitions/stringArray" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "primitivesItems": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRequirement" + }, + "uniqueItems": true + }, + "securityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "tag": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "securityDefinitions": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuthenticationSecurity" + }, + { + "$ref": "#/definitions/apiKeySecurity" + }, + { + "$ref": "#/definitions/oauth2ImplicitSecurity" + }, + { + "$ref": "#/definitions/oauth2PasswordSecurity" + }, + { + "$ref": "#/definitions/oauth2ApplicationSecurity" + }, + { + "$ref": "#/definitions/oauth2AccessCodeSecurity" + } + ] + } + }, + "basicAuthenticationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "apiKeySecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ImplicitSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "implicit" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2PasswordSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "password" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ApplicationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "application" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2AccessCodeSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "accessCode" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2Scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mediaTypeList": { + "type": "array", + "items": { + "$ref": "#/definitions/mimeType" + }, + "uniqueItems": true + }, + "parametersList": { + "type": "array", + "description": "The parameters needed to send a valid API call.", + "additionalItems": false, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/parameter" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "uniqueItems": true + }, + "schemesList": { + "type": "array", + "description": "The transfer protocol of the API.", + "items": { + "type": "string", + "enum": [ + "http", + "https", + "ws", + "wss" + ] + }, + "uniqueItems": true + }, + "collectionFormat": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes" + ], + "default": "csv" + }, + "collectionFormatWithMulti": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes", + "multi" + ], + "default": "csv" + }, + "title": { + "$ref": "draft04.json#/properties/title" + }, + "description": { + "$ref": "draft04.json#/properties/description" + }, + "default": { + "$ref": "draft04.json#/properties/default" + }, + "multipleOf": { + "$ref": "draft04.json#/properties/multipleOf" + }, + "maximum": { + "$ref": "draft04.json#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "draft04.json#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "draft04.json#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "draft04.json#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "draft04.json#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "draft04.json#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "draft04.json#/properties/pattern" + }, + "maxItems": { + "$ref": "draft04.json#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "draft04.json#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "draft04.json#/properties/uniqueItems" + }, + "enum": { + "$ref": "draft04.json#/properties/enum" + }, + "jsonReference": { + "type": "object", + "required": [ + "$ref" + ], + "additionalProperties": false, + "properties": { + "$ref": { + "type": "string" + } + } + } + } +}