From d1ed884a8ddf9a9a1176b78327a8b1efc16eab17 Mon Sep 17 00:00:00 2001 From: Jacky Hu Date: Wed, 15 Jun 2016 12:06:51 +0800 Subject: [PATCH] Introduce the OpenAPI header Also bring back the original behavior of GET "/" --- postgrest.cabal | 3 --- src/PostgREST/ApiRequest.hs | 7 ++++- src/PostgREST/App.hs | 4 ++- src/PostgREST/OpenAPI.hs | 14 +++++----- stack.yaml | 1 - test/Feature/StructureSpec.hs | 48 +++++++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index f7f77a745..0735657a0 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -66,7 +66,6 @@ executable postgrest , wai-middleware-static >= 0.6.0 , warp >= 3.1.0 , insert-ordered-containers >= 0.1.0.1 - , http-media >= 0.6.3 , swagger2 >= 2.1 , HTTP , Ranged-sets @@ -113,7 +112,6 @@ library , wai-middleware-static >= 0.6.0 , warp >= 3.1.0 , insert-ordered-containers >= 0.1.0.1 - , http-media >= 0.6.3 , swagger2 >= 2.1 Other-Modules: Paths_postgrest @@ -194,7 +192,6 @@ Test-Suite spec , wai-middleware-static , warp , insert-ordered-containers - , http-media , swagger2 , HTTP , Ranged-sets diff --git a/src/PostgREST/ApiRequest.hs b/src/PostgREST/ApiRequest.hs index 02fa5da37..c1da04df7 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 OpenAPI -> + PayloadParseError "Content-type not acceptable" -- 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 7ed46136f..ca3ea19c7 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -204,7 +204,9 @@ app dbStructure conf apiRequest = else return notFound (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 (ActionInappropriate, _, _) -> return $ responseLBS status405 [] "" diff --git a/src/PostgREST/OpenAPI.hs b/src/PostgREST/OpenAPI.hs index 6d8c3a0e1..58d85f236 100644 --- a/src/PostgREST/OpenAPI.hs +++ b/src/PostgREST/OpenAPI.hs @@ -10,7 +10,6 @@ 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 Network.HTTP.Media (MediaType) import qualified Data.Set as Set import Prelude hiding (concat) @@ -22,8 +21,8 @@ import PostgREST.Config (prettyVersion) import PostgREST.QueryBuilder (operators) import PostgREST.Types (Table(..), Column(..)) -makeMimeList :: [MediaType] -makeMimeList = map (fromString . show) [ApplicationJSON, TextCSV] +makeMimeList :: [ContentType] -> MimeList +makeMimeList cs = MimeList $ map (fromString . show) cs toSwaggerType :: Text -> SwaggerType t toSwaggerType "text" = SwaggerString @@ -168,15 +167,15 @@ makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t) where tOp = (mempty :: Operation) & tags .~ Set.fromList [tn] - & produces ?~ MimeList makeMimeList + & produces ?~ makeMimeList [ApplicationJSON, TextCSV] & at 200 ?~ "OK" getOp = tOp & parameters .~ map Inline (makeGetParams cs ++ rs) postOp = tOp - & consumes ?~ MimeList makeMimeList + & consumes ?~ makeMimeList [ApplicationJSON, TextCSV] & parameters .~ map Inline (makePostParams tn) patchOp = tOp - & consumes ?~ MimeList makeMimeList + & consumes ?~ makeMimeList [ApplicationJSON, TextCSV] & parameters .~ map Inline (makePostParams tn ++ rs) deletOp = tOp & parameters .~ map Inline (makeDeleteParams ++ rs) @@ -192,7 +191,8 @@ makeRootPathItem = ("/", p) where getOp = (mempty :: Operation) & tags .~ Set.fromList ["/"] - & produces ?~ MimeList [(fromString . show) ApplicationJSON] + & produces ?~ makeMimeList [ApplicationJSON, OpenAPI] + & consumes ?~ makeMimeList [ApplicationJSON, OpenAPI] & at 200 ?~ "OK" pr = (mempty :: PathItem) & get ?~ getOp p = pr diff --git a/stack.yaml b/stack.yaml index 8ba4393b0..318c8ce61 100644 --- a/stack.yaml +++ b/stack.yaml @@ -19,7 +19,6 @@ extra-deps: - unordered-containers-0.2.7.1 - insert-ordered-containers-0.1.0.1 - swagger2-2.1 - - http-media-0.6.3 ghc-options: postgrest: -O2 -Werror -Wall -fwarn-identities diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index 8abe90e9c..3d58947ac 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -13,6 +13,54 @@ import Network.Wai.Test (SResponse(simpleHeaders)) spec :: SpecWith Application 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 it "The structure of complex views is correctly detected" $ request methodOptions "/filtered_tasks" [] "" `shouldRespondWith`