Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/PostgREST/ApiRequest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
-}
data ApiRequest = ApiRequest {
iAction :: Action -- ^ Action on the resource
, iResource :: Resource -- ^ Resource resolved from the request path

Check warning on line 65 in src/PostgREST/ApiRequest.hs

View check run for this annotation

Codecov / codecov/patch

src/PostgREST/ApiRequest.hs#L65

Added line #L65 was not covered by tests
, iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response
, iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
, iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions
Expand Down Expand Up @@ -99,6 +100,7 @@
, iCookies = iCkies
, iPath = rawPathInfo req
, iMethod = method
, iResource = resource
, iSchema = schema
, iNegotiatedByProfile = negotiatedByProfile
, iAcceptMediaType = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
Expand All @@ -111,7 +113,7 @@
iHdrs = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie]
iCkies = maybe [] parseCookies $ lookupHeader "Cookie"
contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
actIsInvokeSafe x = case x of {ActDb (ActRoutine _ (InvRead _)) -> True; _ -> False}
actIsInvokeSafe x = case x of {ActDb (ActRoutine _ (InvRead _)) -> True; _ -> False}

-- | Parses the Prefer header
userPreferences :: AppConfig -> Request -> TimezoneNames -> Preferences.Preferences
Expand All @@ -126,21 +128,21 @@
[] ->
case (configOpenApiMode,configDbRootSpec) of
(OADisabled,_) -> Left OpenAPIDisabled
(_, Just qi) -> Right $ ResourceRoutine (qiName qi)
(_, Just qi) -> Right $ ResourceRoutine (qiName qi) True
(_, Nothing) -> Right ResourceSchema

[table] -> Right $ ResourceRelation table
["rpc", pName] -> Right $ ResourceRoutine pName
["rpc", pName] -> Right $ ResourceRoutine pName False
_ -> Left InvalidResourcePath

getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action
getAction resource schema method =
case (resource, method) of
(ResourceRoutine rout, "HEAD") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead True
(ResourceRoutine rout, "GET") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead False
(ResourceRoutine rout, "POST") -> Right . ActDb $ ActRoutine (qi rout) Inv
(ResourceRoutine rout, "OPTIONS") -> Right $ ActRoutineInfo (qi rout) $ InvRead True
(ResourceRoutine _, _) -> Left $ InvalidRpcMethod method
(ResourceRoutine rout _, "HEAD") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead True
(ResourceRoutine rout _, "GET") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead False
(ResourceRoutine rout _, "POST") -> Right . ActDb $ ActRoutine (qi rout) Inv
(ResourceRoutine rout _, "OPTIONS") -> Right $ ActRoutineInfo (qi rout) $ InvRead True
(ResourceRoutine _ _, _) -> Left $ InvalidRpcMethod method

(ResourceRelation rel, "HEAD") -> Right . ActDb $ ActRelationRead (qi rel) True
(ResourceRelation rel, "GET") -> Right . ActDb $ ActRelationRead (qi rel) False
Expand Down
4 changes: 2 additions & 2 deletions src/PostgREST/ApiRequest/Payload.hs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ getPayload reqBody contentMediaType QueryParams{qsColumns} action = do
shouldParsePayload = case action of
ActDb (ActRelationMut _ MutationDelete) -> False
ActDb (ActRelationMut _ _) -> True
ActDb (ActRoutine _ Inv) -> True
ActDb (ActRoutine _ Inv) -> True
_ -> False

columns = case action of
ActDb (ActRelationMut _ MutationCreate) -> qsColumns
ActDb (ActRelationMut _ MutationUpdate) -> qsColumns
ActDb (ActRoutine _ Inv) -> qsColumns
ActDb (ActRoutine _ Inv) -> qsColumns
_ -> Nothing

isProc = case action of
Expand Down
4 changes: 3 additions & 1 deletion src/PostgREST/ApiRequest/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ data Mutation
| MutationUpdate
deriving Eq

type IsRoot = Bool

data Resource
= ResourceRelation Text
| ResourceRoutine Text
| ResourceRoutine Text IsRoot
| ResourceSchema

data DbAction
Expand Down
2 changes: 1 addition & 1 deletion src/PostgREST/Plan/Negotiate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRep
-- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
-- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
(ActDb (ActRelationRead _ True), Just (_, mt)) -> Right (NoAgg, mt)
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
(_, Just (x, mt)) -> Right (x, mt)
where
firstAcceptedPick = listToMaybe $ mapMaybe matchMT accepts -- If there are multiple accepted media types, pick the first. This is usual in content negotiation.
Expand Down
15 changes: 14 additions & 1 deletion src/PostgREST/Query/PreQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import qualified Data.Aeson as JSON
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)



import PostgREST.ApiRequest (ApiRequest (..))
import PostgREST.ApiRequest.Preferences (PreferTimezone (..),
Preferences (..))
import PostgREST.ApiRequest.Types (Resource (..))
import PostgREST.Auth.Types (AuthResult (..))
import PostgREST.Config (AppConfig (..))
import PostgREST.Plan (CrudPlan (..),
Expand All @@ -31,6 +33,7 @@ import PostgREST.Query.SqlFragment (escapeIdentList, fromQi,
setConfigWithDynamicName)
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.SchemaCache.Routine (Routine (..))
import PostgREST.Version (prettyVersion)

import Protolude hiding (Handler)

Expand All @@ -40,7 +43,7 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
-- To ensure `GRANT SET ON PARAMETER <superuser_setting> TO authenticator` works, the role settings must be set before the impersonated role.
-- Otherwise the GRANT SET would have to be applied to the impersonated role. See https://github.com/PostgREST/postgrest/issues/3045
"select " <> intercalateSnippet ", " (
searchPathSql : roleSettingsSql ++ roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ timezoneSql ++ funcSettingsSql ++ appSettingsSql
searchPathSql : roleSettingsSql ++ roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ timezoneSql ++ funcSettingsSql ++ appSettingsSql ++ rootSpecSettingsSql
)
where
methodSql = setConfigWithConstantName ("request.method", iMethod)
Expand All @@ -54,6 +57,15 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
roleSql = [setConfigWithConstantName ("role", authRole)]
roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings)
appSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> configAppSettings
rootSpecSettingsSql
| isRootSpec iResource =
[ setConfigWithConstantName ("pgrst.server_host", toUtf8 configServerHost)
, setConfigWithConstantName ("pgrst.server_port", toUtf8 (show configServerPort :: Text))
, setConfigWithConstantName ("pgrst.openapi_server_proxy_uri", maybe mempty toUtf8 configOpenApiServerProxyUri)
, setConfigWithConstantName ("pgrst.db_schemas", toUtf8 $ T.intercalate "," $ toList configDbSchemas)
, setConfigWithConstantName ("pgrst.version", prettyVersion)
]
| otherwise = mempty
timezoneSql = maybe mempty (\(PreferTimezone tz) -> [setConfigWithConstantName ("timezone", tz)]) $ preferTimezone iPreferences
funcSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> funcSettings
searchPathSql =
Expand All @@ -62,6 +74,7 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
funcSettings = case dbActPlan of
DbCrud _ CallReadPlan{crProc} -> pdFuncSettings crProc
_ -> mempty
isRootSpec resource = case resource of { ResourceRoutine _ True -> True; _ -> False }

-- runs the pre-request function
preReqQuery :: QualifiedIdentifier -> SQL.Snippet
Expand Down
35 changes: 32 additions & 3 deletions test/spec/Feature/OpenApi/RootSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@ import Test.Hspec.Wai.JSON

import PostgREST.Config (AppConfig (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.Version (prettyVersion)

import Protolude hiding (get)
import SpecHelper

spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root" }) $
spec withConfig = withConfig (baseCfg
{ configDbRootSpec = Just $ QualifiedIdentifier mempty "root"
, configDbSchemas = "test" :| ["v1"]
, configOpenApiServerProxyUri = Just "https://example.com/base"
}) $
describe "root spec function" $ do
it "accepts application/openapi+json" $ do
request methodGet "/"
[("Accept","application/openapi+json")] "" `shouldRespondWith`
[json|{
"swagger": "2.0",
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
"pgrst_server_host": "localhost",
"pgrst_server_port": "3000",
"pgrst_openapi_server_proxy_uri": "https://example.com/base",
"pgrst_db_schemas": "test,v1",
"pgrst_version": #{decodeUtf8 prettyVersion}
Comment thread
laurenceisla marked this conversation as resolved.
}|]
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }

Expand All @@ -29,6 +39,25 @@ spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdent
[("Accept","application/json")] "" `shouldRespondWith`
[json|{
"swagger": "2.0",
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
"pgrst_server_host": "localhost",
"pgrst_server_port": "3000",
"pgrst_openapi_server_proxy_uri": "https://example.com/base",
"pgrst_db_schemas": "test,v1",
"pgrst_version": #{decodeUtf8 prettyVersion}
}|]
{ matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"] }

it "returns null root spec GUCs on /rpc/root" $ do
request methodGet "/rpc/root"
[("Accept","application/openapi+json")] "" `shouldRespondWith`
[json|{
"swagger": "2.0",
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
"pgrst_server_host": null,
"pgrst_server_port": null,
"pgrst_openapi_server_proxy_uri": null,
"pgrst_db_schemas": null,
"pgrst_version": null
}|]
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }
27 changes: 13 additions & 14 deletions test/spec/fixtures/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1865,20 +1865,19 @@ returns integer as $$
$$ language sql;

create or replace function root() returns "application/openapi+json" as $_$
declare
openapi json = $$
{
"swagger": "2.0",
"info":{
"title":"PostgREST API",
"description":"This is a dynamic API generated by PostgREST"
}
}
$$;
begin
return openapi;
end
$_$ language plpgsql;
select json_build_object(
'swagger', '2.0',
'info', json_build_object(
'title', 'PostgREST API',
'description', 'This is a dynamic API generated by PostgREST'
),
'pgrst_server_host', nullif(current_setting('pgrst.server_host', true), ''),
'pgrst_server_port', nullif(current_setting('pgrst.server_port', true), ''),
'pgrst_openapi_server_proxy_uri', nullif(current_setting('pgrst.openapi_server_proxy_uri', true), ''),
'pgrst_db_schemas', nullif(current_setting('pgrst.db_schemas', true), ''),
'pgrst_version', nullif(current_setting('pgrst.version', true), '')
)::json;
$_$ language sql;

create or replace function welcome() returns "text/plain" as $$
select 'Welcome to PostgREST'::"text/plain";
Expand Down