refactor moving findProc into ApiRequest to allow parsing parameters differently by proc
This commit is contained in:
committed by
Steve Chavez
parent
06e85357c7
commit
18cc214c04
+27
-14
@@ -2,8 +2,9 @@
|
|||||||
Module : PostgREST.ApiRequest
|
Module : PostgREST.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 MultiWayIf #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
|
||||||
module PostgREST.ApiRequest (
|
module PostgREST.ApiRequest (
|
||||||
ApiRequest(..)
|
ApiRequest(..)
|
||||||
@@ -45,6 +46,7 @@ import Web.Cookie (parseCookiesText)
|
|||||||
import Data.Ranged.Boundaries
|
import Data.Ranged.Boundaries
|
||||||
|
|
||||||
import PostgREST.Error (ApiRequestError (..))
|
import PostgREST.Error (ApiRequestError (..))
|
||||||
|
import PostgREST.Parsers (pRequestColumns)
|
||||||
import PostgREST.RangeQuery (NonnegRange, allRange, rangeGeq,
|
import PostgREST.RangeQuery (NonnegRange, allRange, rangeGeq,
|
||||||
rangeLimit, rangeOffset, rangeRequested,
|
rangeLimit, rangeOffset, rangeRequested,
|
||||||
restrictRange)
|
restrictRange)
|
||||||
@@ -63,7 +65,7 @@ data Action = ActionCreate | ActionRead{isHead :: Bool}
|
|||||||
deriving Eq
|
deriving Eq
|
||||||
-- | The target db object of a user action
|
-- | The target db object of a user action
|
||||||
data Target = TargetIdent QualifiedIdentifier
|
data Target = TargetIdent QualifiedIdentifier
|
||||||
| TargetProc{tpQi :: QualifiedIdentifier, tpIsRootSpec :: Bool}
|
| TargetProc{tProc :: ProcDescription, tpIsRootSpec :: Bool}
|
||||||
| TargetDefaultSpec{tdsSchema :: Schema} -- The default spec offered at root "/"
|
| TargetDefaultSpec{tdsSchema :: Schema} -- The default spec offered at root "/"
|
||||||
| TargetUnknown [Text]
|
| TargetUnknown [Text]
|
||||||
deriving Eq
|
deriving Eq
|
||||||
@@ -90,7 +92,7 @@ data ApiRequest = ApiRequest {
|
|||||||
, iLogic :: [(Text, Text)] -- ^ &and and &or parameters used for complex boolean logic
|
, iLogic :: [(Text, Text)] -- ^ &and and &or parameters used for complex boolean logic
|
||||||
, iSelect :: Maybe Text -- ^ &select parameter used to shape the response
|
, iSelect :: Maybe Text -- ^ &select parameter used to shape the response
|
||||||
, iOnConflict :: Maybe Text -- ^ &on_conflict parameter used to upsert on specific unique keys
|
, iOnConflict :: Maybe Text -- ^ &on_conflict parameter used to upsert on specific unique keys
|
||||||
, iColumns :: Maybe Text -- ^ &columns parameter used to shape the payload
|
, iColumns :: S.Set FieldName -- ^ parsed colums from &columns parameter and payload
|
||||||
, iOrder :: [(Text, Text)] -- ^ &order parameters for each level
|
, iOrder :: [(Text, Text)] -- ^ &order parameters for each level
|
||||||
, iCanonicalQS :: ByteString -- ^ Alphabetized (canonical) request query string for response URLs
|
, iCanonicalQS :: ByteString -- ^ Alphabetized (canonical) request query string for response URLs
|
||||||
, iJWT :: Text -- ^ JSON Web Token
|
, iJWT :: Text -- ^ JSON Web Token
|
||||||
@@ -103,12 +105,13 @@ data ApiRequest = ApiRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- | Examines HTTP request and translates it into user intent.
|
-- | Examines HTTP request and translates it into user intent.
|
||||||
userApiRequest :: NonEmpty Schema -> Maybe Text -> Request -> RequestBody -> Either ApiRequestError ApiRequest
|
userApiRequest :: NonEmpty Schema -> Maybe Text -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest
|
||||||
userApiRequest confSchemas rootSpec req reqBody
|
userApiRequest confSchemas rootSpec dbStructure req reqBody
|
||||||
| isJust profile && fromJust profile `notElem` confSchemas = Left $ UnacceptableSchema $ toList confSchemas
|
| isJust profile && fromJust profile `notElem` confSchemas = Left $ UnacceptableSchema $ toList confSchemas
|
||||||
| isTargetingProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate
|
| isTargetingProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate
|
||||||
| topLevelRange == emptyRange = Left InvalidRange
|
| topLevelRange == emptyRange = Left InvalidRange
|
||||||
| shouldParsePayload && isLeft payload = either (Left . InvalidBody . toS) witness payload
|
| shouldParsePayload && isLeft payload = either (Left . InvalidBody . toS) witness payload
|
||||||
|
| isLeft parsedColumns = either Left witness parsedColumns
|
||||||
| otherwise = Right ApiRequest {
|
| otherwise = Right ApiRequest {
|
||||||
iAction = action
|
iAction = action
|
||||||
, iTarget = target
|
, iTarget = target
|
||||||
@@ -131,7 +134,7 @@ userApiRequest confSchemas rootSpec req reqBody
|
|||||||
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
||||||
, iSelect = toS <$> join (lookup "select" qParams)
|
, iSelect = toS <$> join (lookup "select" qParams)
|
||||||
, iOnConflict = toS <$> join (lookup "on_conflict" qParams)
|
, iOnConflict = toS <$> join (lookup "on_conflict" qParams)
|
||||||
, iColumns = columns
|
, iColumns = payloadColumns
|
||||||
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
||||||
, iCanonicalQS = toS $ urlEncodeVars
|
, iCanonicalQS = toS $ urlEncodeVars
|
||||||
. L.sortOn fst
|
. L.sortOn fst
|
||||||
@@ -174,6 +177,12 @@ userApiRequest confSchemas rootSpec req reqBody
|
|||||||
columns
|
columns
|
||||||
| action `elem` [ActionCreate, ActionUpdate, ActionInvoke InvPost] = toS <$> join (lookup "columns" qParams)
|
| action `elem` [ActionCreate, ActionUpdate, ActionInvoke InvPost] = toS <$> join (lookup "columns" qParams)
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
parsedColumns = pRequestColumns columns
|
||||||
|
payloadColumns =
|
||||||
|
case (relevantPayload, fromRight Nothing parsedColumns) of
|
||||||
|
(Just ProcessedJSON{pjKeys}, _) -> pjKeys
|
||||||
|
(Just RawJSON{}, Just cls) -> cls
|
||||||
|
_ -> S.empty
|
||||||
payload =
|
payload =
|
||||||
case (contentType, action) of
|
case (contentType, action) of
|
||||||
(_, ActionInvoke InvGet) -> Right rpcPrmsToJson
|
(_, ActionInvoke InvGet) -> Right rpcPrmsToJson
|
||||||
@@ -226,13 +235,17 @@ userApiRequest confSchemas rootSpec req reqBody
|
|||||||
= Just $ maybe defaultSchema toS $ lookupHeader "Accept-Profile"
|
= Just $ maybe defaultSchema toS $ lookupHeader "Accept-Profile"
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
schema = fromMaybe defaultSchema profile
|
schema = fromMaybe defaultSchema profile
|
||||||
target = case path of
|
target =
|
||||||
[] -> case rootSpec of
|
let
|
||||||
Just pName -> TargetProc (QualifiedIdentifier schema pName) True
|
callFindProc proc = findProc (QualifiedIdentifier schema proc) payloadColumns (hasPrefer (show SingleObject)) $ dbProcs dbStructure
|
||||||
Nothing -> TargetDefaultSpec schema
|
in
|
||||||
[table] -> TargetIdent $ QualifiedIdentifier schema table
|
case path of
|
||||||
["rpc", proc] -> TargetProc (QualifiedIdentifier schema proc) False
|
[] -> case rootSpec of
|
||||||
other -> TargetUnknown other
|
Just pName -> TargetProc (callFindProc pName) True
|
||||||
|
Nothing -> TargetDefaultSpec schema
|
||||||
|
[table] -> TargetIdent $ QualifiedIdentifier schema table
|
||||||
|
["rpc", pName] -> TargetProc (callFindProc pName) False
|
||||||
|
other -> TargetUnknown other
|
||||||
|
|
||||||
shouldParsePayload =
|
shouldParsePayload =
|
||||||
action `elem`
|
action `elem`
|
||||||
|
|||||||
+27
-38
@@ -50,7 +50,6 @@ import PostgREST.Error (PgError (..), SimpleError (..),
|
|||||||
errorResponseFor, singularityError)
|
errorResponseFor, singularityError)
|
||||||
import PostgREST.Middleware
|
import PostgREST.Middleware
|
||||||
import PostgREST.OpenAPI
|
import PostgREST.OpenAPI
|
||||||
import PostgREST.Parsers (pRequestColumns)
|
|
||||||
import PostgREST.QueryBuilder (limitedQuery, mutateRequestToQuery,
|
import PostgREST.QueryBuilder (limitedQuery, mutateRequestToQuery,
|
||||||
readRequestToCountQuery,
|
readRequestToCountQuery,
|
||||||
readRequestToQuery,
|
readRequestToQuery,
|
||||||
@@ -76,12 +75,10 @@ postgrest logLev refConf refDbStructure pool getTime connWorker =
|
|||||||
Nothing -> respond . errorResponseFor $ ConnectionLostError
|
Nothing -> respond . errorResponseFor $ ConnectionLostError
|
||||||
Just dbStructure -> do
|
Just dbStructure -> do
|
||||||
response <- do
|
response <- do
|
||||||
let apiReq = userApiRequest (configSchemas conf) (configRootSpec conf) req body
|
let apiReq = userApiRequest (configSchemas conf) (configRootSpec conf) dbStructure req body
|
||||||
-- Need to parse ?columns early because findProc needs it to solve overloaded functions.
|
case apiReq of
|
||||||
apiReqCols = (,) <$> apiReq <*> (pRequestColumns . iColumns =<< apiReq)
|
|
||||||
case apiReqCols of
|
|
||||||
Left err -> return . errorResponseFor $ err
|
Left err -> return . errorResponseFor $ err
|
||||||
Right (apiRequest, maybeCols) -> do
|
Right apiRequest -> do
|
||||||
-- The jwt must be checked before touching the db.
|
-- The jwt must be checked before touching the db.
|
||||||
attempt <- attemptJwtClaims (configJWKS conf) (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
attempt <- attemptJwtClaims (configJWKS conf) (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
||||||
case jwtClaims attempt of
|
case jwtClaims attempt of
|
||||||
@@ -89,38 +86,27 @@ postgrest logLev refConf refDbStructure pool getTime connWorker =
|
|||||||
Right claims -> do
|
Right claims -> do
|
||||||
let
|
let
|
||||||
authed = containsRole claims
|
authed = containsRole claims
|
||||||
cols = case (iPayload apiRequest, maybeCols) of
|
handleReq = runPgLocals conf claims (app dbStructure conf) apiRequest
|
||||||
(Just ProcessedJSON{pjKeys}, _) -> pjKeys
|
dbResp <- P.use pool $ HT.transaction HT.ReadCommitted (txMode apiRequest) handleReq
|
||||||
(Just RawJSON{}, Just cls) -> cls
|
|
||||||
_ -> S.empty
|
|
||||||
proc = case iTarget apiRequest of
|
|
||||||
TargetProc qi _ -> findProc qi cols (iPreferParameters apiRequest == Just SingleObject) $ dbProcs dbStructure
|
|
||||||
_ -> Nothing
|
|
||||||
handleReq = runPgLocals conf claims (app dbStructure proc cols conf) apiRequest
|
|
||||||
txMode = transactionMode proc (iAction apiRequest)
|
|
||||||
dbResp <- P.use pool $ HT.transaction HT.ReadCommitted txMode handleReq
|
|
||||||
return $ either (errorResponseFor . PgError authed) identity dbResp
|
return $ either (errorResponseFor . PgError authed) identity dbResp
|
||||||
-- Launch the connWorker when the connection is down. The postgrest function can respond successfully(with a stale schema cache) before the connWorker is done.
|
-- Launch the connWorker when the connection is down. The postgrest function can respond successfully(with a stale schema cache) before the connWorker is done.
|
||||||
when (responseStatus response == status503) connWorker
|
when (responseStatus response == status503) connWorker
|
||||||
respond response
|
respond response
|
||||||
|
|
||||||
transactionMode :: Maybe ProcDescription -> Action -> HT.Mode
|
txMode :: ApiRequest -> HT.Mode
|
||||||
transactionMode proc action =
|
txMode apiRequest =
|
||||||
case action of
|
case (iAction apiRequest, iTarget apiRequest) of
|
||||||
ActionRead _ -> HT.Read
|
(ActionRead _ , _) -> HT.Read
|
||||||
ActionInfo -> HT.Read
|
(ActionInfo , _) -> HT.Read
|
||||||
ActionInspect _ -> HT.Read
|
(ActionInspect _ , _) -> HT.Read
|
||||||
ActionInvoke InvGet -> HT.Read
|
(ActionInvoke InvGet , _) -> HT.Read
|
||||||
ActionInvoke InvHead -> HT.Read
|
(ActionInvoke InvHead, _) -> HT.Read
|
||||||
ActionInvoke InvPost ->
|
(ActionInvoke InvPost, TargetProc ProcDescription{pdVolatility=Stable} _) -> HT.Read
|
||||||
let v = maybe Volatile pdVolatility proc in
|
(ActionInvoke InvPost, TargetProc ProcDescription{pdVolatility=Immutable} _) -> HT.Read
|
||||||
if v == Stable || v == Immutable
|
|
||||||
then HT.Read
|
|
||||||
else HT.Write
|
|
||||||
_ -> HT.Write
|
_ -> HT.Write
|
||||||
|
|
||||||
app :: DbStructure -> Maybe ProcDescription -> S.Set FieldName -> AppConfig -> ApiRequest -> H.Transaction Response
|
app :: DbStructure -> AppConfig -> ApiRequest -> H.Transaction Response
|
||||||
app dbStructure proc cols conf apiRequest =
|
app dbStructure conf apiRequest =
|
||||||
let rawContentTypes = (decodeContentType <$> configRawMediaTypes conf) `L.union` [ CTOctetStream, CTTextPlain ] in
|
let rawContentTypes = (decodeContentType <$> configRawMediaTypes conf) `L.union` [ CTOctetStream, CTTextPlain ] in
|
||||||
case responseContentTypeOrError (iAccepts apiRequest) rawContentTypes (iAction apiRequest) (iTarget apiRequest) of
|
case responseContentTypeOrError (iAccepts apiRequest) rawContentTypes (iAction apiRequest) (iTarget apiRequest) of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
@@ -210,7 +196,7 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
Left err -> return $ errorResponseFor err
|
Left err -> return $ errorResponseFor err
|
||||||
Right (ghdrs, gstatus) -> do
|
Right (ghdrs, gstatus) -> do
|
||||||
let
|
let
|
||||||
updateIsNoOp = S.null cols
|
updateIsNoOp = S.null (iColumns apiRequest)
|
||||||
defStatus | queryTotal == 0 && not updateIsNoOp = status404
|
defStatus | queryTotal == 0 && not updateIsNoOp = status404
|
||||||
| iPreferRepresentation apiRequest == Full = status200
|
| iPreferRepresentation apiRequest == Full = status200
|
||||||
| otherwise = status204
|
| otherwise = status204
|
||||||
@@ -294,14 +280,14 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header in
|
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header in
|
||||||
return $ responseLBS status200 [allOrigins, allowH] mempty
|
return $ responseLBS status200 [allOrigins, allowH] mempty
|
||||||
|
|
||||||
(ActionInvoke invMethod, TargetProc qi@(QualifiedIdentifier tSchema pName) _, Just pJson) ->
|
(ActionInvoke invMethod, TargetProc proc@ProcDescription{pdSchema, pdName} _, Just pJson) ->
|
||||||
let tName = fromMaybe pName $ procTableName =<< proc in
|
let tName = fromMaybe pdName $ procTableName proc in
|
||||||
case readSqlParts tSchema tName of
|
case readSqlParts pdSchema tName of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right (q, cq, bField, returning) -> do
|
Right (q, cq, bField, returning) -> do
|
||||||
let
|
let
|
||||||
preferParams = iPreferParameters apiRequest
|
preferParams = iPreferParameters apiRequest
|
||||||
pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams returning
|
pq = requestToCallProcQuery (QualifiedIdentifier pdSchema pdName) (specifiedProcArgs (iColumns apiRequest) proc) returnsScalar preferParams returning
|
||||||
stm = callProcStatement returnsScalar pq q cq shouldCount (contentType == CTSingularJSON)
|
stm = callProcStatement returnsScalar pq q cq shouldCount (contentType == CTSingularJSON)
|
||||||
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
|
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
|
||||||
bField pgVer
|
bField pgVer
|
||||||
@@ -351,7 +337,10 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
plannedCount = iPreferCount apiRequest == Just PlannedCount
|
plannedCount = iPreferCount apiRequest == Just PlannedCount
|
||||||
shouldCount = exactCount || estimatedCount
|
shouldCount = exactCount || estimatedCount
|
||||||
topLevelRange = iTopLevelRange apiRequest
|
topLevelRange = iTopLevelRange apiRequest
|
||||||
returnsScalar = maybe False procReturnsScalar proc
|
returnsScalar =
|
||||||
|
case iTarget apiRequest of
|
||||||
|
TargetProc proc _ -> procReturnsScalar proc
|
||||||
|
_ -> False
|
||||||
pgVer = pgVersion dbStructure
|
pgVer = pgVersion dbStructure
|
||||||
profileH = contentProfileH <$> iProfile apiRequest
|
profileH = contentProfileH <$> iProfile apiRequest
|
||||||
|
|
||||||
@@ -370,7 +359,7 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
mutateSqlParts s t =
|
mutateSqlParts s t =
|
||||||
let
|
let
|
||||||
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
||||||
mutReq = mutateRequest s t apiRequest cols (tablePKCols dbStructure s t) =<< readReq
|
mutReq = mutateRequest s t apiRequest (tablePKCols dbStructure s t) =<< readReq
|
||||||
in
|
in
|
||||||
(,) <$>
|
(,) <$>
|
||||||
(readRequestToQuery <$> readReq) <*>
|
(readRequestToQuery <$> readReq) <*>
|
||||||
|
|||||||
@@ -287,15 +287,15 @@ addProperty f (targetNodeName:remainingPath, a) (Node rn forest) =
|
|||||||
where
|
where
|
||||||
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
||||||
|
|
||||||
mutateRequest :: Schema -> TableName -> ApiRequest -> S.Set FieldName -> [FieldName] -> ReadRequest -> Either Response MutateRequest
|
mutateRequest :: Schema -> TableName -> ApiRequest -> [FieldName] -> ReadRequest -> Either Response MutateRequest
|
||||||
mutateRequest schema tName apiRequest cols pkCols readReq = mapLeft errorResponseFor $
|
mutateRequest schema tName apiRequest pkCols readReq = mapLeft errorResponseFor $
|
||||||
case action of
|
case action of
|
||||||
ActionCreate -> do
|
ActionCreate -> do
|
||||||
confCols <- case iOnConflict apiRequest of
|
confCols <- case iOnConflict apiRequest of
|
||||||
Nothing -> pure pkCols
|
Nothing -> pure pkCols
|
||||||
Just param -> pRequestOnConflict param
|
Just param -> pRequestOnConflict param
|
||||||
pure $ Insert qi cols ((,) <$> iPreferResolution apiRequest <*> Just confCols) [] returnings
|
pure $ Insert qi (iColumns apiRequest) ((,) <$> iPreferResolution apiRequest <*> Just confCols) [] returnings
|
||||||
ActionUpdate -> Update qi cols <$> combinedLogic <*> pure returnings
|
ActionUpdate -> Update qi (iColumns apiRequest) <$> combinedLogic <*> pure returnings
|
||||||
ActionSingleUpsert ->
|
ActionSingleUpsert ->
|
||||||
(\flts ->
|
(\flts ->
|
||||||
if null (iLogic apiRequest) &&
|
if null (iLogic apiRequest) &&
|
||||||
@@ -304,7 +304,7 @@ mutateRequest schema tName apiRequest cols pkCols readReq = mapLeft errorRespons
|
|||||||
all (\case
|
all (\case
|
||||||
Filter _ (OpExpr False (Op "eq" _)) -> True
|
Filter _ (OpExpr False (Op "eq" _)) -> True
|
||||||
_ -> False) flts
|
_ -> False) flts
|
||||||
then Insert qi cols (Just (MergeDuplicates, pkCols)) <$> combinedLogic <*> pure returnings
|
then Insert qi (iColumns apiRequest) (Just (MergeDuplicates, pkCols)) <$> combinedLogic <*> pure returnings
|
||||||
else
|
else
|
||||||
Left InvalidFilters) =<< filters
|
Left InvalidFilters) =<< filters
|
||||||
ActionDelete -> Delete qi <$> combinedLogic <*> pure returnings
|
ActionDelete -> Delete qi <$> combinedLogic <*> pure returnings
|
||||||
|
|||||||
+12
-11
@@ -157,13 +157,17 @@ type ProcsMap = M.HashMap QualifiedIdentifier [ProcDescription]
|
|||||||
An overloaded function can have a different volatility or even a different return type.
|
An overloaded function can have a different volatility or even a different return type.
|
||||||
Ideally, handling overloaded functions should be left to pg itself. But we need to know certain proc attributes in advance.
|
Ideally, handling overloaded functions should be left to pg itself. But we need to know certain proc attributes in advance.
|
||||||
-}
|
-}
|
||||||
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> Maybe ProcDescription
|
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> ProcDescription
|
||||||
findProc qi payloadKeys paramsAsSingleObject allProcs =
|
findProc qi payloadKeys paramsAsSingleObject allProcs = fromMaybe fallback bestMatch
|
||||||
case M.lookup qi allProcs of
|
|
||||||
Nothing -> Nothing
|
|
||||||
Just [proc] -> Just proc -- if it's not an overloaded function then immediately get the ProcDescription
|
|
||||||
Just procs -> find matches procs -- Handle overloaded functions case
|
|
||||||
where
|
where
|
||||||
|
-- instead of passing Maybe ProcDescription around, we create a fallback description here when we can't find a matching function
|
||||||
|
-- args is empty, but because "specifiedProcArgs" will fill the missing arguments with default type text, this is not a problem
|
||||||
|
fallback = ProcDescription (qiSchema qi) (qiName qi) Nothing mempty (SetOf $ Composite $ QualifiedIdentifier "" "record") Volatile
|
||||||
|
bestMatch =
|
||||||
|
case M.lookup qi allProcs of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just [proc] -> Just proc -- if it's not an overloaded function then immediately get the ProcDescription
|
||||||
|
Just procs -> find matches procs -- Handle overloaded functions case
|
||||||
matches proc =
|
matches proc =
|
||||||
if paramsAsSingleObject
|
if paramsAsSingleObject
|
||||||
-- if the arg is not of json type let the db give the err
|
-- if the arg is not of json type let the db give the err
|
||||||
@@ -174,12 +178,9 @@ findProc qi payloadKeys paramsAsSingleObject allProcs =
|
|||||||
Search the procedure parameters by matching them with the specified keys.
|
Search the procedure parameters by matching them with the specified keys.
|
||||||
If the key doesn't match a parameter, a parameter with a default type "text" is assumed.
|
If the key doesn't match a parameter, a parameter with a default type "text" is assumed.
|
||||||
-}
|
-}
|
||||||
specifiedProcArgs :: S.Set FieldName -> Maybe ProcDescription -> [PgArg]
|
specifiedProcArgs :: S.Set FieldName -> ProcDescription -> [PgArg]
|
||||||
specifiedProcArgs keys proc =
|
specifiedProcArgs keys proc =
|
||||||
let
|
(\k -> fromMaybe (PgArg k "text" True) (find ((==) k . pgaName) (pdArgs proc))) <$> S.toList keys
|
||||||
args = maybe [] pdArgs proc
|
|
||||||
in
|
|
||||||
(\k -> fromMaybe (PgArg k "text" True) (find ((==) k . pgaName) args)) <$> S.toList keys
|
|
||||||
|
|
||||||
procReturnsScalar :: ProcDescription -> Bool
|
procReturnsScalar :: ProcDescription -> Bool
|
||||||
procReturnsScalar proc = case proc of
|
procReturnsScalar proc = case proc of
|
||||||
|
|||||||
+20
-13
@@ -511,19 +511,26 @@ spec actualPgVersion =
|
|||||||
`shouldRespondWith` [json|{ "p1": "1", "p2": "text"}|]
|
`shouldRespondWith` [json|{ "p1": "1", "p2": "text"}|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "should work with an overloaded function" $ do
|
context "should work with an overloaded function" $ do
|
||||||
get "/rpc/overloaded" `shouldRespondWith`
|
it "overloaded()" $
|
||||||
[json|[{ "overloaded": 1 },
|
get "/rpc/overloaded" `shouldRespondWith`
|
||||||
{ "overloaded": 2 },
|
[json|[{ "overloaded": 1 },
|
||||||
{ "overloaded": 3 }]|]
|
{ "overloaded": 2 },
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ "overloaded": 3 }]|]
|
||||||
request methodPost "/rpc/overloaded" [("Prefer","params=single-object")]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
|
||||||
`shouldRespondWith`
|
it "overloaded(json) single-object" $
|
||||||
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
request methodPost "/rpc/overloaded" [("Prefer","params=single-object")]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
||||||
get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|]
|
`shouldRespondWith`
|
||||||
get "/rpc/overloaded?a=1&b=2&c=3" `shouldRespondWith` [str|"123"|]
|
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "overloaded(int, int)" $
|
||||||
|
get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|]
|
||||||
|
|
||||||
|
it "overloaded(text, text, text)" $
|
||||||
|
get "/rpc/overloaded?a=1&b=2&c=3" `shouldRespondWith` [str|"123"|]
|
||||||
|
|
||||||
context "only for POST rpc" $ do
|
context "only for POST rpc" $ do
|
||||||
it "gives a parse filter error if GET style proc args are specified" $
|
it "gives a parse filter error if GET style proc args are specified" $
|
||||||
|
|||||||
Reference in New Issue
Block a user