Introduce the OpenAPI header
Also bring back the original behavior of GET "/"
This commit is contained in:
@@ -66,7 +66,6 @@ executable postgrest
|
|||||||
, wai-middleware-static >= 0.6.0
|
, wai-middleware-static >= 0.6.0
|
||||||
, warp >= 3.1.0
|
, warp >= 3.1.0
|
||||||
, insert-ordered-containers >= 0.1.0.1
|
, insert-ordered-containers >= 0.1.0.1
|
||||||
, http-media >= 0.6.3
|
|
||||||
, swagger2 >= 2.1
|
, swagger2 >= 2.1
|
||||||
, HTTP
|
, HTTP
|
||||||
, Ranged-sets
|
, Ranged-sets
|
||||||
@@ -113,7 +112,6 @@ library
|
|||||||
, wai-middleware-static >= 0.6.0
|
, wai-middleware-static >= 0.6.0
|
||||||
, warp >= 3.1.0
|
, warp >= 3.1.0
|
||||||
, insert-ordered-containers >= 0.1.0.1
|
, insert-ordered-containers >= 0.1.0.1
|
||||||
, http-media >= 0.6.3
|
|
||||||
, swagger2 >= 2.1
|
, swagger2 >= 2.1
|
||||||
|
|
||||||
Other-Modules: Paths_postgrest
|
Other-Modules: Paths_postgrest
|
||||||
@@ -194,7 +192,6 @@ Test-Suite spec
|
|||||||
, wai-middleware-static
|
, wai-middleware-static
|
||||||
, warp
|
, warp
|
||||||
, insert-ordered-containers
|
, insert-ordered-containers
|
||||||
, http-media
|
|
||||||
, swagger2
|
, swagger2
|
||||||
, HTTP
|
, HTTP
|
||||||
, Ranged-sets
|
, Ranged-sets
|
||||||
|
|||||||
@@ -45,10 +45,11 @@ data Target = TargetIdent QualifiedIdentifier
|
|||||||
data PreferRepresentation = Full | HeadersOnly | None deriving Eq
|
data PreferRepresentation = Full | HeadersOnly | None deriving Eq
|
||||||
-- | Enumeration of currently supported content types for
|
-- | Enumeration of currently supported content types for
|
||||||
-- route responses and upload payloads
|
-- route responses and upload payloads
|
||||||
data ContentType = ApplicationJSON | TextCSV deriving Eq
|
data ContentType = ApplicationJSON | TextCSV | OpenAPI deriving Eq
|
||||||
instance Show ContentType where
|
instance Show ContentType where
|
||||||
show ApplicationJSON = "application/json; charset=utf-8"
|
show ApplicationJSON = "application/json; charset=utf-8"
|
||||||
show TextCSV = "text/csv; 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
|
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"
|
Nothing -> PayloadParseError "All lines must have same number of fields"
|
||||||
Just json -> PayloadJSON json)
|
Just json -> PayloadJSON json)
|
||||||
(CSV.decodeByName reqBody)
|
(CSV.decodeByName reqBody)
|
||||||
|
Right OpenAPI ->
|
||||||
|
PayloadParseError "Content-type not acceptable"
|
||||||
-- This is a Left value because form-urlencoded is not a content
|
-- This is a Left value because form-urlencoded is not a content
|
||||||
-- type which we ever use for responses, only something we handle
|
-- type which we ever use for responses, only something we handle
|
||||||
-- just this once for requests
|
-- just this once for requests
|
||||||
@@ -208,11 +211,13 @@ pickContentType :: Maybe BS.ByteString -> Either BS.ByteString ContentType
|
|||||||
pickContentType accept
|
pickContentType accept
|
||||||
| isNothing accept || has ctAll || has ctJson = Right ApplicationJSON
|
| isNothing accept || has ctAll || has ctJson = Right ApplicationJSON
|
||||||
| has ctCsv = Right TextCSV
|
| has ctCsv = Right TextCSV
|
||||||
|
| has ctOpenAPI = Right OpenAPI
|
||||||
| otherwise = Left accept'
|
| otherwise = Left accept'
|
||||||
where
|
where
|
||||||
ctAll = "*/*"
|
ctAll = "*/*"
|
||||||
ctCsv = "text/csv"
|
ctCsv = "text/csv"
|
||||||
ctJson = "application/json"
|
ctJson = "application/json"
|
||||||
|
ctOpenAPI = "application/openapi+json"
|
||||||
Just accept' = accept
|
Just accept' = accept
|
||||||
findInAccept = flip find $ parseHttpAccept accept'
|
findInAccept = flip find $ parseHttpAccept accept'
|
||||||
has = isJust . findInAccept . BS.isPrefixOf
|
has = isJust . findInAccept . BS.isPrefixOf
|
||||||
|
|||||||
@@ -204,7 +204,9 @@ app dbStructure conf apiRequest =
|
|||||||
else return notFound
|
else return notFound
|
||||||
|
|
||||||
(ActionRead, TargetRoot, Nothing) -> do
|
(ActionRead, TargetRoot, Nothing) -> do
|
||||||
body <- (encodeApi . toTableInfo) <$> H.query schema accessibleTables
|
body <- if contentType == OpenAPI
|
||||||
|
then (encodeApi . toTableInfo) <$> H.query schema accessibleTables
|
||||||
|
else encode <$> H.query schema accessibleTables
|
||||||
return $ responseLBS status200 [jsonH] $ cs body
|
return $ responseLBS status200 [jsonH] $ cs body
|
||||||
|
|
||||||
(ActionInappropriate, _, _) -> return $ responseLBS status405 [] ""
|
(ActionInappropriate, _, _) -> return $ responseLBS status405 [] ""
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import Data.ByteString.Lazy (ByteString)
|
|||||||
import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList)
|
import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Data.Text (Text, unpack, pack, concat, intercalate)
|
import Data.Text (Text, unpack, pack, concat, intercalate)
|
||||||
import Network.HTTP.Media (MediaType)
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
import Prelude hiding (concat)
|
import Prelude hiding (concat)
|
||||||
@@ -22,8 +21,8 @@ import PostgREST.Config (prettyVersion)
|
|||||||
import PostgREST.QueryBuilder (operators)
|
import PostgREST.QueryBuilder (operators)
|
||||||
import PostgREST.Types (Table(..), Column(..))
|
import PostgREST.Types (Table(..), Column(..))
|
||||||
|
|
||||||
makeMimeList :: [MediaType]
|
makeMimeList :: [ContentType] -> MimeList
|
||||||
makeMimeList = map (fromString . show) [ApplicationJSON, TextCSV]
|
makeMimeList cs = MimeList $ map (fromString . show) cs
|
||||||
|
|
||||||
toSwaggerType :: Text -> SwaggerType t
|
toSwaggerType :: Text -> SwaggerType t
|
||||||
toSwaggerType "text" = SwaggerString
|
toSwaggerType "text" = SwaggerString
|
||||||
@@ -168,15 +167,15 @@ makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t)
|
|||||||
where
|
where
|
||||||
tOp = (mempty :: Operation)
|
tOp = (mempty :: Operation)
|
||||||
& tags .~ Set.fromList [tn]
|
& tags .~ Set.fromList [tn]
|
||||||
& produces ?~ MimeList makeMimeList
|
& produces ?~ makeMimeList [ApplicationJSON, TextCSV]
|
||||||
& at 200 ?~ "OK"
|
& at 200 ?~ "OK"
|
||||||
getOp = tOp
|
getOp = tOp
|
||||||
& parameters .~ map Inline (makeGetParams cs ++ rs)
|
& parameters .~ map Inline (makeGetParams cs ++ rs)
|
||||||
postOp = tOp
|
postOp = tOp
|
||||||
& consumes ?~ MimeList makeMimeList
|
& consumes ?~ makeMimeList [ApplicationJSON, TextCSV]
|
||||||
& parameters .~ map Inline (makePostParams tn)
|
& parameters .~ map Inline (makePostParams tn)
|
||||||
patchOp = tOp
|
patchOp = tOp
|
||||||
& consumes ?~ MimeList makeMimeList
|
& consumes ?~ makeMimeList [ApplicationJSON, TextCSV]
|
||||||
& parameters .~ map Inline (makePostParams tn ++ rs)
|
& parameters .~ map Inline (makePostParams tn ++ rs)
|
||||||
deletOp = tOp
|
deletOp = tOp
|
||||||
& parameters .~ map Inline (makeDeleteParams ++ rs)
|
& parameters .~ map Inline (makeDeleteParams ++ rs)
|
||||||
@@ -192,7 +191,8 @@ makeRootPathItem = ("/", p)
|
|||||||
where
|
where
|
||||||
getOp = (mempty :: Operation)
|
getOp = (mempty :: Operation)
|
||||||
& tags .~ Set.fromList ["/"]
|
& tags .~ Set.fromList ["/"]
|
||||||
& produces ?~ MimeList [(fromString . show) ApplicationJSON]
|
& produces ?~ makeMimeList [ApplicationJSON, OpenAPI]
|
||||||
|
& consumes ?~ makeMimeList [ApplicationJSON, OpenAPI]
|
||||||
& at 200 ?~ "OK"
|
& at 200 ?~ "OK"
|
||||||
pr = (mempty :: PathItem) & get ?~ getOp
|
pr = (mempty :: PathItem) & get ?~ getOp
|
||||||
p = pr
|
p = pr
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ extra-deps:
|
|||||||
- unordered-containers-0.2.7.1
|
- unordered-containers-0.2.7.1
|
||||||
- insert-ordered-containers-0.1.0.1
|
- insert-ordered-containers-0.1.0.1
|
||||||
- swagger2-2.1
|
- swagger2-2.1
|
||||||
- http-media-0.6.3
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
postgrest: -O2 -Werror -Wall -fwarn-identities
|
postgrest: -O2 -Werror -Wall -fwarn-identities
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,54 @@ import Network.Wai.Test (SResponse(simpleHeaders))
|
|||||||
spec :: SpecWith Application
|
spec :: SpecWith Application
|
||||||
spec = do
|
spec = do
|
||||||
|
|
||||||
|
describe "GET /" $ do
|
||||||
|
it "lists views in schema" $
|
||||||
|
request methodGet "/" [] ""
|
||||||
|
`shouldRespondWith` [json| [
|
||||||
|
{"schema":"test","name":"Escap3e;","insertable":true}
|
||||||
|
, {"schema":"test","name":"addresses","insertable":true}
|
||||||
|
, {"schema":"test","name":"articleStars","insertable":true}
|
||||||
|
, {"schema":"test","name":"articles","insertable":true}
|
||||||
|
, {"schema":"test","name":"auto_incrementing_pk","insertable":true}
|
||||||
|
, {"schema":"test","name":"clients","insertable":true}
|
||||||
|
, {"schema":"test","name":"comments","insertable":true}
|
||||||
|
, {"schema":"test","name":"complex_items","insertable":true}
|
||||||
|
, {"schema":"test","name":"compound_pk","insertable":true}
|
||||||
|
, {"schema":"test","name":"empty_table","insertable":true}
|
||||||
|
, {"schema":"test","name":"filtered_tasks","insertable":true}
|
||||||
|
, {"schema":"test","name":"ghostBusters","insertable":true}
|
||||||
|
, {"schema":"test","name":"has_count_column","insertable":false}
|
||||||
|
, {"schema":"test","name":"has_fk","insertable":true}
|
||||||
|
, {"schema":"test","name":"insertable_view_with_join","insertable":true}
|
||||||
|
, {"schema":"test","name":"insertonly","insertable":true}
|
||||||
|
, {"schema":"test","name":"items","insertable":true}
|
||||||
|
, {"schema":"test","name":"json","insertable":true}
|
||||||
|
, {"schema":"test","name":"materialized_view","insertable":false}
|
||||||
|
, {"schema":"test","name":"menagerie","insertable":true}
|
||||||
|
, {"schema":"test","name":"no_pk","insertable":true}
|
||||||
|
, {"schema":"test","name":"nullable_integer","insertable":true}
|
||||||
|
, {"schema":"test","name":"orders","insertable":true}
|
||||||
|
, {"schema":"test","name":"projects","insertable":true}
|
||||||
|
, {"schema":"test","name":"projects_view","insertable":true}
|
||||||
|
, {"schema":"test","name":"simple_pk","insertable":true}
|
||||||
|
, {"schema":"test","name":"tasks","insertable":true}
|
||||||
|
, {"schema":"test","name":"tsearch","insertable":true}
|
||||||
|
, {"schema":"test","name":"users","insertable":true}
|
||||||
|
, {"schema":"test","name":"users_projects","insertable":true}
|
||||||
|
, {"schema":"test","name":"users_tasks","insertable":true}
|
||||||
|
, {"schema":"test","name":"withUnique","insertable":true}
|
||||||
|
] |]
|
||||||
|
{matchStatus = 200}
|
||||||
|
|
||||||
|
it "lists only views user has permission to see" $ do
|
||||||
|
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
|
||||||
|
|
||||||
|
request methodGet "/" [auth] ""
|
||||||
|
`shouldRespondWith` [json| [
|
||||||
|
{"schema":"test","name":"authors_only","insertable":true}
|
||||||
|
] |]
|
||||||
|
{matchStatus = 200}
|
||||||
|
|
||||||
describe "Table info" $ do
|
describe "Table info" $ do
|
||||||
it "The structure of complex views is correctly detected" $
|
it "The structure of complex views is correctly detected" $
|
||||||
request methodOptions "/filtered_tasks" [] "" `shouldRespondWith`
|
request methodOptions "/filtered_tasks" [] "" `shouldRespondWith`
|
||||||
|
|||||||
Reference in New Issue
Block a user