Merge pull request #706 from begriffs/func-descriptions

Include RPC endpoints in OpenAPI output
This commit is contained in:
Joe Nelson
2016-09-24 20:38:42 -07:00
committed by GitHub
8 changed files with 171 additions and 30 deletions
+1
View File
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Filtering, shaping and embedding with &select for the /rpc path - @ruslantalpa - Filtering, shaping and embedding with &select for the /rpc path - @ruslantalpa
- Output names of used-defined types (instead of 'USER-DEFINED') - @martingms - Output names of used-defined types (instead of 'USER-DEFINED') - @martingms
- Implement support for singular representation responses for POST/PATCH requests - @ehamberg - Implement support for singular representation responses for POST/PATCH requests - @ehamberg
- Include RPC endpoints in OpenAPI output - @begriffs, @LogvinovLeon
### Fixed ### Fixed
- Do not apply limit to parent items - @ruslantalpa - Do not apply limit to parent items - @ruslantalpa
+1
View File
@@ -151,6 +151,7 @@ Test-Suite spec
, SpecHelper , SpecHelper
, TestTypes , TestTypes
Build-Depends: aeson Build-Depends: aeson
, aeson-qq
, async , async
, base , base
, protolude , protolude
+5 -3
View File
@@ -213,7 +213,8 @@ app dbStructure conf apiRequest =
singular = iPreferSingular apiRequest singular = iPreferSingular apiRequest
jwtSecret = configJwtSecret conf jwtSecret = configJwtSecret conf
returnType = lookup (qiName qi) $ dbProcs dbStructure returnType = lookup (qiName qi) $ dbProcs dbStructure
returnsJWT = fromMaybe False $ isInfixOf "jwt_claims" <$> returnType returnsJWT = fromMaybe False $
isInfixOf "jwt_claims" . pdReturnType <$> returnType
serves [CTApplicationJSON] (iAccepts apiRequest) $ \_ -> case readSqlParts of serves [CTApplicationJSON] (iAccepts apiRequest) $ \_ -> case readSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ toS e Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (q,cq) -> respondToRange $ do Right (q,cq) -> respondToRange $ do
@@ -233,7 +234,7 @@ app dbStructure conf apiRequest =
uri Nothing = ("http", host, port, "/") uri Nothing = ("http", host, port, "/")
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b) uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
uri' = uri proxy uri' = uri proxy
encodeApi ti = encodeOpenAPI ti uri' encodeApi ti = encodeOpenAPI (map snd $ dbProcs dbStructure) ti uri'
serves [CTOpenAPI] (iAccepts apiRequest) $ \_ -> do serves [CTOpenAPI] (iAccepts apiRequest) $ \_ -> do
body <- encodeApi . toTableInfo <$> H.query schema accessibleTables body <- encodeApi . toTableInfo <$> H.query schema accessibleTables
return $ responseLBS status200 [openapiH] $ toS body return $ responseLBS status200 [openapiH] $ toS body
@@ -269,7 +270,8 @@ app dbStructure conf apiRequest =
schema = toS $ configSchema conf schema = toS $ configSchema conf
shouldCount = iPreferCount apiRequest shouldCount = iPreferCount apiRequest
topLevelRange = fromMaybe allRange $ M.lookup "limit" $ iRange apiRequest topLevelRange = fromMaybe allRange $ M.lookup "limit" $ iRange apiRequest
readDbRequest = DbRead <$> buildReadRequest (configMaxRows conf) (dbRelations dbStructure) (dbProcs dbStructure) apiRequest mapSnd f (a, b) = (a, f b)
readDbRequest = DbRead <$> buildReadRequest (configMaxRows conf) (dbRelations dbStructure) (map (mapSnd pdReturnType) $ dbProcs dbStructure) apiRequest
mutateDbRequest = DbMutate <$> buildMutateRequest apiRequest mutateDbRequest = DbMutate <$> buildMutateRequest apiRequest
selectQuery = requestToQuery schema False <$> readDbRequest selectQuery = requestToQuery schema False <$> readDbRequest
countQuery = requestToCountQuery schema <$> readDbRequest countQuery = requestToCountQuery schema <$> readDbRequest
+26 -4
View File
@@ -16,7 +16,9 @@ import Control.Applicative
import Data.List (elemIndex) import Data.List (elemIndex)
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Data.Monoid import Data.Monoid
import Data.Text (split) import Data.Text (split, strip,
breakOn, dropAround)
import qualified Data.Text as T
import qualified Hasql.Session as H import qualified Hasql.Session as H
import PostgREST.Types import PostgREST.Types
import Text.InterpolatedString.Perl6 (q) import Text.InterpolatedString.Perl6 (q)
@@ -95,12 +97,32 @@ decodeSynonyms cols =
<*> HD.value HD.text <*> HD.value HD.text <*> HD.value HD.text <*> HD.value HD.text
<*> HD.value HD.text <*> HD.value HD.text <*> HD.value HD.text <*> HD.value HD.text
accessibleProcs :: H.Query Schema [(Text, Text)] accessibleProcs :: H.Query Schema [(Text, ProcDescription)]
accessibleProcs = accessibleProcs =
H.statement sql (HE.value HE.text) (HD.rowsList ((,) <$> HD.value HD.text <*> HD.value HD.text)) True H.statement sql (HE.value HE.text)
(map addName <$> HD.rowsList (ProcDescription <$> HD.value HD.text
<*> (parseArgs <$> HD.value HD.text)
<*> HD.value HD.text)) True
where where
addName :: ProcDescription -> (Text, ProcDescription)
addName pd = (pdName pd, pd)
parseArgs :: Text -> [PgArg]
parseArgs = mapMaybe (parseArg . strip) . split (==',')
parseArg :: Text -> Maybe PgArg
parseArg a =
let (body, def) = breakOn " DEFAULT " a
(name, typ) = breakOn " " body in
if T.null typ
then Nothing
else Just $
PgArg (dropAround (== '"') name) (strip typ) (T.null def)
sql = [q| sql = [q|
SELECT p.proname as "proc_name", pg_get_function_result(p.oid) as "return_type" SELECT p.proname as "proc_name",
pg_get_function_arguments(p.oid) as "args",
pg_get_function_result(p.oid) as "return_type"
FROM pg_namespace n FROM pg_namespace n
JOIN pg_proc p JOIN pg_proc p
ON pronamespace = n.oid ON pronamespace = n.oid
+48 -21
View File
@@ -23,8 +23,8 @@ import Data.Swagger
import PostgREST.ApiRequest (ContentType(..), toHeader) import PostgREST.ApiRequest (ContentType(..), toHeader)
import PostgREST.Config (prettyVersion) import PostgREST.Config (prettyVersion)
import PostgREST.QueryBuilder (operators) import PostgREST.QueryBuilder (operators)
import PostgREST.Types (Table(..), Column(..), import PostgREST.Types (Table(..), Column(..), PgArg(..),
Proxy(..)) Proxy(..), ProcDescription(..))
makeMimeList :: [ContentType] -> MimeList makeMimeList :: [ContentType] -> MimeList
makeMimeList cs = MimeList $ map (fromString . toS . toHeader) cs makeMimeList cs = MimeList $ map (fromString . toS . toHeader) cs
@@ -36,6 +36,13 @@ toSwaggerType "boolean" = SwaggerBoolean
toSwaggerType "numeric" = SwaggerNumber toSwaggerType "numeric" = SwaggerNumber
toSwaggerType _ = SwaggerString toSwaggerType _ = SwaggerString
makeTableDef :: (Table, [Column], [Text]) -> (Text, Schema)
makeTableDef (t, cs, _) =
let tn = tableName t in
(tn, (mempty :: Schema)
& type_ .~ SwaggerObject
& properties .~ fromList (map makeProperty cs))
makeProperty :: Column -> (Text, Referenced Schema) makeProperty :: Column -> (Text, Referenced Schema)
makeProperty c = (colName c, Inline u) makeProperty c = (colName c, Inline u)
where where
@@ -46,18 +53,20 @@ makeProperty c = (colName c, Inline u)
t = s & type_ .~ toSwaggerType (colType c) t = s & type_ .~ toSwaggerType (colType c)
u = t & format ?~ colType c u = t & format ?~ colType c
makeProperties :: [Column] -> InsOrdHashMap Text (Referenced Schema) makeProcDef :: ProcDescription -> (Text, Schema)
makeProperties cs = fromList $ map makeProperty cs makeProcDef pd = ("(rpc) " <> pdName pd, s)
where
s = (mempty :: Schema)
& type_ .~ SwaggerObject
& properties .~ fromList (map makeProcProperty (pdArgs pd))
& required .~ map pgaName (filter pgaReq (pdArgs pd))
makeDefinition :: (Table, [Column], [Text]) -> (Text, Schema) makeProcProperty :: PgArg -> (Text, Referenced Schema)
makeDefinition (t, cs, _) = makeProcProperty (PgArg n t _) = (n, Inline s)
let tn = tableName t in where
(tn, (mempty :: Schema) s = (mempty :: Schema)
& type_ .~ SwaggerObject & type_ .~ toSwaggerType t
& properties .~ makeProperties cs) & format ?~ t
makeDefinitions :: [(Table, [Column], [Text])] -> InsOrdHashMap Text Schema
makeDefinitions ti = fromList $ map makeDefinition ti
makeOperatorPattern :: Text makeOperatorPattern :: Text
makeOperatorPattern = makeOperatorPattern =
@@ -173,6 +182,13 @@ makePostParams tn =
& schema .~ ParamBody (Ref (Reference tn)) & schema .~ ParamBody (Ref (Reference tn))
] ]
makeProcParam :: Text -> Param
makeProcParam refName =
(mempty :: Param)
& name .~ "args"
& required ?~ True
& schema .~ ParamBody (Ref (Reference refName))
makeDeleteParams :: [Param] makeDeleteParams :: [Param]
makeDeleteParams = makeDeleteParams =
[ makePreferParam ["return=representation", "return=minimal", "return=none"] ] [ makePreferParam ["return=representation", "return=minimal", "return=none"] ]
@@ -204,6 +220,16 @@ makePathItem (t, cs, _) = ("/" ++ unpack tn, p $ tableInsertable t)
rs = makeRowFilters cs rs = makeRowFilters cs
tn = tableName t tn = tableName t
makeProcPathItem :: ProcDescription -> (FilePath, PathItem)
makeProcPathItem pd = ("/rpc/" ++ toS (pdName pd), pe)
where
postOp = (mempty :: Operation)
& parameters .~ [Inline (makeProcParam $ "(rpc) " <> pdName pd)]
& tags .~ Set.fromList ["(rpc) " <> pdName pd]
& produces ?~ makeMimeList [CTApplicationJSON]
& at 200 ?~ "OK"
pe = (mempty :: PathItem) & post ?~ postOp
makeRootPathItem :: (FilePath, PathItem) makeRootPathItem :: (FilePath, PathItem)
makeRootPathItem = ("/", p) makeRootPathItem = ("/", p)
where where
@@ -214,8 +240,9 @@ makeRootPathItem = ("/", p)
pr = (mempty :: PathItem) & get ?~ getOp pr = (mempty :: PathItem) & get ?~ getOp
p = pr p = pr
makePathItems :: [(Table, [Column], [Text])] -> InsOrdHashMap FilePath PathItem makePathItems :: [ProcDescription] -> [(Table, [Column], [Text])] -> InsOrdHashMap FilePath PathItem
makePathItems ti = fromList $ makeRootPathItem : map makePathItem ti makePathItems pds ti = fromList $ makeRootPathItem :
map makePathItem ti ++ map makeProcPathItem pds
escapeHostName :: Text -> Text escapeHostName :: Text -> Text
escapeHostName "*" = "0.0.0.0" escapeHostName "*" = "0.0.0.0"
@@ -225,8 +252,8 @@ escapeHostName "*6" = "0.0.0.0"
escapeHostName "!6" = "0.0.0.0" escapeHostName "!6" = "0.0.0.0"
escapeHostName h = h escapeHostName h = h
postgrestSpec:: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> Swagger postgrestSpec :: [ProcDescription] -> [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> Swagger
postgrestSpec ti (s, h, p, b) = (mempty :: Swagger) postgrestSpec pds ti (s, h, p, b) = (mempty :: Swagger)
& basePath ?~ unpack b & basePath ?~ unpack b
& schemes ?~ [s'] & schemes ?~ [s']
& info .~ ((mempty :: Info) & info .~ ((mempty :: Info)
@@ -234,14 +261,14 @@ postgrestSpec ti (s, h, p, b) = (mempty :: Swagger)
& title .~ "PostgREST API" & title .~ "PostgREST API"
& description ?~ "This is a dynamic API generated by PostgREST") & description ?~ "This is a dynamic API generated by PostgREST")
& host .~ h' & host .~ h'
& definitions .~ makeDefinitions ti & definitions .~ fromList (map makeTableDef ti <> map makeProcDef pds)
& paths .~ makePathItems ti & paths .~ makePathItems pds ti
where where
s' = if s == "http" then Http else Https s' = if s == "http" then Http else Https
h' = Just $ Host (unpack $ escapeHostName h) (Just (fromInteger p)) h' = Just $ Host (unpack $ escapeHostName h) (Just (fromInteger p))
encodeOpenAPI :: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> LByteString encodeOpenAPI :: [ProcDescription] -> [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> LByteString
encodeOpenAPI ti uri = encode $ postgrestSpec ti uri encodeOpenAPI pds ti uri = encode $ postgrestSpec pds ti uri
{-| {-|
Test whether a proxy uri is malformed or not. Test whether a proxy uri is malformed or not.
+13 -1
View File
@@ -13,7 +13,19 @@ data DbStructure = DbStructure {
, dbColumns :: [Column] , dbColumns :: [Column]
, dbRelations :: [Relation] , dbRelations :: [Relation]
, dbPrimaryKeys :: [PrimaryKey] , dbPrimaryKeys :: [PrimaryKey]
, dbProcs :: [(Text,Text)] , dbProcs :: [(Text,ProcDescription)]
} deriving (Show, Eq)
data PgArg = PgArg {
pgaName :: Text
, pgaType :: Text
, pgaReq :: Bool
} deriving (Show, Eq)
data ProcDescription = ProcDescription {
pdName :: Text
, pdArgs :: [PgArg]
, pdReturnType :: Text
} deriving (Show, Eq) } deriving (Show, Eq)
type Schema = Text type Schema = Text
+62 -1
View File
@@ -4,10 +4,14 @@ import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import Network.HTTP.Types import Network.HTTP.Types
import Control.Lens ((^?))
import Data.Aeson.Lens
import Data.Aeson.QQ
import SpecHelper import SpecHelper
import Network.Wai (Application) import Network.Wai (Application)
import Network.Wai.Test (SResponse(simpleHeaders)) import Network.Wai.Test (SResponse(..))
spec :: SpecWith Application spec :: SpecWith Application
spec = do spec = do
@@ -21,6 +25,63 @@ spec = do
(acceptHdrs "application/openapi+json") "" (acceptHdrs "application/openapi+json") ""
`shouldRespondWith` 415 `shouldRespondWith` 415
describe "RPC" $
it "includes a representative function with parameters" $ do
r <- simpleBody <$> get "/"
let ref = r ^? key "paths" . key "/rpc/varied_arguments"
. key "post" . key "parameters"
. nth 0 . key "schema"
. key "$ref" . _String
args = r ^? key "definitions" . key "(rpc) varied_arguments"
liftIO $ do
ref `shouldBe` Just "#/definitions/(rpc) varied_arguments"
args `shouldBe` Just
[aesonQQ|
{
"required": [
"double",
"varchar",
"boolean",
"date",
"money",
"enum"
],
"properties": {
"double": {
"format": "double precision",
"type": "string"
},
"varchar": {
"format": "character varying",
"type": "string"
},
"boolean": {
"format": "boolean",
"type": "boolean"
},
"date": {
"format": "date",
"type": "string"
},
"money": {
"format": "money",
"type": "string"
},
"enum": {
"format": "test.enum_menagerie_type",
"type": "string"
},
"integer": {
"format": "integer",
"type": "integer"
}
},
"type": "object"
}
|]
describe "Allow header" $ do describe "Allow header" $ do
it "includes read/write verbs for writeable table" $ do it "includes read/write verbs for writeable table" $ do
+15
View File
@@ -215,6 +215,21 @@ SELECT rolname::text, id::text FROM postgrest.auth WHERE id = id AND pass = pass
$$; $$;
CREATE FUNCTION varied_arguments(
double double precision,
"varchar" character varying,
"boolean" boolean,
date date,
money money,
enum enum_menagerie_type,
"integer" integer default 42
) RETURNS text
LANGUAGE sql
AS $_$
SELECT 'Hi'::text;
$_$;
-- --
-- Name: jwt_test(); Type: FUNCTION; Schema: test; Owner: - -- Name: jwt_test(); Type: FUNCTION; Schema: test; Owner: -
-- --