Fix some OpenAPI issues (#970)
* Fix #933, update externals docs url to current version * Fix #962, openApi don't err on nonexistent schema * Fix #954, make OpenAPI rpc output dependent on user privileges
This commit is contained in:
@@ -268,8 +268,8 @@ app dbStructure conf apiRequest =
|
||||
uri Nothing = ("http", host, port, "/")
|
||||
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
|
||||
uri' = uri proxy
|
||||
encodeApi ti sd = encodeOpenAPI (M.elems allProcs) (toTableInfo ti) uri' sd (dbPrimaryKeys dbStructure)
|
||||
body <- encodeApi <$> H.query schema accessibleTables <*> H.query schema schemaDescription
|
||||
encodeApi ti sd procs = encodeOpenAPI (M.elems procs) (toTableInfo ti) uri' sd (dbPrimaryKeys dbStructure)
|
||||
body <- encodeApi <$> H.query schema accessibleTables <*> H.query schema schemaDescription <*> H.query schema accessibleProcs
|
||||
return $ responseLBS status200 [toHeader CTOpenAPI] $ toS body
|
||||
|
||||
_ -> return notFound
|
||||
|
||||
@@ -14,6 +14,7 @@ turned in configurable behaviour if needed.
|
||||
Other hardcoded options such as the minimum version number also belong here.
|
||||
-}
|
||||
module PostgREST.Config ( prettyVersion
|
||||
, docsVersion
|
||||
, readOptions
|
||||
, corsPolicy
|
||||
, minimumPgVersion
|
||||
@@ -33,7 +34,7 @@ import Data.Configurator.Types (Value(..))
|
||||
import Data.List (lookup)
|
||||
import Data.Monoid
|
||||
import Data.Scientific (floatingOrInteger)
|
||||
import Data.Text (strip, intercalate, lines)
|
||||
import Data.Text (strip, intercalate, lines, dropAround)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Data.Text.IO (hPutStrLn)
|
||||
import Data.Version (versionBranch)
|
||||
@@ -92,6 +93,10 @@ corsPolicy req = case lookup "origin" headers of
|
||||
prettyVersion :: Text
|
||||
prettyVersion = intercalate "." $ map show $ versionBranch version
|
||||
|
||||
-- | Version number used in docs
|
||||
docsVersion :: Text
|
||||
docsVersion = "v" <> dropAround (== '.') (dropAround (/= '.') prettyVersion)
|
||||
|
||||
-- | Function to read and parse options from the command line
|
||||
readOptions :: IO AppConfig
|
||||
readOptions = do
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
module PostgREST.DbStructure (
|
||||
getDbStructure
|
||||
, accessibleTables
|
||||
, accessibleProcs
|
||||
, schemaDescription
|
||||
) where
|
||||
|
||||
@@ -35,7 +36,7 @@ getDbStructure schema = do
|
||||
syns <- H.query () $ allSynonyms cols
|
||||
rels <- H.query () $ allRelations tabs cols
|
||||
keys <- H.query () $ allPrimaryKeys tabs
|
||||
procs <- H.query schema accessibleProcs
|
||||
procs <- H.query schema allProcs
|
||||
|
||||
let rels' = (addManyToManyRelations . raiseRelations schema syns . addParentRelations . addSynonymousRelations syns) rels
|
||||
cols' = addForeignKeys rels' cols
|
||||
@@ -100,59 +101,64 @@ decodeSynonyms cols =
|
||||
<*> HD.value HD.text <*> HD.value HD.text
|
||||
<*> HD.value HD.text <*> HD.value HD.text
|
||||
|
||||
decodeProcs :: HD.Result (M.HashMap Text ProcDescription)
|
||||
decodeProcs =
|
||||
M.fromList . map addName <$> HD.rowsList tblRow
|
||||
where
|
||||
tblRow = ProcDescription
|
||||
<$> HD.value HD.text
|
||||
<*> HD.nullableValue HD.text
|
||||
<*> (parseArgs <$> HD.value HD.text)
|
||||
<*> (parseRetType
|
||||
<$> HD.value HD.text
|
||||
<*> HD.value HD.text
|
||||
<*> HD.value HD.bool
|
||||
<*> HD.value HD.char)
|
||||
<*> (parseVolatility <$> HD.value HD.char)
|
||||
|
||||
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)
|
||||
|
||||
parseRetType :: Text -> Text -> Bool -> Char -> RetType
|
||||
parseRetType schema name isSetOf typ
|
||||
| isSetOf = SetOf pgType
|
||||
| otherwise = Single pgType
|
||||
where
|
||||
qi = QualifiedIdentifier schema name
|
||||
pgType = case typ of
|
||||
'c' -> Composite qi
|
||||
'p' -> if name == "record" -- Only pg pseudo type that is a row type is 'record'
|
||||
then Composite qi
|
||||
else Scalar qi
|
||||
_ -> Scalar qi -- 'b'ase, 'd'omain, 'e'num, 'r'ange
|
||||
|
||||
parseVolatility :: Char -> ProcVolatility
|
||||
parseVolatility v | v == 'i' = Immutable
|
||||
| v == 's' = Stable
|
||||
| otherwise = Volatile -- only 'v' can happen here
|
||||
|
||||
allProcs :: H.Query Schema (M.HashMap Text ProcDescription)
|
||||
allProcs = H.statement (toS procsSqlQuery) (HE.value HE.text) decodeProcs True
|
||||
|
||||
accessibleProcs :: H.Query Schema (M.HashMap Text ProcDescription)
|
||||
accessibleProcs =
|
||||
H.statement sql (HE.value HE.text)
|
||||
(M.fromList . map addName <$>
|
||||
HD.rowsList (
|
||||
ProcDescription <$> HD.value HD.text
|
||||
<*> HD.nullableValue HD.text
|
||||
<*> (parseArgs <$> HD.value HD.text)
|
||||
<*> (parseRetType <$>
|
||||
HD.value HD.text <*>
|
||||
HD.value HD.text <*>
|
||||
HD.value HD.bool <*>
|
||||
HD.value HD.char)
|
||||
<*> (parseVolatility <$>
|
||||
HD.value HD.char)
|
||||
)
|
||||
) True
|
||||
where
|
||||
addName :: ProcDescription -> (Text, ProcDescription)
|
||||
addName pd = (pdName pd, pd)
|
||||
accessibleProcs = H.statement (toS sql) (HE.value HE.text) decodeProcs True
|
||||
where
|
||||
sql = procsSqlQuery <> " AND has_function_privilege(p.oid, 'execute')"
|
||||
|
||||
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)
|
||||
|
||||
parseRetType :: Text -> Text -> Bool -> Char -> RetType
|
||||
parseRetType schema name isSetOf typ
|
||||
| isSetOf = SetOf pgType
|
||||
| otherwise = Single pgType
|
||||
where
|
||||
qi = QualifiedIdentifier schema name
|
||||
pgType = case typ of
|
||||
'c' -> Composite qi
|
||||
'p' -> if name == "record" -- Only pg pseudo type that is a row type is 'record'
|
||||
then Composite qi
|
||||
else Scalar qi
|
||||
_ -> Scalar qi -- 'b'ase, 'd'omain, 'e'num, 'r'ange
|
||||
|
||||
parseVolatility :: Char -> ProcVolatility
|
||||
parseVolatility 'i' = Immutable
|
||||
parseVolatility 's' = Stable
|
||||
parseVolatility 'v' = Volatile
|
||||
parseVolatility _ = Volatile -- should not happen, but be pessimistic
|
||||
|
||||
sql = [q|
|
||||
procsSqlQuery :: SqlQuery
|
||||
procsSqlQuery = [q|
|
||||
SELECT p.proname as "proc_name",
|
||||
d.description as "proc_description",
|
||||
pg_get_function_arguments(p.oid) as "args",
|
||||
@@ -167,11 +173,12 @@ accessibleProcs =
|
||||
JOIN pg_namespace tn ON tn.oid = t.typnamespace
|
||||
LEFT JOIN pg_class comp ON comp.oid = t.typrelid
|
||||
LEFT JOIN pg_catalog.pg_description as d on d.objoid = p.oid
|
||||
WHERE pn.nspname = $1|]
|
||||
WHERE pn.nspname = $1
|
||||
|]
|
||||
|
||||
schemaDescription :: H.Query Schema (Maybe Text)
|
||||
schemaDescription =
|
||||
H.statement sql (HE.value HE.text) (HD.singleRow $ HD.nullableValue HD.text) True
|
||||
H.statement sql (HE.value HE.text) (join <$> HD.maybeRow (HD.nullableValue HD.text)) True
|
||||
where
|
||||
sql = [q|
|
||||
select
|
||||
|
||||
@@ -22,7 +22,7 @@ import Protolude hiding ((&), Proxy, get, intercalate, dr
|
||||
import Data.Swagger
|
||||
|
||||
import PostgREST.ApiRequest (ContentType(..))
|
||||
import PostgREST.Config (prettyVersion)
|
||||
import PostgREST.Config (prettyVersion, docsVersion)
|
||||
import PostgREST.Types (Table(..), Column(..), PgArg(..), ForeignKey(..),
|
||||
PrimaryKey(..), Proxy(..), ProcDescription(..), toMime)
|
||||
|
||||
@@ -260,7 +260,7 @@ postgrestSpec pds ti (s, h, p, b) sd pks = (mempty :: Swagger)
|
||||
& description ?~ d)
|
||||
& externalDocs ?~ ((mempty :: ExternalDocs)
|
||||
& description ?~ "PostgREST Documentation"
|
||||
& url .~ URL "https://postgrest.com/en/latest/api.html")
|
||||
& url .~ URL ("https://postgrest.com/en/" <> docsVersion <> "/api.html"))
|
||||
& host .~ h'
|
||||
& definitions .~ fromList (map (makeTableDef pks) ti)
|
||||
& parameters .~ fromList (makeParamDefs ti)
|
||||
|
||||
Reference in New Issue
Block a user