Revert "add: configs as json GUC for db-root-spec"

This reverts commit f34ca15e84.
This commit is contained in:
steve-chavez
2026-08-04 00:50:18 -05:00
parent f34ca15e84
commit ca4a6d9e99
6 changed files with 32 additions and 94 deletions
-1
View File
@@ -18,7 +18,6 @@ All notable changes to this project will be documented in this file. From versio
- Add config `server-reuseport` to allow starting multiple PostgREST instances using the same port on supported platforms by @mkleczek in #4703, #4694 - Add config `server-reuseport` to allow starting multiple PostgREST instances using the same port on supported platforms by @mkleczek in #4703, #4694
- Make config `log-level` reloadable by @taimoorzaeem in #5113 - Make config `log-level` reloadable by @taimoorzaeem in #5113
- Optimize schema cache domain type resolution by using `pg_basetype` on PostgreSQL 17+ by @joelonsql in #4567 - Optimize schema cache domain type resolution by using `pg_basetype` on PostgreSQL 17+ by @joelonsql in #4567
- When `db-root-spec` is used, a `request.root.configs` GUC will be available to build a complete custom OpenAPI response by @steve-chavez in #3029
### Fixed ### Fixed
+11 -22
View File
@@ -66,34 +66,24 @@ You can override the whole default response with a function result. To do this,
db-root-spec = "root" db-root-spec = "root"
When this function is called, the ``request.root.configs`` GUC contains a JSON object with the following keys:
* ``server_host`` (string)
* ``server_port`` (string)
* ``openapi_server_proxy_uri`` (string)
* ``db_schemas`` (JSON array of strings)
* ``version`` (string)
The root spec function can read this JSON and use its values when constructing the response:
.. code:: postgres .. code:: postgres
create or replace function root() returns json as $_$ create or replace function root() returns json as $_$
declare declare
configs json := current_setting('request.root.configs', true)::json; openapi json = $$
{
"swagger": "2.0",
"info":{
"title":"Overridden",
"description":"This is a my own API"
}
}
$$;
begin begin
return json_build_object( return openapi;
'swagger', '2.0',
'info', json_build_object(
'title', 'Overridden',
'description', 'This is my own API',
'version', configs->>'version'
)
);
end end
$_$ language plpgsql; $_$ language plpgsql;
.. code-block:: bash .. code-block:: bash
curl http://localhost:3000 curl http://localhost:3000
@@ -106,7 +96,6 @@ The root spec function can read this JSON and use its values when constructing t
"swagger": "2.0", "swagger": "2.0",
"info":{ "info":{
"title":"Overridden", "title":"Overridden",
"description":"This is my own API", "description":"This is a my own API"
"version":"14.0.0"
} }
} }
+6 -8
View File
@@ -57,7 +57,6 @@ import Protolude
-} -}
data ApiRequest = ApiRequest { data ApiRequest = ApiRequest {
iAction :: Action -- ^ Action on the resource iAction :: Action -- ^ Action on the resource
, iIsRootRoutine :: Bool -- ^ If the request wants the root routine
, iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response , iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response
, iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level , iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
, iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions , iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions
@@ -77,7 +76,7 @@ data ApiRequest = ApiRequest {
-- | Examines HTTP request and translates it into user intent. -- | Examines HTTP request and translates it into user intent.
userApiRequest :: AppConfig -> Preferences.Preferences -> Request -> RequestBody -> Either ApiRequestError ApiRequest userApiRequest :: AppConfig -> Preferences.Preferences -> Request -> RequestBody -> Either ApiRequestError ApiRequest
userApiRequest conf prefs req reqBody = do userApiRequest conf prefs req reqBody = do
(resource, isRootRoutine) <- getResource conf $ pathInfo req resource <- getResource conf $ pathInfo req
(schema, negotiatedByProfile) <- getSchema conf hdrs method (schema, negotiatedByProfile) <- getSchema conf hdrs method
act <- getAction resource schema method act <- getAction resource schema method
qPrms <- first QueryParamError $ QueryParams.parse (actIsInvokeSafe act) $ rawQueryString req qPrms <- first QueryParamError $ QueryParams.parse (actIsInvokeSafe act) $ rawQueryString req
@@ -95,7 +94,6 @@ userApiRequest conf prefs req reqBody = do
, iCookies = iCkies , iCookies = iCkies
, iPath = rawPathInfo req , iPath = rawPathInfo req
, iMethod = method , iMethod = method
, iIsRootRoutine = isRootRoutine
, iSchema = schema , iSchema = schema
, iNegotiatedByProfile = negotiatedByProfile , iNegotiatedByProfile = negotiatedByProfile
, iAcceptMediaType = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept" , iAcceptMediaType = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
@@ -118,16 +116,16 @@ userPreferences conf req timezones = Preferences.fromHeaders (configDbTxAllowOve
userBearerAuth :: Request -> Maybe ByteString userBearerAuth :: Request -> Maybe ByteString
userBearerAuth req = extractBearerAuth =<< lookup hAuthorization (requestHeaders req) userBearerAuth req = extractBearerAuth =<< lookup hAuthorization (requestHeaders req)
getResource :: AppConfig -> [Text] -> Either ApiRequestError (Resource, Bool) getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource
getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case
[] -> [] ->
case (configOpenApiMode,configDbRootSpec) of case (configOpenApiMode,configDbRootSpec) of
(OADisabled,_) -> Left OpenAPIDisabled (OADisabled,_) -> Left OpenAPIDisabled
(_, Just qi) -> Right (ResourceRoutine (qiName qi), True) (_, Just qi) -> Right $ ResourceRoutine (qiName qi)
(_, Nothing) -> Right (ResourceSchema, False) (_, Nothing) -> Right ResourceSchema
[table] -> Right (ResourceRelation table, False) [table] -> Right $ ResourceRelation table
["rpc", pName] -> Right (ResourceRoutine pName , False) ["rpc", pName] -> Right $ ResourceRoutine pName
_ -> Left InvalidResourcePath _ -> Left InvalidResourcePath
getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action
+1 -16
View File
@@ -29,7 +29,6 @@ import PostgREST.Query.SqlFragment (escapeIdentList, fromQi,
setConfigWithDynamicName) setConfigWithDynamicName)
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.SchemaCache.Routine (Routine (..)) import PostgREST.SchemaCache.Routine (Routine (..))
import PostgREST.Version (prettyVersion)
import Protolude hiding (Handler) import Protolude hiding (Handler)
@@ -39,7 +38,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. -- 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 -- Otherwise the GRANT SET would have to be applied to the impersonated role. See https://github.com/PostgREST/postgrest/issues/3045
"select " <> intercalateSnippet ", " ( "select " <> intercalateSnippet ", " (
searchPathSql : roleSettingsSql ++ roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ timezoneSql ++ funcSettingsSql ++ appSettingsSql ++ rootSpecSettingsSql searchPathSql : roleSettingsSql ++ roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ timezoneSql ++ funcSettingsSql ++ appSettingsSql
) )
where where
methodSql = setConfigWithConstantName ("request.method", iMethod) methodSql = setConfigWithConstantName ("request.method", iMethod)
@@ -53,20 +52,6 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
roleSql = [setConfigWithConstantName ("role", authRole)] roleSql = [setConfigWithConstantName ("role", authRole)]
roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings) roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings)
appSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> configAppSettings appSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> configAppSettings
rootSpecSettingsSql
| iIsRootRoutine =
[ setConfigWithConstantName
( "request.root.configs"
, LBS.toStrict $ JSON.encode $ JSON.object
[ "server_host" JSON..= configServerHost
, "server_port" JSON..= (show configServerPort :: Text)
, "openapi_server_proxy_uri" JSON..= configOpenApiServerProxyUri
, "db_schemas" JSON..= toList configDbSchemas
, "version" JSON..= decodeUtf8 prettyVersion
]
)
]
| otherwise = mempty
timezoneSql = maybe mempty (\(PreferTimezone tz) -> [setConfigWithConstantName ("timezone", tz)]) $ preferTimezone iPreferences timezoneSql = maybe mempty (\(PreferTimezone tz) -> [setConfigWithConstantName ("timezone", tz)]) $ preferTimezone iPreferences
funcSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> funcSettings funcSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> funcSettings
searchPathSql = searchPathSql =
+3 -32
View File
@@ -8,29 +8,19 @@ import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..)) import PostgREST.Config (AppConfig (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.Version (prettyVersion)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
spec :: SpecWithConfig spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root" }) $
{ configDbRootSpec = Just $ QualifiedIdentifier mempty "root"
, configDbSchemas = "test" :| ["v1"]
, configOpenApiServerProxyUri = Just "https://example.com/base"
}) $
describe "root spec function" $ do describe "root spec function" $ do
it "accepts application/openapi+json" $ do it "accepts application/openapi+json" $ do
request methodGet "/" request methodGet "/"
[("Accept","application/openapi+json")] "" `shouldRespondWith` [("Accept","application/openapi+json")] "" `shouldRespondWith`
[json|{ [json|{
"swagger": "2.0", "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/openapi+json; charset=utf-8"] } { matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }
@@ -39,25 +29,6 @@ spec withConfig = withConfig (baseCfg
[("Accept","application/json")] "" `shouldRespondWith` [("Accept","application/json")] "" `shouldRespondWith`
[json|{ [json|{
"swagger": "2.0", "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"] } { matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"] }
it "returns null GUCs on non-root paths" $ 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"] }
+11 -15
View File
@@ -1866,22 +1866,18 @@ $$ language sql;
create or replace function root() returns "application/openapi+json" as $_$ create or replace function root() returns "application/openapi+json" as $_$
declare declare
pgrst_config json := nullif(current_setting('request.root.configs', true), '')::json; openapi json = $$
{
"swagger": "2.0",
"info":{
"title":"PostgREST API",
"description":"This is a dynamic API generated by PostgREST"
}
}
$$;
begin begin
return ( return openapi;
select json_build_object( end
'swagger', '2.0',
'info', json_build_object(
'title', 'PostgREST API',
'description', 'This is a dynamic API generated by PostgREST'
),
'pgrst_server_host', pgrst_config->>'server_host',
'pgrst_server_port', pgrst_config->>'server_port',
'pgrst_openapi_server_proxy_uri', pgrst_config->>'openapi_server_proxy_uri',
'pgrst_db_schemas', pgrst_config->'db_schemas',
'pgrst_version', pgrst_config->>'version'
)::json);
end;
$_$ language plpgsql; $_$ language plpgsql;
create or replace function welcome() returns "text/plain" as $$ create or replace function welcome() returns "text/plain" as $$