From a5bb20bbf8e96cdc024bedfacf3b2df186305efe Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Tue, 12 Mar 2024 17:23:51 -0500 Subject: [PATCH] refactor: remove unreacheable 404 --- src/PostgREST/ApiRequest.hs | 175 ++++++++++++------------ src/PostgREST/ApiRequest/QueryParams.hs | 8 +- src/PostgREST/App.hs | 58 ++++---- src/PostgREST/Plan.hs | 57 ++++---- src/PostgREST/Response.hs | 2 +- 5 files changed, 151 insertions(+), 149 deletions(-) diff --git a/src/PostgREST/ApiRequest.hs b/src/PostgREST/ApiRequest.hs index 52923583f..9e25389d6 100644 --- a/src/PostgREST/ApiRequest.hs +++ b/src/PostgREST/ApiRequest.hs @@ -2,10 +2,8 @@ Module : PostgREST.Request.ApiRequest Description : PostgREST functions to translate HTTP request to a domain type called ApiRequest. -} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} module PostgREST.ApiRequest ( ApiRequest(..) @@ -13,7 +11,9 @@ module PostgREST.ApiRequest , Mutation(..) , MediaType(..) , Action(..) - , Target(..) + , ActionRelation(..) + , ActionRoutine(..) + , ActionSchema(..) , Payload(..) , userApiRequest ) where @@ -84,29 +84,31 @@ data Payload | RawJSON { payRaw :: LBS.ByteString } | RawPay { payRaw :: LBS.ByteString } -data InvokeMethod = InvHead | InvGet | InvPost deriving Eq +data InvokeMethod = Inv | InvRead Bool deriving Eq data Mutation = MutationCreate | MutationDelete | MutationSingleUpsert | MutationUpdate deriving Eq --- | Types of things a user wants to do to tables/views/procs +data Resource + = ResourceRelation Text + | ResourceRoutine Text + | ResourceSchema + +data ActionRelation + = ActRead Bool + | ActMutate Mutation + | ActRelInfo + +data ActionRoutine + = ActInvoke InvokeMethod + | ActRoutInfo + +data ActionSchema + = ActSchemaRead Bool + | ActSchemaInfo + data Action - = ActionMutate Mutation - | ActionRead {isHead :: Bool} - | ActionInvoke InvokeMethod - | ActionInfo - | ActionInspect {isHead :: Bool} - deriving Eq --- | The path info that will be mapped to a target (used to handle validations and errors before defining the Target) -data PathInfo - = PathInfo - { pathName :: Text - , pathIsProc :: Bool - , pathIsDefSpec :: Bool - , pathIsRootSpec :: Bool - } --- | The target db object of a user action -data Target = TargetIdent QualifiedIdentifier - | TargetProc{tProc :: QualifiedIdentifier, tpIsRootSpec :: Bool} - | TargetDefaultSpec{tdsSchema :: Schema} -- The default spec offered at root "/" + = ActRelation QualifiedIdentifier ActionRelation + | ActRoutine QualifiedIdentifier ActionRoutine + | ActSchema Schema ActionSchema {-| Describes what the user wants to do. This data type is a @@ -116,10 +118,9 @@ data Target = TargetIdent QualifiedIdentifier if it is an action we are able to perform. -} data ApiRequest = ApiRequest { - iAction :: Action -- ^ Similar but not identical to HTTP method, e.g. Create/Invoke both POST + iAction :: Action -- ^ Action on the resource , iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response , iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level - , iTarget :: Target -- ^ The target, be it calling a proc or accessing a table , iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions , iPreferences :: Preferences.Preferences -- ^ Prefer header values , iQueryParams :: QueryParams.QueryParams @@ -137,17 +138,14 @@ data ApiRequest = ApiRequest { -- | Examines HTTP request and translates it into user intent. userApiRequest :: AppConfig -> Request -> RequestBody -> SchemaCache -> Either ApiRequestError ApiRequest userApiRequest conf req reqBody sCache = do - pInfo@PathInfo{..} <- getPathInfo conf $ pathInfo req - act <- getAction pInfo method - qPrms <- first QueryParamError $ QueryParams.parse (pathIsProc && act `elem` [ActionInvoke InvGet, ActionInvoke InvHead]) $ rawQueryString 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 (topLevelRange, ranges) <- getRanges method qPrms hdrs - (payload, columns) <- getPayload reqBody contentMediaType qPrms act pInfo + (payload, columns) <- getPayload reqBody contentMediaType qPrms act return $ ApiRequest { iAction = act - , iTarget = if | pathIsProc -> TargetProc (QualifiedIdentifier schema pathName) pathIsRootSpec - | pathIsDefSpec -> TargetDefaultSpec schema - | otherwise -> TargetIdent $ QualifiedIdentifier schema pathName , iRange = ranges , iTopLevelRange = topLevelRange , iPayload = payload @@ -170,38 +168,43 @@ userApiRequest conf req reqBody sCache = do 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 {ActRoutine _ (ActInvoke (InvRead _)) -> True; _ -> False} -getPathInfo :: AppConfig -> [Text] -> Either ApiRequestError PathInfo -getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path = - case path of - [] -> case configDbRootSpec of - Just (QualifiedIdentifier _ pathName) -> Right $ PathInfo pathName True False True - Nothing | configOpenApiMode == OADisabled -> Left NotFound - | otherwise -> Right $ PathInfo mempty False True False - [table] -> Right $ PathInfo table False False False - ["rpc", pName] -> Right $ PathInfo pName True False False - _ -> Left NotFound +getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource +getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case + [] -> case configDbRootSpec of + Just (QualifiedIdentifier _ pathName) -> Right $ ResourceRoutine pathName + Nothing | configOpenApiMode == OADisabled -> Left NotFound + | otherwise -> Right ResourceSchema + [table] -> Right $ ResourceRelation table + ["rpc", pName] -> Right $ ResourceRoutine pName + _ -> Left NotFound + +getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action +getAction resource schema method = + case (resource, method) of + (ResourceRoutine rout, "HEAD") -> Right $ ActRoutine (qi rout) $ ActInvoke $ InvRead True + (ResourceRoutine rout, "GET") -> Right $ ActRoutine (qi rout) $ ActInvoke $ InvRead False + (ResourceRoutine rout, "POST") -> Right $ ActRoutine (qi rout) $ ActInvoke Inv + (ResourceRoutine rout, "OPTIONS") -> Right $ ActRoutine (qi rout) ActRoutInfo + (ResourceRoutine _, _) -> Left $ InvalidRpcMethod method + + (ResourceRelation rel, "HEAD") -> Right $ ActRelation (qi rel) $ ActRead True + (ResourceRelation rel, "GET") -> Right $ ActRelation (qi rel) $ ActRead False + (ResourceRelation rel, "POST") -> Right $ ActRelation (qi rel) $ ActMutate MutationCreate + (ResourceRelation rel, "PUT") -> Right $ ActRelation (qi rel) $ ActMutate MutationSingleUpsert + (ResourceRelation rel, "PATCH") -> Right $ ActRelation (qi rel) $ ActMutate MutationUpdate + (ResourceRelation rel, "DELETE") -> Right $ ActRelation (qi rel) $ ActMutate MutationDelete + (ResourceRelation rel, "OPTIONS") -> Right $ ActRelation (qi rel) ActRelInfo + + (ResourceSchema, "HEAD") -> Right $ ActSchema schema $ ActSchemaRead True + (ResourceSchema, "GET") -> Right $ ActSchema schema $ ActSchemaRead False + (ResourceSchema, "OPTIONS") -> Right $ ActSchema schema ActSchemaInfo + + _ -> Left $ UnsupportedMethod method + where + qi = QualifiedIdentifier schema -getAction :: PathInfo -> ByteString -> Either ApiRequestError Action -getAction PathInfo{pathIsProc, pathIsDefSpec} method = - if pathIsProc && method `notElem` ["HEAD", "GET", "POST", "OPTIONS"] - then Left $ InvalidRpcMethod method - else case method of - -- The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response - -- From https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 - "HEAD" | pathIsDefSpec -> Right $ ActionInspect{isHead=True} - | pathIsProc -> Right $ ActionInvoke InvHead - | otherwise -> Right $ ActionRead{isHead=True} - "GET" | pathIsDefSpec -> Right $ ActionInspect{isHead=False} - | pathIsProc -> Right $ ActionInvoke InvGet - | otherwise -> Right $ ActionRead{isHead=False} - "POST" | pathIsProc -> Right $ ActionInvoke InvPost - | otherwise -> Right $ ActionMutate MutationCreate - "PATCH" -> Right $ ActionMutate MutationUpdate - "PUT" -> Right $ ActionMutate MutationSingleUpsert - "DELETE" -> Right $ ActionMutate MutationDelete - "OPTIONS" -> Right ActionInfo - _ -> Left $ UnsupportedMethod method getSchema :: AppConfig -> RequestHeaders -> ByteString -> Either ApiRequestError (Schema, Bool) getSchema AppConfig{configDbSchemas} hdrs method = do @@ -241,8 +244,8 @@ getRanges method QueryParams{qsOrder,qsRanges} hdrs isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange) topLevelRange = fromMaybe allRange $ HM.lookup "limit" ranges -- if no limit is specified, get all the request rows -getPayload :: RequestBody -> MediaType -> QueryParams.QueryParams -> Action -> PathInfo -> Either ApiRequestError (Maybe Payload, S.Set FieldName) -getPayload reqBody contentMediaType QueryParams{qsColumns} action PathInfo{pathIsProc}= do +getPayload :: RequestBody -> MediaType -> QueryParams.QueryParams -> Action -> Either ApiRequestError (Maybe Payload, S.Set FieldName) +getPayload reqBody contentMediaType QueryParams{qsColumns} action = do checkedPayload <- if shouldParsePayload then payload else Right Nothing let cols = case (checkedPayload, columns) of (Just ProcessedJSON{payKeys}, _) -> payKeys @@ -252,12 +255,12 @@ getPayload reqBody contentMediaType QueryParams{qsColumns} action PathInfo{pathI return (checkedPayload, cols) where payload :: Either ApiRequestError (Maybe Payload) - payload = mapBoth InvalidBody Just $ case (contentMediaType, pathIsProc) of + payload = mapBoth InvalidBody Just $ case (contentMediaType, isProc) of (MTApplicationJSON, _) -> if isJust columns then Right $ RawJSON reqBody else note "All object keys must match" . payloadAttributes reqBody - =<< if LBS.null reqBody && pathIsProc + =<< if LBS.null reqBody && isProc then Right emptyObject else first BS.pack $ -- Drop parsing error message in favor of generic one (https://github.com/PostgREST/postgrest/issues/2344) @@ -265,30 +268,32 @@ getPayload reqBody contentMediaType QueryParams{qsColumns} action PathInfo{pathI (MTTextCSV, _) -> do json <- csvToJson <$> first BS.pack (CSV.decodeByName reqBody) note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json - (MTUrlEncoded, isProc) -> do - let params = (T.decodeUtf8 *** T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) - if isProc - then Right $ ProcessedUrlEncoded params (S.fromList $ fst <$> params) - else - let paramsMap = HM.fromList $ (identity *** JSON.String) <$> params in - Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap) + (MTUrlEncoded, True) -> + Right $ ProcessedUrlEncoded params (S.fromList $ fst <$> params) + (MTUrlEncoded, False) -> + let paramsMap = HM.fromList $ (identity *** JSON.String) <$> params in + Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap) (MTTextPlain, True) -> Right $ RawPay reqBody (MTTextXML, True) -> Right $ RawPay reqBody (MTOctetStream, True) -> Right $ RawPay reqBody (ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct - shouldParsePayload = case (action, contentMediaType) of - (ActionMutate MutationCreate, _) -> True - (ActionInvoke InvPost, _) -> True - (ActionMutate MutationSingleUpsert, _) -> True - (ActionMutate MutationUpdate, _) -> True - _ -> False + shouldParsePayload = case action of + ActRelation _ (ActMutate MutationDelete) -> False + ActRelation _ (ActMutate _) -> True + ActRoutine _ (ActInvoke Inv) -> True + _ -> False columns = case action of - ActionMutate MutationCreate -> qsColumns - ActionMutate MutationUpdate -> qsColumns - ActionInvoke InvPost -> qsColumns - _ -> Nothing + ActRelation _ (ActMutate MutationCreate) -> qsColumns + ActRelation _ (ActMutate MutationUpdate) -> qsColumns + ActRoutine _ (ActInvoke Inv) -> qsColumns + _ -> Nothing + + isProc = case action of + ActRoutine _ _ -> True + _ -> False + params = (T.decodeUtf8 *** T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) type CsvData = V.Vector (M.Map Text LBS.ByteString) diff --git a/src/PostgREST/ApiRequest/QueryParams.hs b/src/PostgREST/ApiRequest/QueryParams.hs index 7b2738357..f9150e04e 100644 --- a/src/PostgREST/ApiRequest/QueryParams.hs +++ b/src/PostgREST/ApiRequest/QueryParams.hs @@ -112,12 +112,12 @@ data QueryParams = -- >>> qsFilters <$> parse False "a.b=noop.0" -- Left (QPError "\"failed to parse filter (noop.0)\" (line 1, column 1)" "unexpected \"o\" expecting \"not\" or operator (eq, gt, ...)") parse :: Bool -> ByteString -> Either QPError QueryParams -parse isRpcGet qs = do +parse isRpcRead qs = do rOrd <- pRequestOrder `traverse` order rLogic <- pRequestLogicTree `traverse` logic rCols <- pRequestColumns columns rSel <- pRequestSelect select - (rFlts, params) <- L.partition hasOp <$> pRequestFilter isRpcGet `traverse` filters + (rFlts, params) <- L.partition hasOp <$> pRequestFilter isRpcRead `traverse` filters (rFltsRoot, rFltsNotRoot) <- pure $ L.partition hasRootFilter rFlts rOnConflict <- pRequestOnConflict `traverse` onConflict @@ -226,11 +226,11 @@ pRequestOnConflict oncStr = -- >>> pRequestFilter True ("id", "val") -- Right ([],Filter {field = ("id",[]), opExpr = NoOpExpr "val"}) pRequestFilter :: Bool -> (Text, Text) -> Either QPError (EmbedPath, Filter) -pRequestFilter isRpcGet (k, v) = mapError $ (,) <$> path <*> (Filter <$> fld <*> oper) +pRequestFilter isRpcRead (k, v) = mapError $ (,) <$> path <*> (Filter <$> fld <*> oper) where treePath = P.parse pTreePath ("failed to parse tree path (" ++ toS k ++ ")") $ toS k oper = P.parse parseFlt ("failed to parse filter (" ++ toS v ++ ")") $ toS v - parseFlt = if isRpcGet + parseFlt = if isRpcRead then pOpExpr pSingleVal <|> pure (NoOpExpr v) else pOpExpr pSingleVal path = fst <$> treePath diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index d58a284cc..dc336f0d9 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -30,21 +30,23 @@ import qualified Hasql.Transaction.Sessions as SQL import qualified Network.Wai as Wai import qualified Network.Wai.Handler.Warp as Warp -import qualified PostgREST.Admin as Admin -import qualified PostgREST.ApiRequest as ApiRequest -import qualified PostgREST.ApiRequest.Types as ApiRequestTypes -import qualified PostgREST.AppState as AppState -import qualified PostgREST.Auth as Auth -import qualified PostgREST.Cors as Cors -import qualified PostgREST.Error as Error -import qualified PostgREST.Logger as Logger -import qualified PostgREST.Plan as Plan -import qualified PostgREST.Query as Query -import qualified PostgREST.Response as Response -import qualified PostgREST.Unix as Unix (installSignalHandlers) +import qualified PostgREST.Admin as Admin +import qualified PostgREST.ApiRequest as ApiRequest +import qualified PostgREST.AppState as AppState +import qualified PostgREST.Auth as Auth +import qualified PostgREST.Cors as Cors +import qualified PostgREST.Error as Error +import qualified PostgREST.Logger as Logger +import qualified PostgREST.Plan as Plan +import qualified PostgREST.Query as Query +import qualified PostgREST.Response as Response +import qualified PostgREST.Unix as Unix (installSignalHandlers) -import PostgREST.ApiRequest (Action (..), ApiRequest (..), - Mutation (..), Target (..)) +import PostgREST.ApiRequest (Action (..), + ActionRelation (..), + ActionRoutine (..), + ActionSchema (..), + ApiRequest (..), Mutation (..)) import PostgREST.AppState (AppState) import PostgREST.Auth (AuthResult (..)) import PostgREST.Config (AppConfig (..)) @@ -170,66 +172,62 @@ runDbHandler appState config isoLvl mode authenticated prepared observer handler handleRequest :: AuthResult -> AppConfig -> AppState.AppState -> Bool -> Bool -> PgVersion -> ApiRequest -> SchemaCache -> Maybe Double -> Maybe Double -> (Observation -> IO ()) -> Handler IO Wai.Response handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@ApiRequest{..} sCache jwtTime parseTime observer = - case (iAction, iTarget) of - (ActionRead headersOnly, TargetIdent identifier) -> do + case iAction of + ActRelation identifier (ActRead headersOnly) -> do (planTime', wrPlan) <- withTiming $ liftEither $ Plan.wrappedReadPlan identifier conf sCache apiReq (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.wrTxMode wrPlan) $ Query.readQuery wrPlan conf apiReq (respTime', pgrst) <- withTiming $ liftEither $ Response.readResponse wrPlan headersOnly identifier apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionMutate MutationCreate, TargetIdent identifier) -> do + ActRelation identifier (ActMutate MutationCreate) -> do (planTime', mrPlan) <- withTiming $ liftEither $ Plan.mutateReadPlan MutationCreate apiReq identifier conf sCache (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.createQuery mrPlan apiReq conf (respTime', pgrst) <- withTiming $ liftEither $ Response.createResponse identifier mrPlan apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionMutate MutationUpdate, TargetIdent identifier) -> do + ActRelation identifier (ActMutate MutationUpdate) -> do (planTime', mrPlan) <- withTiming $ liftEither $ Plan.mutateReadPlan MutationUpdate apiReq identifier conf sCache (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.updateQuery mrPlan apiReq conf (respTime', pgrst) <- withTiming $ liftEither $ Response.updateResponse mrPlan apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionMutate MutationSingleUpsert, TargetIdent identifier) -> do + ActRelation identifier (ActMutate MutationSingleUpsert) -> do (planTime', mrPlan) <- withTiming $ liftEither $ Plan.mutateReadPlan MutationSingleUpsert apiReq identifier conf sCache (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.singleUpsertQuery mrPlan apiReq conf (respTime', pgrst) <- withTiming $ liftEither $ Response.singleUpsertResponse mrPlan apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionMutate MutationDelete, TargetIdent identifier) -> do + ActRelation identifier (ActMutate MutationDelete) -> do (planTime', mrPlan) <- withTiming $ liftEither $ Plan.mutateReadPlan MutationDelete apiReq identifier conf sCache (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.deleteQuery mrPlan apiReq conf (respTime', pgrst) <- withTiming $ liftEither $ Response.deleteResponse mrPlan apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionInvoke invMethod, TargetProc identifier _) -> do + ActRoutine identifier (ActInvoke invMethod) -> do (planTime', cPlan) <- withTiming $ liftEither $ Plan.callReadPlan identifier conf sCache apiReq invMethod (txTime', resultSet) <- withTiming $ runQuery (fromMaybe roleIsoLvl $ pdIsoLvl (Plan.crProc cPlan)) (pdFuncSettings $ Plan.crProc cPlan) (Plan.crTxMode cPlan) $ Query.invokeQuery (Plan.crProc cPlan) cPlan apiReq conf pgVer (respTime', pgrst) <- withTiming $ liftEither $ Response.invokeResponse cPlan invMethod (Plan.crProc cPlan) apiReq resultSet return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do + ActSchema tSchema (ActSchemaRead headersOnly) -> do (planTime', iPlan) <- withTiming $ liftEither $ Plan.inspectPlan apiReq (txTime', oaiResult) <- withTiming $ runQuery roleIsoLvl mempty (Plan.ipTxmode iPlan) $ Query.openApiQuery sCache pgVer conf tSchema (respTime', pgrst) <- withTiming $ liftEither $ Response.openApiResponse (T.decodeUtf8 prettyVersion, docsVersion) headersOnly oaiResult conf sCache iSchema iNegotiatedByProfile return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst - (ActionInfo, TargetIdent identifier) -> do + ActRelation identifier ActRelInfo -> do (respTime', pgrst) <- withTiming $ liftEither $ Response.infoIdentResponse identifier sCache return $ pgrstResponse (ServerTiming jwtTime parseTime Nothing Nothing respTime') pgrst - (ActionInfo, TargetProc identifier _) -> do - (planTime', cPlan) <- withTiming $ liftEither $ Plan.callReadPlan identifier conf sCache apiReq ApiRequest.InvHead + ActRoutine identifier ActRoutInfo -> do + (planTime', cPlan) <- withTiming $ liftEither $ Plan.callReadPlan identifier conf sCache apiReq $ ApiRequest.InvRead True (respTime', pgrst) <- withTiming $ liftEither $ Response.infoProcResponse (Plan.crProc cPlan) return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' Nothing respTime') pgrst - (ActionInfo, TargetDefaultSpec _) -> do + ActSchema _ ActSchemaInfo -> do (respTime', pgrst) <- withTiming $ liftEither Response.infoRootResponse return $ pgrstResponse (ServerTiming jwtTime parseTime Nothing Nothing respTime') pgrst - _ -> - -- This is unreachable as the ApiRequest.hs rejects it before - -- TODO Refactor the Action/Target types to remove this line - throwError $ Error.ApiRequestError ApiRequestTypes.NotFound where roleSettings = fromMaybe mempty (HM.lookup authRole $ configRoleSettings conf) roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index 564a6fe58..848303066 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -38,6 +38,8 @@ import Data.List (delete, lookup) import Data.Tree (Tree (..)) import PostgREST.ApiRequest (Action (..), + ActionRelation (..), + ActionRoutine (..), ApiRequest (..), InvokeMethod (..), Mutation (..), @@ -139,24 +141,21 @@ mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..} callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} invMethod = do let paramKeys = case invMethod of - InvGet -> S.fromList $ fst <$> qsParams' - InvHead -> S.fromList $ fst <$> qsParams' - InvPost -> iColumns + InvRead _ -> S.fromList $ fst <$> qsParams' + Inv -> iColumns proc@Function{..} <- mapLeft ApiRequestError $ - findProc identifier paramKeys (preferParameters == Just SingleObject) (dbRoutines sCache) iContentMediaType (invMethod == InvPost) + findProc identifier paramKeys (preferParameters == Just SingleObject) (dbRoutines sCache) iContentMediaType (invMethod == Inv) let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations rPlan <- readPlan relIdentifier conf sCache apiRequest let args = case (invMethod, iContentMediaType) of - (InvGet, _) -> jsonRpcParams proc qsParams' - (InvHead, _) -> jsonRpcParams proc qsParams' - (InvPost, MTUrlEncoded) -> maybe mempty (jsonRpcParams proc . payArray) iPayload - (InvPost, _) -> maybe mempty payRaw iPayload + (InvRead _, _) -> jsonRpcParams proc qsParams' + (Inv, MTUrlEncoded) -> maybe mempty (jsonRpcParams proc . payArray) iPayload + (Inv, _) -> maybe mempty payRaw iPayload txMode = case (invMethod, pdVolatility) of - (InvGet, _) -> SQL.Read - (InvHead, _) -> SQL.Read - (InvPost, Routine.Stable) -> SQL.Read - (InvPost, Routine.Immutable) -> SQL.Read - (InvPost, Routine.Volatile) -> SQL.Write + (InvRead _, _) -> SQL.Read + (Inv, Routine.Stable) -> SQL.Read + (Inv, Routine.Immutable) -> SQL.Read + (Inv, Routine.Volatile) -> SQL.Write cPlan = callPlan proc apiRequest paramKeys args rPlan (handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest relIdentifier iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan) if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right () @@ -425,7 +424,7 @@ expandStarsForTable ctx@ResolverContext{representations, outputType} hasAgg rp@R -- | Enforces the `max-rows` config on the result treeRestrictRange :: Maybe Integer -> Action -> ReadPlanTree -> Either ApiRequestError ReadPlanTree -treeRestrictRange _ (ActionMutate _) request = Right request +treeRestrictRange _ (ActRelation _ (ActMutate _)) request = Right request treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> request where nodeRestrictRange :: Maybe Integer -> ReadPlan -> ReadPlan @@ -462,9 +461,9 @@ addRels schema action allRels parentNode (Node rPlan@ReadPlan{relName,relHint,re newReadPlan = case action of -- the CTE for mutations/rpc is used as WITH sourceCTEName .. SELECT .. FROM sourceCTEName as alias, -- we use the table name as an alias so findRel can find the right relationship. - ActionMutate _ -> rPlan{from=newFrom, fromAlias=newAlias} - ActionInvoke _ -> rPlan{from=newFrom, fromAlias=newAlias} - _ -> rPlan + ActRelation _ (ActMutate _) -> rPlan{from=newFrom, fromAlias=newAlias} + ActRoutine _ _ -> rPlan{from=newFrom, fromAlias=newAlias} + _ -> rPlan in Node newReadPlan <$> updateForest (Just $ Node newReadPlan forest) where @@ -702,9 +701,9 @@ addFilters ctx ApiRequest{..} rReq = QueryParams.QueryParams{..} = iQueryParams flts = case iAction of - ActionInvoke _ -> qsFilters - ActionRead _ -> qsFilters - _ -> qsFiltersNotRoot + ActRelation _ (ActRead _) -> qsFilters + ActRoutine _ _ -> qsFilters + _ -> qsFiltersNotRoot addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree addFilterToNode = @@ -713,8 +712,8 @@ addFilters ctx ApiRequest{..} rReq = addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addOrders ctx ApiRequest{..} rReq = case iAction of - ActionMutate _ -> Right rReq - _ -> foldr addOrderToNode (Right rReq) qsOrder + ActRelation _ (ActMutate _) -> Right rReq + _ -> foldr addOrderToNode (Right rReq) qsOrder where QueryParams.QueryParams{..} = iQueryParams @@ -834,8 +833,8 @@ addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addRanges ApiRequest{..} rReq = case iAction of - ActionMutate _ -> Right rReq - _ -> foldr addRangeToNode (Right rReq) =<< ranges + ActRelation _ (ActMutate _) -> Right rReq + _ -> foldr addRangeToNode (Right rReq) =<< ranges where ranges :: Either ApiRequestError [(EmbedPath, NonnegRange)] ranges = first QueryParamError $ QueryParams.pRequestRange `traverse` HM.toList iRange @@ -997,13 +996,13 @@ addFilterToLogicForest flt lf = CoercibleStmnt flt : lf negotiateContent :: AppConfig -> ApiRequest -> QualifiedIdentifier -> [MediaType] -> MediaHandlerMap -> Bool -> Either ApiRequestError ResolvedHandler negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRepresentation=rep}} identifier accepts produces defaultSelect = case (act, firstAcceptedPick) of - (_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts - (ActionMutate _, Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg, mt) + (_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts + (ActRelation _ (ActMutate _), Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg, mt) -- 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. - (ActionRead True, Just (_, mt)) -> Right (NoAgg, mt) - (ActionInvoke InvHead, Just (_, mt)) -> Right (NoAgg, mt) - (_, Just (x, mt)) -> Right (x, mt) + (ActRelation _ (ActRead True), Just (_, mt)) -> Right (NoAgg, mt) + (ActRoutine _ (ActInvoke (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. matchMT mt = case mt of diff --git a/src/PostgREST/Response.hs b/src/PostgREST/Response.hs index 2404764ce..dfb58639f 100644 --- a/src/PostgREST/Response.hs +++ b/src/PostgREST/Response.hs @@ -252,7 +252,7 @@ invokeResponse CallReadPlan{crMedia} invMethod proc ctxApiRequest@ApiRequest{iPr else (status, headers ++ contentTypeHeaders crMedia ctxApiRequest, - if invMethod == InvHead then mempty else rsOrErrBody) + if invMethod == InvRead True then mempty else rsOrErrBody) (ovStatus, ovHeaders) <- overrideStatusHeaders rsGucStatus rsGucHeaders status' headers'