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
- 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
- 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
+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"
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
create or replace function root() returns json as $_$
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
return json_build_object(
'swagger', '2.0',
'info', json_build_object(
'title', 'Overridden',
'description', 'This is my own API',
'version', configs->>'version'
)
);
return openapi;
end
$_$ language plpgsql;
.. code-block:: bash
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",
"info":{
"title":"Overridden",
"description":"This is my own API",
"version":"14.0.0"
"description":"This is a my own API"
}
}
+6 -8
View File
@@ -57,7 +57,6 @@ import Protolude
-}
data ApiRequest = ApiRequest {
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
, iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
, 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.
userApiRequest :: AppConfig -> Preferences.Preferences -> Request -> RequestBody -> Either ApiRequestError ApiRequest
userApiRequest conf prefs req reqBody = do
(resource, isRootRoutine) <- getResource conf $ pathInfo req
resource <- getResource conf $ pathInfo req
(schema, negotiatedByProfile) <- getSchema conf hdrs method
act <- getAction resource schema method
qPrms <- first QueryParamError $ QueryParams.parse (actIsInvokeSafe act) $ rawQueryString req
@@ -95,7 +94,6 @@ userApiRequest conf prefs req reqBody = do
, iCookies = iCkies
, iPath = rawPathInfo req
, iMethod = method
, iIsRootRoutine = isRootRoutine
, iSchema = schema
, iNegotiatedByProfile = negotiatedByProfile
, 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 req = extractBearerAuth =<< lookup hAuthorization (requestHeaders req)
getResource :: AppConfig -> [Text] -> Either ApiRequestError (Resource, Bool)
getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource
getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case
[] ->
case (configOpenApiMode,configDbRootSpec) of
(OADisabled,_) -> Left OpenAPIDisabled
(_, Just qi) -> Right (ResourceRoutine (qiName qi), True)
(_, Nothing) -> Right (ResourceSchema, False)
(_, Just qi) -> Right $ ResourceRoutine (qiName qi)
(_, Nothing) -> Right ResourceSchema
[table] -> Right (ResourceRelation table, False)
["rpc", pName] -> Right (ResourceRoutine pName , False)
[table] -> Right $ ResourceRelation table
["rpc", pName] -> Right $ ResourceRoutine pName
_ -> Left InvalidResourcePath
getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action
+1 -16
View File
@@ -29,7 +29,6 @@ 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)
@@ -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.
-- 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 ++ rootSpecSettingsSql
searchPathSql : roleSettingsSql ++ roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ timezoneSql ++ funcSettingsSql ++ appSettingsSql
)
where
methodSql = setConfigWithConstantName ("request.method", iMethod)
@@ -53,20 +52,6 @@ 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
| 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
funcSettingsSql = setConfigWithDynamicName . join bimap toUtf8 <$> funcSettings
searchPathSql =
+3 -32
View File
@@ -8,29 +8,19 @@ 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"
, configDbSchemas = "test" :| ["v1"]
, configOpenApiServerProxyUri = Just "https://example.com/base"
}) $
spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root" }) $
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"},
"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}
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
}|]
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }
@@ -39,25 +29,6 @@ spec withConfig = withConfig (baseCfg
[("Accept","application/json")] "" `shouldRespondWith`
[json|{
"swagger": "2.0",
"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}
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
}|]
{ 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 $_$
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
return (
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', 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;
return openapi;
end
$_$ language plpgsql;
create or replace function welcome() returns "text/plain" as $$