diff --git a/CHANGELOG.md b/CHANGELOG.md index b75cf7148..6cd52a53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/references/api/openapi.rst b/docs/references/api/openapi.rst index 9e3bc8fc2..ec546eb72 100644 --- a/docs/references/api/openapi.rst +++ b/docs/references/api/openapi.rst @@ -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" } } diff --git a/src/library/PostgREST/ApiRequest.hs b/src/library/PostgREST/ApiRequest.hs index 3ad0b20a9..eb09e6e6e 100644 --- a/src/library/PostgREST/ApiRequest.hs +++ b/src/library/PostgREST/ApiRequest.hs @@ -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 diff --git a/src/library/PostgREST/Query/PreQuery.hs b/src/library/PostgREST/Query/PreQuery.hs index 6c581c524..59cd4cc79 100644 --- a/src/library/PostgREST/Query/PreQuery.hs +++ b/src/library/PostgREST/Query/PreQuery.hs @@ -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 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 = diff --git a/test/spec/Feature/OpenApi/RootSpec.hs b/test/spec/Feature/OpenApi/RootSpec.hs index 6ebc4b05c..5391ebffc 100644 --- a/test/spec/Feature/OpenApi/RootSpec.hs +++ b/test/spec/Feature/OpenApi/RootSpec.hs @@ -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"] } diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index 776d41082..cce6136e8 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -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 $$