refactor: remove unreacheable 404

This commit is contained in:
steve-chavez
2024-03-25 18:17:01 +01:00
committed by Steve Chavez
parent f1f01f1f5c
commit a5bb20bbf8
5 changed files with 151 additions and 149 deletions
+90 -85
View File
@@ -2,10 +2,8 @@
Module : PostgREST.Request.ApiRequest Module : PostgREST.Request.ApiRequest
Description : PostgREST functions to translate HTTP request to a domain type called ApiRequest. Description : PostgREST functions to translate HTTP request to a domain type called ApiRequest.
-} -}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module PostgREST.ApiRequest module PostgREST.ApiRequest
( ApiRequest(..) ( ApiRequest(..)
@@ -13,7 +11,9 @@ module PostgREST.ApiRequest
, Mutation(..) , Mutation(..)
, MediaType(..) , MediaType(..)
, Action(..) , Action(..)
, Target(..) , ActionRelation(..)
, ActionRoutine(..)
, ActionSchema(..)
, Payload(..) , Payload(..)
, userApiRequest , userApiRequest
) where ) where
@@ -84,29 +84,31 @@ data Payload
| RawJSON { payRaw :: LBS.ByteString } | RawJSON { payRaw :: LBS.ByteString }
| RawPay { 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 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 data Action
= ActionMutate Mutation = ActRelation QualifiedIdentifier ActionRelation
| ActionRead {isHead :: Bool} | ActRoutine QualifiedIdentifier ActionRoutine
| ActionInvoke InvokeMethod | ActSchema Schema ActionSchema
| 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 "/"
{-| {-|
Describes what the user wants to do. This data type is a 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. if it is an action we are able to perform.
-} -}
data ApiRequest = ApiRequest { 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 , 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
, 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 , iPayload :: Maybe Payload -- ^ Data sent by client and used for mutation actions
, iPreferences :: Preferences.Preferences -- ^ Prefer header values , iPreferences :: Preferences.Preferences -- ^ Prefer header values
, iQueryParams :: QueryParams.QueryParams , iQueryParams :: QueryParams.QueryParams
@@ -137,17 +138,14 @@ data ApiRequest = ApiRequest {
-- | Examines HTTP request and translates it into user intent. -- | Examines HTTP request and translates it into user intent.
userApiRequest :: AppConfig -> Request -> RequestBody -> SchemaCache -> Either ApiRequestError ApiRequest userApiRequest :: AppConfig -> Request -> RequestBody -> SchemaCache -> Either ApiRequestError ApiRequest
userApiRequest conf req reqBody sCache = do userApiRequest conf req reqBody sCache = do
pInfo@PathInfo{..} <- getPathInfo conf $ pathInfo req resource <- getResource conf $ pathInfo req
act <- getAction pInfo method
qPrms <- first QueryParamError $ QueryParams.parse (pathIsProc && act `elem` [ActionInvoke InvGet, ActionInvoke InvHead]) $ rawQueryString req
(schema, negotiatedByProfile) <- getSchema conf hdrs method (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 (topLevelRange, ranges) <- getRanges method qPrms hdrs
(payload, columns) <- getPayload reqBody contentMediaType qPrms act pInfo (payload, columns) <- getPayload reqBody contentMediaType qPrms act
return $ ApiRequest { return $ ApiRequest {
iAction = act iAction = act
, iTarget = if | pathIsProc -> TargetProc (QualifiedIdentifier schema pathName) pathIsRootSpec
| pathIsDefSpec -> TargetDefaultSpec schema
| otherwise -> TargetIdent $ QualifiedIdentifier schema pathName
, iRange = ranges , iRange = ranges
, iTopLevelRange = topLevelRange , iTopLevelRange = topLevelRange
, iPayload = payload , iPayload = payload
@@ -170,38 +168,43 @@ userApiRequest conf req reqBody sCache = do
iHdrs = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie] iHdrs = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie]
iCkies = maybe [] parseCookies $ lookupHeader "Cookie" iCkies = maybe [] parseCookies $ lookupHeader "Cookie"
contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type" contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
actIsInvokeSafe x = case x of {ActRoutine _ (ActInvoke (InvRead _)) -> True; _ -> False}
getPathInfo :: AppConfig -> [Text] -> Either ApiRequestError PathInfo getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path = getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case
case path of [] -> case configDbRootSpec of
[] -> case configDbRootSpec of Just (QualifiedIdentifier _ pathName) -> Right $ ResourceRoutine pathName
Just (QualifiedIdentifier _ pathName) -> Right $ PathInfo pathName True False True Nothing | configOpenApiMode == OADisabled -> Left NotFound
Nothing | configOpenApiMode == OADisabled -> Left NotFound | otherwise -> Right ResourceSchema
| otherwise -> Right $ PathInfo mempty False True False [table] -> Right $ ResourceRelation table
[table] -> Right $ PathInfo table False False False ["rpc", pName] -> Right $ ResourceRoutine pName
["rpc", pName] -> Right $ PathInfo pName True False False _ -> Left NotFound
_ -> 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 -> RequestHeaders -> ByteString -> Either ApiRequestError (Schema, Bool)
getSchema AppConfig{configDbSchemas} hdrs method = do getSchema AppConfig{configDbSchemas} hdrs method = do
@@ -241,8 +244,8 @@ getRanges method QueryParams{qsOrder,qsRanges} hdrs
isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange) isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange)
topLevelRange = fromMaybe allRange $ HM.lookup "limit" ranges -- if no limit is specified, get all the request rows 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 :: RequestBody -> MediaType -> QueryParams.QueryParams -> Action -> Either ApiRequestError (Maybe Payload, S.Set FieldName)
getPayload reqBody contentMediaType QueryParams{qsColumns} action PathInfo{pathIsProc}= do getPayload reqBody contentMediaType QueryParams{qsColumns} action = do
checkedPayload <- if shouldParsePayload then payload else Right Nothing checkedPayload <- if shouldParsePayload then payload else Right Nothing
let cols = case (checkedPayload, columns) of let cols = case (checkedPayload, columns) of
(Just ProcessedJSON{payKeys}, _) -> payKeys (Just ProcessedJSON{payKeys}, _) -> payKeys
@@ -252,12 +255,12 @@ getPayload reqBody contentMediaType QueryParams{qsColumns} action PathInfo{pathI
return (checkedPayload, cols) return (checkedPayload, cols)
where where
payload :: Either ApiRequestError (Maybe Payload) payload :: Either ApiRequestError (Maybe Payload)
payload = mapBoth InvalidBody Just $ case (contentMediaType, pathIsProc) of payload = mapBoth InvalidBody Just $ case (contentMediaType, isProc) of
(MTApplicationJSON, _) -> (MTApplicationJSON, _) ->
if isJust columns if isJust columns
then Right $ RawJSON reqBody then Right $ RawJSON reqBody
else note "All object keys must match" . payloadAttributes reqBody else note "All object keys must match" . payloadAttributes reqBody
=<< if LBS.null reqBody && pathIsProc =<< if LBS.null reqBody && isProc
then Right emptyObject then Right emptyObject
else first BS.pack $ else first BS.pack $
-- Drop parsing error message in favor of generic one (https://github.com/PostgREST/postgrest/issues/2344) -- 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 (MTTextCSV, _) -> do
json <- csvToJson <$> first BS.pack (CSV.decodeByName reqBody) json <- csvToJson <$> first BS.pack (CSV.decodeByName reqBody)
note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json
(MTUrlEncoded, isProc) -> do (MTUrlEncoded, True) ->
let params = (T.decodeUtf8 *** T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) Right $ ProcessedUrlEncoded params (S.fromList $ fst <$> params)
if isProc (MTUrlEncoded, False) ->
then Right $ ProcessedUrlEncoded params (S.fromList $ fst <$> params) let paramsMap = HM.fromList $ (identity *** JSON.String) <$> params in
else Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap)
let paramsMap = HM.fromList $ (identity *** JSON.String) <$> params in
Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap)
(MTTextPlain, True) -> Right $ RawPay reqBody (MTTextPlain, True) -> Right $ RawPay reqBody
(MTTextXML, True) -> Right $ RawPay reqBody (MTTextXML, True) -> Right $ RawPay reqBody
(MTOctetStream, True) -> Right $ RawPay reqBody (MTOctetStream, True) -> Right $ RawPay reqBody
(ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct (ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct
shouldParsePayload = case (action, contentMediaType) of shouldParsePayload = case action of
(ActionMutate MutationCreate, _) -> True ActRelation _ (ActMutate MutationDelete) -> False
(ActionInvoke InvPost, _) -> True ActRelation _ (ActMutate _) -> True
(ActionMutate MutationSingleUpsert, _) -> True ActRoutine _ (ActInvoke Inv) -> True
(ActionMutate MutationUpdate, _) -> True _ -> False
_ -> False
columns = case action of columns = case action of
ActionMutate MutationCreate -> qsColumns ActRelation _ (ActMutate MutationCreate) -> qsColumns
ActionMutate MutationUpdate -> qsColumns ActRelation _ (ActMutate MutationUpdate) -> qsColumns
ActionInvoke InvPost -> qsColumns ActRoutine _ (ActInvoke Inv) -> qsColumns
_ -> Nothing _ -> 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) type CsvData = V.Vector (M.Map Text LBS.ByteString)
+4 -4
View File
@@ -112,12 +112,12 @@ data QueryParams =
-- >>> qsFilters <$> parse False "a.b=noop.0" -- >>> 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, ...)") -- 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 :: Bool -> ByteString -> Either QPError QueryParams
parse isRpcGet qs = do parse isRpcRead qs = do
rOrd <- pRequestOrder `traverse` order rOrd <- pRequestOrder `traverse` order
rLogic <- pRequestLogicTree `traverse` logic rLogic <- pRequestLogicTree `traverse` logic
rCols <- pRequestColumns columns rCols <- pRequestColumns columns
rSel <- pRequestSelect select 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 (rFltsRoot, rFltsNotRoot) <- pure $ L.partition hasRootFilter rFlts
rOnConflict <- pRequestOnConflict `traverse` onConflict rOnConflict <- pRequestOnConflict `traverse` onConflict
@@ -226,11 +226,11 @@ pRequestOnConflict oncStr =
-- >>> pRequestFilter True ("id", "val") -- >>> pRequestFilter True ("id", "val")
-- Right ([],Filter {field = ("id",[]), opExpr = NoOpExpr "val"}) -- Right ([],Filter {field = ("id",[]), opExpr = NoOpExpr "val"})
pRequestFilter :: Bool -> (Text, Text) -> Either QPError (EmbedPath, Filter) 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 where
treePath = P.parse pTreePath ("failed to parse tree path (" ++ toS k ++ ")") $ toS k 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 oper = P.parse parseFlt ("failed to parse filter (" ++ toS v ++ ")") $ toS v
parseFlt = if isRpcGet parseFlt = if isRpcRead
then pOpExpr pSingleVal <|> pure (NoOpExpr v) then pOpExpr pSingleVal <|> pure (NoOpExpr v)
else pOpExpr pSingleVal else pOpExpr pSingleVal
path = fst <$> treePath path = fst <$> treePath
+28 -30
View File
@@ -30,21 +30,23 @@ import qualified Hasql.Transaction.Sessions as SQL
import qualified Network.Wai as Wai import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp as Warp
import qualified PostgREST.Admin as Admin import qualified PostgREST.Admin as Admin
import qualified PostgREST.ApiRequest as ApiRequest import qualified PostgREST.ApiRequest as ApiRequest
import qualified PostgREST.ApiRequest.Types as ApiRequestTypes import qualified PostgREST.AppState as AppState
import qualified PostgREST.AppState as AppState import qualified PostgREST.Auth as Auth
import qualified PostgREST.Auth as Auth import qualified PostgREST.Cors as Cors
import qualified PostgREST.Cors as Cors import qualified PostgREST.Error as Error
import qualified PostgREST.Error as Error import qualified PostgREST.Logger as Logger
import qualified PostgREST.Logger as Logger import qualified PostgREST.Plan as Plan
import qualified PostgREST.Plan as Plan import qualified PostgREST.Query as Query
import qualified PostgREST.Query as Query import qualified PostgREST.Response as Response
import qualified PostgREST.Response as Response import qualified PostgREST.Unix as Unix (installSignalHandlers)
import qualified PostgREST.Unix as Unix (installSignalHandlers)
import PostgREST.ApiRequest (Action (..), ApiRequest (..), import PostgREST.ApiRequest (Action (..),
Mutation (..), Target (..)) ActionRelation (..),
ActionRoutine (..),
ActionSchema (..),
ApiRequest (..), Mutation (..))
import PostgREST.AppState (AppState) import PostgREST.AppState (AppState)
import PostgREST.Auth (AuthResult (..)) import PostgREST.Auth (AuthResult (..))
import PostgREST.Config (AppConfig (..)) 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 -> handleRequest :: AuthResult -> AppConfig -> AppState.AppState -> Bool -> Bool -> PgVersion -> ApiRequest -> SchemaCache ->
Maybe Double -> Maybe Double -> (Observation -> IO ()) -> Handler IO Wai.Response Maybe Double -> Maybe Double -> (Observation -> IO ()) -> Handler IO Wai.Response
handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@ApiRequest{..} sCache jwtTime parseTime observer = handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@ApiRequest{..} sCache jwtTime parseTime observer =
case (iAction, iTarget) of case iAction of
(ActionRead headersOnly, TargetIdent identifier) -> do ActRelation identifier (ActRead headersOnly) -> do
(planTime', wrPlan) <- withTiming $ liftEither $ Plan.wrappedReadPlan identifier conf sCache apiReq (planTime', wrPlan) <- withTiming $ liftEither $ Plan.wrappedReadPlan identifier conf sCache apiReq
(txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.wrTxMode wrPlan) $ Query.readQuery wrPlan conf 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 (respTime', pgrst) <- withTiming $ liftEither $ Response.readResponse wrPlan headersOnly identifier apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (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 (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.createQuery mrPlan apiReq conf
(respTime', pgrst) <- withTiming $ liftEither $ Response.createResponse identifier mrPlan apiReq resultSet (respTime', pgrst) <- withTiming $ liftEither $ Response.createResponse identifier mrPlan apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (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 (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.updateQuery mrPlan apiReq conf
(respTime', pgrst) <- withTiming $ liftEither $ Response.updateResponse mrPlan apiReq resultSet (respTime', pgrst) <- withTiming $ liftEither $ Response.updateResponse mrPlan apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (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 (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.singleUpsertQuery mrPlan apiReq conf
(respTime', pgrst) <- withTiming $ liftEither $ Response.singleUpsertResponse mrPlan apiReq resultSet (respTime', pgrst) <- withTiming $ liftEither $ Response.singleUpsertResponse mrPlan apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (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 (txTime', resultSet) <- withTiming $ runQuery roleIsoLvl mempty (Plan.mrTxMode mrPlan) $ Query.deleteQuery mrPlan apiReq conf
(respTime', pgrst) <- withTiming $ liftEither $ Response.deleteResponse mrPlan apiReq resultSet (respTime', pgrst) <- withTiming $ liftEither $ Response.deleteResponse mrPlan apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (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 (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 (respTime', pgrst) <- withTiming $ liftEither $ Response.invokeResponse cPlan invMethod (Plan.crProc cPlan) apiReq resultSet
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst 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 (planTime', iPlan) <- withTiming $ liftEither $ Plan.inspectPlan apiReq
(txTime', oaiResult) <- withTiming $ runQuery roleIsoLvl mempty (Plan.ipTxmode iPlan) $ Query.openApiQuery sCache pgVer conf tSchema (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 (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 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 (respTime', pgrst) <- withTiming $ liftEither $ Response.infoIdentResponse identifier sCache
return $ pgrstResponse (ServerTiming jwtTime parseTime Nothing Nothing respTime') pgrst return $ pgrstResponse (ServerTiming jwtTime parseTime Nothing Nothing respTime') pgrst
(ActionInfo, TargetProc identifier _) -> do ActRoutine identifier ActRoutInfo -> do
(planTime', cPlan) <- withTiming $ liftEither $ Plan.callReadPlan identifier conf sCache apiReq ApiRequest.InvHead (planTime', cPlan) <- withTiming $ liftEither $ Plan.callReadPlan identifier conf sCache apiReq $ ApiRequest.InvRead True
(respTime', pgrst) <- withTiming $ liftEither $ Response.infoProcResponse (Plan.crProc cPlan) (respTime', pgrst) <- withTiming $ liftEither $ Response.infoProcResponse (Plan.crProc cPlan)
return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' Nothing respTime') pgrst return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' Nothing respTime') pgrst
(ActionInfo, TargetDefaultSpec _) -> do ActSchema _ ActSchemaInfo -> do
(respTime', pgrst) <- withTiming $ liftEither Response.infoRootResponse (respTime', pgrst) <- withTiming $ liftEither Response.infoRootResponse
return $ pgrstResponse (ServerTiming jwtTime parseTime Nothing Nothing respTime') pgrst 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 where
roleSettings = fromMaybe mempty (HM.lookup authRole $ configRoleSettings conf) roleSettings = fromMaybe mempty (HM.lookup authRole $ configRoleSettings conf)
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf
+28 -29
View File
@@ -38,6 +38,8 @@ import Data.List (delete, lookup)
import Data.Tree (Tree (..)) import Data.Tree (Tree (..))
import PostgREST.ApiRequest (Action (..), import PostgREST.ApiRequest (Action (..),
ActionRelation (..),
ActionRoutine (..),
ApiRequest (..), ApiRequest (..),
InvokeMethod (..), InvokeMethod (..),
Mutation (..), Mutation (..),
@@ -139,24 +141,21 @@ mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..}
callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan
callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} invMethod = do callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} invMethod = do
let paramKeys = case invMethod of let paramKeys = case invMethod of
InvGet -> S.fromList $ fst <$> qsParams' InvRead _ -> S.fromList $ fst <$> qsParams'
InvHead -> S.fromList $ fst <$> qsParams' Inv -> iColumns
InvPost -> iColumns
proc@Function{..} <- mapLeft ApiRequestError $ 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 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 rPlan <- readPlan relIdentifier conf sCache apiRequest
let args = case (invMethod, iContentMediaType) of let args = case (invMethod, iContentMediaType) of
(InvGet, _) -> jsonRpcParams proc qsParams' (InvRead _, _) -> jsonRpcParams proc qsParams'
(InvHead, _) -> jsonRpcParams proc qsParams' (Inv, MTUrlEncoded) -> maybe mempty (jsonRpcParams proc . payArray) iPayload
(InvPost, MTUrlEncoded) -> maybe mempty (jsonRpcParams proc . payArray) iPayload (Inv, _) -> maybe mempty payRaw iPayload
(InvPost, _) -> maybe mempty payRaw iPayload
txMode = case (invMethod, pdVolatility) of txMode = case (invMethod, pdVolatility) of
(InvGet, _) -> SQL.Read (InvRead _, _) -> SQL.Read
(InvHead, _) -> SQL.Read (Inv, Routine.Stable) -> SQL.Read
(InvPost, Routine.Stable) -> SQL.Read (Inv, Routine.Immutable) -> SQL.Read
(InvPost, Routine.Immutable) -> SQL.Read (Inv, Routine.Volatile) -> SQL.Write
(InvPost, Routine.Volatile) -> SQL.Write
cPlan = callPlan proc apiRequest paramKeys args rPlan cPlan = callPlan proc apiRequest paramKeys args rPlan
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest relIdentifier iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect 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 () 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 -- | Enforces the `max-rows` config on the result
treeRestrictRange :: Maybe Integer -> Action -> ReadPlanTree -> Either ApiRequestError ReadPlanTree 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 treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> request
where where
nodeRestrictRange :: Maybe Integer -> ReadPlan -> ReadPlan nodeRestrictRange :: Maybe Integer -> ReadPlan -> ReadPlan
@@ -462,9 +461,9 @@ addRels schema action allRels parentNode (Node rPlan@ReadPlan{relName,relHint,re
newReadPlan = case action of newReadPlan = case action of
-- the CTE for mutations/rpc is used as WITH sourceCTEName .. SELECT .. FROM sourceCTEName as alias, -- 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. -- we use the table name as an alias so findRel can find the right relationship.
ActionMutate _ -> rPlan{from=newFrom, fromAlias=newAlias} ActRelation _ (ActMutate _) -> rPlan{from=newFrom, fromAlias=newAlias}
ActionInvoke _ -> rPlan{from=newFrom, fromAlias=newAlias} ActRoutine _ _ -> rPlan{from=newFrom, fromAlias=newAlias}
_ -> rPlan _ -> rPlan
in in
Node newReadPlan <$> updateForest (Just $ Node newReadPlan forest) Node newReadPlan <$> updateForest (Just $ Node newReadPlan forest)
where where
@@ -702,9 +701,9 @@ addFilters ctx ApiRequest{..} rReq =
QueryParams.QueryParams{..} = iQueryParams QueryParams.QueryParams{..} = iQueryParams
flts = flts =
case iAction of case iAction of
ActionInvoke _ -> qsFilters ActRelation _ (ActRead _) -> qsFilters
ActionRead _ -> qsFilters ActRoutine _ _ -> qsFilters
_ -> qsFiltersNotRoot _ -> qsFiltersNotRoot
addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
addFilterToNode = addFilterToNode =
@@ -713,8 +712,8 @@ addFilters ctx ApiRequest{..} rReq =
addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
addOrders ctx ApiRequest{..} rReq = addOrders ctx ApiRequest{..} rReq =
case iAction of case iAction of
ActionMutate _ -> Right rReq ActRelation _ (ActMutate _) -> Right rReq
_ -> foldr addOrderToNode (Right rReq) qsOrder _ -> foldr addOrderToNode (Right rReq) qsOrder
where where
QueryParams.QueryParams{..} = iQueryParams QueryParams.QueryParams{..} = iQueryParams
@@ -834,8 +833,8 @@ addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do
addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
addRanges ApiRequest{..} rReq = addRanges ApiRequest{..} rReq =
case iAction of case iAction of
ActionMutate _ -> Right rReq ActRelation _ (ActMutate _) -> Right rReq
_ -> foldr addRangeToNode (Right rReq) =<< ranges _ -> foldr addRangeToNode (Right rReq) =<< ranges
where where
ranges :: Either ApiRequestError [(EmbedPath, NonnegRange)] ranges :: Either ApiRequestError [(EmbedPath, NonnegRange)]
ranges = first QueryParamError $ QueryParams.pRequestRange `traverse` HM.toList iRange 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 :: AppConfig -> ApiRequest -> QualifiedIdentifier -> [MediaType] -> MediaHandlerMap -> Bool -> Either ApiRequestError ResolvedHandler
negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRepresentation=rep}} identifier accepts produces defaultSelect = negotiateContent conf ApiRequest{iAction=act, iPreferences=Preferences{preferRepresentation=rep}} identifier accepts produces defaultSelect =
case (act, firstAcceptedPick) of case (act, firstAcceptedPick) of
(_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts (_, Nothing) -> Left . MediaTypeError $ map MediaType.toMime accepts
(ActionMutate _, Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg, mt) (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 -- 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. -- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
(ActionRead True, Just (_, mt)) -> Right (NoAgg, mt) (ActRelation _ (ActRead True), Just (_, mt)) -> Right (NoAgg, mt)
(ActionInvoke InvHead, Just (_, mt)) -> Right (NoAgg, mt) (ActRoutine _ (ActInvoke (InvRead True)), Just (_, mt)) -> Right (NoAgg, mt)
(_, Just (x, mt)) -> Right (x, mt) (_, Just (x, mt)) -> Right (x, mt)
where where
firstAcceptedPick = listToMaybe $ mapMaybe matchMT accepts -- If there are multiple accepted media types, pick the first. This is usual in content negotiation. 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 matchMT mt = case mt of
+1 -1
View File
@@ -252,7 +252,7 @@ invokeResponse CallReadPlan{crMedia} invMethod proc ctxApiRequest@ApiRequest{iPr
else else
(status, (status,
headers ++ contentTypeHeaders crMedia ctxApiRequest, 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' (ovStatus, ovHeaders) <- overrideStatusHeaders rsGucStatus rsGucHeaders status' headers'