refactor: Replace elem with pattern matching to highlight missing code coverage

Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
Wolfgang Walther
2022-01-28 19:26:33 +01:00
committed by Wolfgang Walther
parent 271c329677
commit 8a722e2cfe
2 changed files with 34 additions and 28 deletions
+4 -4
View File
@@ -65,7 +65,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
"FROM (" <> selectF <> ") _postgrest_t" "FROM (" <> selectF <> ") _postgrest_t"
locF = locF =
if isInsert && rep `elem` [Full, HeadersOnly] if isInsert && rep /= None
then BS.unwords [ then BS.unwords [
"CASE WHEN pg_catalog.count(_postgrest_t) = 1", "CASE WHEN pg_catalog.count(_postgrest_t) = 1",
"THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")", "THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")",
@@ -74,15 +74,15 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
else noLocationF else noLocationF
bodyF bodyF
| rep `elem` [None, HeadersOnly] = "''" | rep /= Full = "''"
| asCsv = asCsvF | asCsv = asCsvF
| wantSingle = asJsonSingleF False | wantSingle = asJsonSingleF False
| otherwise = asJsonF False | otherwise = asJsonF False
selectF selectF
-- prevent using any of the column names in ?select= when no response is returned from the CTE -- prevent using any of the column names in ?select= when no response is returned from the CTE
| rep `elem` [None, HeadersOnly] = SQL.sql ("SELECT * FROM " <> sourceCTEName) | rep /= Full = SQL.sql ("SELECT * FROM " <> sourceCTEName)
| otherwise = selectQuery | otherwise = selectQuery
decodeStandard :: HD.Result ResultsWithCount decodeStandard :: HD.Result ResultsWithCount
decodeStandard = decodeStandard =
+30 -24
View File
@@ -214,11 +214,11 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
_ -> False _ -> False
contentType = maybe CTApplicationJSON ContentType.decodeContentType $ lookupHeader "content-type" contentType = maybe CTApplicationJSON ContentType.decodeContentType $ lookupHeader "content-type"
columns = columns = case action of
if action `elem` [ActionCreate, ActionUpdate, ActionInvoke InvPost] then ActionCreate -> qsColumns
qsColumns ActionUpdate -> qsColumns
else ActionInvoke InvPost -> qsColumns
Nothing _ -> Nothing
payloadColumns = payloadColumns =
case (contentType, action) of case (contentType, action) of
@@ -230,24 +230,23 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
(Just RawJSON{}, Just cls) -> cls (Just RawJSON{}, Just cls) -> cls
_ -> S.empty _ -> S.empty
payload :: Either ByteString Payload payload :: Either ByteString Payload
payload = case contentType of payload = case (contentType, isTargetingProc) of
CTApplicationJSON -> (CTApplicationJSON, _) ->
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 && isTargetingProc =<< if LBS.null reqBody && isTargetingProc
then Right emptyObject then Right emptyObject
else first BS.pack $ JSON.eitherDecode reqBody else first BS.pack $ JSON.eitherDecode reqBody
CTTextCSV -> do (CTTextCSV, _) -> 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
CTUrlEncoded -> (CTUrlEncoded, _) ->
let paramsMap = M.fromList $ (T.decodeUtf8 *** JSON.String . T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) in let paramsMap = M.fromList $ (T.decodeUtf8 *** JSON.String . T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) in
Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (M.keys paramsMap) Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (M.keys paramsMap)
ct -> (CTTextPlain, True) -> Right $ RawPay reqBody
if isTargetingProc && ct `elem` [CTTextPlain, CTOctetStream] (CTOctetStream, True) -> Right $ RawPay reqBody
then Right $ RawPay reqBody (ct, _) -> Left $ "Content-Type not acceptable: " <> ContentType.toMime ct
else Left $ "Content-Type not acceptable: " <> ContentType.toMime ct
topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows
action = action =
case method of case method of
@@ -297,9 +296,13 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
| otherwise -> Right $ TargetIdent $ QualifiedIdentifier pSchema pName | otherwise -> Right $ TargetIdent $ QualifiedIdentifier pSchema pName
PathUnknown -> Right TargetUnknown PathUnknown -> Right TargetUnknown
shouldParsePayload = case (contentType, action) of shouldParsePayload = case (action, contentType) of
(CTUrlEncoded, ActionInvoke InvPost) -> False (ActionCreate, _) -> True
(_, act) -> act `elem` [ActionCreate, ActionUpdate, ActionSingleUpsert, ActionInvoke InvPost] (ActionInvoke InvPost, CTUrlEncoded) -> False
(ActionInvoke InvPost, _) -> True
(ActionSingleUpsert, _) -> True
(ActionUpdate, _) -> True
_ -> False
relevantPayload = case (contentType, action) of relevantPayload = case (contentType, action) of
-- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way -- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way
-- to store the query string arguments to the function. -- to store the query string arguments to the function.
@@ -440,18 +443,21 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost =
| otherwise = (ts,fs) | otherwise = (ts,fs)
-- If the function is called with post and has a single unnamed parameter -- If the function is called with post and has a single unnamed parameter
-- it can be called depending on content type and the parameter type -- it can be called depending on content type and the parameter type
hasSingleUnnamedParam proc = isInvPost && case pdParams proc of hasSingleUnnamedParam ProcDescription{pdParams=[ProcParam{ppType}]} = isInvPost && case (contentType, ppType) of
[ProcParam "" ppType _ _] (CTApplicationJSON, "json") -> True
| contentType == CTApplicationJSON -> ppType `elem` ["json", "jsonb"] (CTApplicationJSON, "jsonb") -> True
| contentType == CTTextPlain -> ppType == "text" (CTTextPlain, "text") -> True
| contentType == CTOctetStream -> ppType == "bytea" (CTOctetStream, "bytea") -> True
| otherwise -> False
_ -> False _ -> False
hasSingleUnnamedParam _ = False
matchesParams proc = matchesParams proc =
let params = pdParams proc in let
params = pdParams proc
firstType = (ppType <$> headMay params)
in
-- exceptional case for Prefer: params=single-object -- exceptional case for Prefer: params=single-object
if paramsAsSingleObject if paramsAsSingleObject
then length params == 1 && (ppType <$> headMay params) `elem` [Just "json", Just "jsonb"] then length params == 1 && (firstType == Just "json" || firstType == Just "jsonb")
-- If the function has no parameters, the arguments keys must be empty as well -- If the function has no parameters, the arguments keys must be empty as well
else if null params else if null params
then null argumentsKeys && contentType `notElem` [CTTextPlain, CTOctetStream] then null argumentsKeys && contentType `notElem` [CTTextPlain, CTOctetStream]