Skip to content

Commit 14f41be

Browse files
committed
add: configs as gucs for db-root-spec
Fixes #3029
1 parent 0bda2bc commit 14f41be

7 files changed

Lines changed: 75 additions & 30 deletions

File tree

src/PostgREST/ApiRequest.hs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import Protolude
6262
-}
6363
data ApiRequest = ApiRequest {
6464
iAction :: Action -- ^ Action on the resource
65+
, iResource :: Resource -- ^ Resource resolved from the request path
6566
, iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response
6667
, iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
6768
, iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions
@@ -99,6 +100,7 @@ userApiRequest conf prefs req reqBody = do
99100
, iCookies = iCkies
100101
, iPath = rawPathInfo req
101102
, iMethod = method
103+
, iResource = resource
102104
, iSchema = schema
103105
, iNegotiatedByProfile = negotiatedByProfile
104106
, iAcceptMediaType = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
@@ -111,7 +113,7 @@ userApiRequest conf prefs req reqBody = do
111113
iHdrs = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie]
112114
iCkies = maybe [] parseCookies $ lookupHeader "Cookie"
113115
contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
114-
actIsInvokeSafe x = case x of {ActDb (ActRoutine _ (InvRead _)) -> True; _ -> False}
116+
actIsInvokeSafe x = case x of {ActDb (ActRoutine _ (InvRead _)) -> True; _ -> False}
115117

116118
-- | Parses the Prefer header
117119
userPreferences :: AppConfig -> Request -> TimezoneNames -> Preferences.Preferences
@@ -126,21 +128,21 @@ getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case
126128
[] ->
127129
case (configOpenApiMode,configDbRootSpec) of
128130
(OADisabled,_) -> Left OpenAPIDisabled
129-
(_, Just qi) -> Right $ ResourceRoutine (qiName qi)
131+
(_, Just qi) -> Right $ ResourceRoutine (qiName qi) True
130132
(_, Nothing) -> Right ResourceSchema
131133

132134
[table] -> Right $ ResourceRelation table
133-
["rpc", pName] -> Right $ ResourceRoutine pName
135+
["rpc", pName] -> Right $ ResourceRoutine pName False
134136
_ -> Left InvalidResourcePath
135137

136138
getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action
137139
getAction resource schema method =
138140
case (resource, method) of
139-
(ResourceRoutine rout, "HEAD") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead True
140-
(ResourceRoutine rout, "GET") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead False
141-
(ResourceRoutine rout, "POST") -> Right . ActDb $ ActRoutine (qi rout) Inv
142-
(ResourceRoutine rout, "OPTIONS") -> Right $ ActRoutineInfo (qi rout) $ InvRead True
143-
(ResourceRoutine _, _) -> Left $ InvalidRpcMethod method
141+
(ResourceRoutine rout _, "HEAD") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead True
142+
(ResourceRoutine rout _, "GET") -> Right . ActDb $ ActRoutine (qi rout) $ InvRead False
143+
(ResourceRoutine rout _, "POST") -> Right . ActDb $ ActRoutine (qi rout) Inv
144+
(ResourceRoutine rout _, "OPTIONS") -> Right $ ActRoutineInfo (qi rout) $ InvRead True
145+
(ResourceRoutine _ _, _) -> Left $ InvalidRpcMethod method
144146

145147
(ResourceRelation rel, "HEAD") -> Right . ActDb $ ActRelationRead (qi rel) True
146148
(ResourceRelation rel, "GET") -> Right . ActDb $ ActRelationRead (qi rel) False

src/PostgREST/ApiRequest/Payload.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ getPayload reqBody contentMediaType QueryParams{qsColumns} action = do
7474
shouldParsePayload = case action of
7575
ActDb (ActRelationMut _ MutationDelete) -> False
7676
ActDb (ActRelationMut _ _) -> True
77-
ActDb (ActRoutine _ Inv) -> True
77+
ActDb (ActRoutine _ Inv) -> True
7878
_ -> False
7979

8080
columns = case action of
8181
ActDb (ActRelationMut _ MutationCreate) -> qsColumns
8282
ActDb (ActRelationMut _ MutationUpdate) -> qsColumns
83-
ActDb (ActRoutine _ Inv) -> qsColumns
83+
ActDb (ActRoutine _ Inv) -> qsColumns
8484
_ -> Nothing
8585

8686
isProc = case action of

src/PostgREST/ApiRequest/Types.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ data Mutation
5858
| MutationUpdate
5959
deriving Eq
6060

61+
type IsRoot = Bool
62+
6163
data Resource
6264
= ResourceRelation Text
63-
| ResourceRoutine Text
65+
| ResourceRoutine Text IsRoot
6466
| ResourceSchema
6567

6668
data DbAction

src/PostgREST/Plan/Negotiate.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRep
5858
-- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
5959
-- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
6060
(ActDb (ActRelationRead _ True), Just (_, mt)) -> Right (NoAgg, mt)
61-
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
61+
(ActDb (ActRoutine _ (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
6262
(_, Just (x, mt)) -> Right (x, mt)
6363
where
6464
firstAcceptedPick = listToMaybe $ mapMaybe matchMT accepts -- If there are multiple accepted media types, pick the first. This is usual in content negotiation.

src/PostgREST/Query/PreQuery.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import qualified Data.Aeson as JSON
1313
import qualified Data.Aeson.KeyMap as KM
1414
import qualified Data.ByteString.Lazy.Char8 as LBS
1515
import qualified Data.HashMap.Strict as HM
16+
import qualified Data.Text as T
1617
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
1718

1819

1920

2021
import PostgREST.ApiRequest (ApiRequest (..))
2122
import PostgREST.ApiRequest.Preferences (PreferTimezone (..),
2223
Preferences (..))
24+
import PostgREST.ApiRequest.Types (Resource (..))
2325
import PostgREST.Auth.Types (AuthResult (..))
2426
import PostgREST.Config (AppConfig (..))
2527
import PostgREST.Plan (CrudPlan (..),
@@ -31,6 +33,7 @@ import PostgREST.Query.SqlFragment (escapeIdentList, fromQi,
3133
setConfigWithDynamicName)
3234
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
3335
import PostgREST.SchemaCache.Routine (Routine (..))
36+
import PostgREST.Version (prettyVersion)
3437

3538
import Protolude hiding (Handler)
3639

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

6679
-- runs the pre-request function
6780
preReqQuery :: QualifiedIdentifier -> SQL.Snippet

test/spec/Feature/OpenApi/RootSpec.hs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,29 @@ import Test.Hspec.Wai.JSON
88

99
import PostgREST.Config (AppConfig (..))
1010
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
11+
import PostgREST.Version (prettyVersion)
1112

1213
import Protolude hiding (get)
1314
import SpecHelper
1415

1516
spec :: SpecWithConfig
16-
spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root" }) $
17+
spec withConfig = withConfig (baseCfg
18+
{ configDbRootSpec = Just $ QualifiedIdentifier mempty "root"
19+
, configDbSchemas = "test" :| ["v1"]
20+
, configOpenApiServerProxyUri = Just "https://example.com/base"
21+
}) $
1722
describe "root spec function" $ do
1823
it "accepts application/openapi+json" $ do
1924
request methodGet "/"
2025
[("Accept","application/openapi+json")] "" `shouldRespondWith`
2126
[json|{
2227
"swagger": "2.0",
23-
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
28+
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
29+
"pgrst_server_host": "localhost",
30+
"pgrst_server_port": "3000",
31+
"pgrst_openapi_server_proxy_uri": "https://example.com/base",
32+
"pgrst_db_schemas": "test,v1",
33+
"pgrst_version": #{decodeUtf8 prettyVersion}
2434
}|]
2535
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }
2636

@@ -29,6 +39,25 @@ spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdent
2939
[("Accept","application/json")] "" `shouldRespondWith`
3040
[json|{
3141
"swagger": "2.0",
32-
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
42+
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
43+
"pgrst_server_host": "localhost",
44+
"pgrst_server_port": "3000",
45+
"pgrst_openapi_server_proxy_uri": "https://example.com/base",
46+
"pgrst_db_schemas": "test,v1",
47+
"pgrst_version": #{decodeUtf8 prettyVersion}
3348
}|]
3449
{ matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"] }
50+
51+
it "returns null root spec GUCs on /rpc/root" $ do
52+
request methodGet "/rpc/root"
53+
[("Accept","application/openapi+json")] "" `shouldRespondWith`
54+
[json|{
55+
"swagger": "2.0",
56+
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"},
57+
"pgrst_server_host": null,
58+
"pgrst_server_port": null,
59+
"pgrst_openapi_server_proxy_uri": null,
60+
"pgrst_db_schemas": null,
61+
"pgrst_version": null
62+
}|]
63+
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }

test/spec/fixtures/schema.sql

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,20 +1865,19 @@ returns integer as $$
18651865
$$ language sql;
18661866

18671867
create or replace function root() returns "application/openapi+json" as $_$
1868-
declare
1869-
openapi json = $$
1870-
{
1871-
"swagger": "2.0",
1872-
"info":{
1873-
"title":"PostgREST API",
1874-
"description":"This is a dynamic API generated by PostgREST"
1875-
}
1876-
}
1877-
$$;
1878-
begin
1879-
return openapi;
1880-
end
1881-
$_$ language plpgsql;
1868+
select json_build_object(
1869+
'swagger', '2.0',
1870+
'info', json_build_object(
1871+
'title', 'PostgREST API',
1872+
'description', 'This is a dynamic API generated by PostgREST'
1873+
),
1874+
'pgrst_server_host', nullif(current_setting('pgrst.server_host', true), ''),
1875+
'pgrst_server_port', nullif(current_setting('pgrst.server_port', true), ''),
1876+
'pgrst_openapi_server_proxy_uri', nullif(current_setting('pgrst.openapi_server_proxy_uri', true), ''),
1877+
'pgrst_db_schemas', nullif(current_setting('pgrst.db_schemas', true), ''),
1878+
'pgrst_version', nullif(current_setting('pgrst.version', true), '')
1879+
)::json;
1880+
$_$ language sql;
18821881

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

0 commit comments

Comments
 (0)