refactor: Move accept content type code from App into ApiRequest (#1830)

This commit is contained in:
Remo Rechkemmer
2021-04-25 21:58:03 +02:00
committed by GitHub
parent 63e0292e23
commit 6670a3214b
3 changed files with 77 additions and 69 deletions
+27 -51
View File
@@ -89,7 +89,6 @@ data RequestContext = RequestContext
{ ctxConfig :: AppConfig { ctxConfig :: AppConfig
, ctxDbStructure :: DbStructure , ctxDbStructure :: DbStructure
, ctxApiRequest :: ApiRequest , ctxApiRequest :: ApiRequest
, ctxContentType :: ContentType
} }
type Handler = ExceptT Error type Handler = ExceptT Error
@@ -161,7 +160,7 @@ postgrestResponse
-> UTCTime -> UTCTime
-> Wai.Request -> Wai.Request
-> Handler IO Wai.Response -> Handler IO Wai.Response
postgrestResponse conf@AppConfig{..} maybeDbStructure pool time req = do postgrestResponse conf maybeDbStructure pool time req = do
body <- lift $ Wai.strictRequestBody req body <- lift $ Wai.strictRequestBody req
dbStructure <- dbStructure <-
@@ -173,21 +172,14 @@ postgrestResponse conf@AppConfig{..} maybeDbStructure pool time req = do
apiRequest@ApiRequest{..} <- apiRequest@ApiRequest{..} <-
liftEither . mapLeft Error.ApiRequestError $ liftEither . mapLeft Error.ApiRequestError $
ApiRequest.userApiRequest configDbSchemas configDbRootSpec dbStructure req body ApiRequest.userApiRequest conf dbStructure req body
-- The JWT must be checked before touching the db -- The JWT must be checked before touching the db
jwtClaims <- Auth.jwtClaims conf (toS iJWT) time jwtClaims <- Auth.jwtClaims conf (toS iJWT) time
contentType <-
case ApiRequest.mutuallyAgreeable (requestContentTypes conf apiRequest) iAccepts of
Just ct ->
return ct
Nothing ->
throwError . Error.ContentTypeError $ map ContentType.toMime iAccepts
let let
handleReq apiReq = handleReq apiReq =
handleRequest $ RequestContext conf dbStructure apiReq contentType handleRequest $ RequestContext conf dbStructure apiReq
runDbHandler pool (txMode apiRequest) jwtClaims . runDbHandler pool (txMode apiRequest) jwtClaims .
Middleware.optionalRollback conf apiRequest $ Middleware.optionalRollback conf apiRequest $
@@ -205,7 +197,7 @@ runDbHandler pool mode jwtClaims handler = do
liftEither resp liftEither resp
handleRequest :: RequestContext -> DbHandler Wai.Response handleRequest :: RequestContext -> DbHandler Wai.Response
handleRequest context@(RequestContext _ _ ApiRequest{..} _) = handleRequest context@(RequestContext _ _ ApiRequest{..}) =
case (iAction, iTarget) of case (iAction, iTarget) of
(ActionRead headersOnly, TargetIdent identifier) -> (ActionRead headersOnly, TargetIdent identifier) ->
handleRead headersOnly identifier context handleRead headersOnly identifier context
@@ -246,9 +238,9 @@ handleRead headersOnly identifier context@RequestContext{..} = do
else else
countQuery countQuery
) )
(ctxContentType == CTSingularJSON) (iAcceptContentType == CTSingularJSON)
(shouldCount iPreferCount) (shouldCount iPreferCount)
(ctxContentType == CTTextCSV) (iAcceptContentType == CTTextCSV)
bField bField
(pgVersion ctxDbStructure) (pgVersion ctxDbStructure)
configDbPreparedStatements configDbPreparedStatements
@@ -268,7 +260,7 @@ handleRead headersOnly identifier context@RequestContext{..} = do
] ]
++ contentTypeHeaders context ++ contentTypeHeaders context
failNotSingular ctxContentType queryTotal . response status headers $ failNotSingular iAcceptContentType queryTotal . response status headers $
if headersOnly then mempty else toS body if headersOnly then mempty else toS body
readTotal :: AppConfig -> ApiRequest -> Maybe Int64 -> SQL.Snippet -> DbHandler (Maybe Int64) readTotal :: AppConfig -> ApiRequest -> Maybe Int64 -> SQL.Snippet -> DbHandler (Maybe Int64)
@@ -317,14 +309,14 @@ handleCreate identifier@QualifiedIdentifier{..} context@RequestContext{..} = do
(\x -> ("Preference-Applied", BS8.pack $ show x)) <$> iPreferResolution (\x -> ("Preference-Applied", BS8.pack $ show x)) <$> iPreferResolution
] ]
failNotSingular ctxContentType resQueryTotal $ failNotSingular iAcceptContentType resQueryTotal $
if iPreferRepresentation == Full then if iPreferRepresentation == Full then
response HTTP.status201 (headers ++ contentTypeHeaders context) (toS resBody) response HTTP.status201 (headers ++ contentTypeHeaders context) (toS resBody)
else else
response HTTP.status201 headers mempty response HTTP.status201 headers mempty
handleUpdate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleUpdate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
handleUpdate identifier context@(RequestContext _ _ ApiRequest{..} contentType) = do handleUpdate identifier context@(RequestContext _ _ ApiRequest{..}) = do
WriteQueryResult{..} <- writeQuery identifier False mempty context WriteQueryResult{..} <- writeQuery identifier False mempty context
let let
@@ -339,14 +331,14 @@ handleUpdate identifier context@(RequestContext _ _ ApiRequest{..} contentType)
RangeQuery.contentRangeH 0 (resQueryTotal - 1) $ RangeQuery.contentRangeH 0 (resQueryTotal - 1) $
if shouldCount iPreferCount then Just resQueryTotal else Nothing if shouldCount iPreferCount then Just resQueryTotal else Nothing
failNotSingular contentType resQueryTotal $ failNotSingular iAcceptContentType resQueryTotal $
if fullRepr then if fullRepr then
response status (contentTypeHeaders context ++ [contentRangeHeader]) (toS resBody) response status (contentTypeHeaders context ++ [contentRangeHeader]) (toS resBody)
else else
response status [contentRangeHeader] mempty response status [contentRangeHeader] mempty
handleSingleUpsert :: QualifiedIdentifier -> RequestContext-> DbHandler Wai.Response handleSingleUpsert :: QualifiedIdentifier -> RequestContext-> DbHandler Wai.Response
handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..} _) = do handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..}) = do
when (iTopLevelRange /= RangeQuery.allRange) $ when (iTopLevelRange /= RangeQuery.allRange) $
throwError Error.PutRangeNotAllowedError throwError Error.PutRangeNotAllowedError
@@ -370,7 +362,7 @@ handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..} _) = do
response HTTP.status204 (contentTypeHeaders context) mempty response HTTP.status204 (contentTypeHeaders context) mempty
handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
handleDelete identifier context@(RequestContext _ _ ApiRequest{..} contentType) = do handleDelete identifier context@(RequestContext _ _ ApiRequest{..}) = do
WriteQueryResult{..} <- writeQuery identifier False mempty context WriteQueryResult{..} <- writeQuery identifier False mempty context
let let
@@ -379,7 +371,7 @@ handleDelete identifier context@(RequestContext _ _ ApiRequest{..} contentType)
RangeQuery.contentRangeH 1 0 $ RangeQuery.contentRangeH 1 0 $
if shouldCount iPreferCount then Just resQueryTotal else Nothing if shouldCount iPreferCount then Just resQueryTotal else Nothing
failNotSingular contentType resQueryTotal $ failNotSingular iAcceptContentType resQueryTotal $
if iPreferRepresentation == Full then if iPreferRepresentation == Full then
response HTTP.status200 response HTTP.status200
(contentTypeHeaders context ++ [contentRangeHeader]) (contentTypeHeaders context ++ [contentRangeHeader])
@@ -443,8 +435,8 @@ handleInvoke invMethod proc context@RequestContext{..} = do
(QueryBuilder.readRequestToQuery req) (QueryBuilder.readRequestToQuery req)
(QueryBuilder.readRequestToCountQuery req) (QueryBuilder.readRequestToCountQuery req)
(shouldCount iPreferCount) (shouldCount iPreferCount)
(ctxContentType == CTSingularJSON) (iAcceptContentType == CTSingularJSON)
(ctxContentType == CTTextCSV) (iAcceptContentType == CTTextCSV)
(iPreferParameters == Just MultipleObjects) (iPreferParameters == Just MultipleObjects)
bField bField
(pgVersion ctxDbStructure) (pgVersion ctxDbStructure)
@@ -456,13 +448,13 @@ handleInvoke invMethod proc context@RequestContext{..} = do
(status, contentRange) = (status, contentRange) =
RangeQuery.rangeStatusHeader iTopLevelRange queryTotal tableTotal RangeQuery.rangeStatusHeader iTopLevelRange queryTotal tableTotal
failNotSingular ctxContentType queryTotal $ failNotSingular iAcceptContentType queryTotal $
response status response status
(contentTypeHeaders context ++ [contentRange]) (contentTypeHeaders context ++ [contentRange])
(if invMethod == InvHead then mempty else toS body) (if invMethod == InvHead then mempty else toS body)
handleOpenApi :: Bool -> Schema -> RequestContext -> DbHandler Wai.Response handleOpenApi :: Bool -> Schema -> RequestContext -> DbHandler Wai.Response
handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure apiRequest _) = do handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure apiRequest) = do
body <- body <-
lift $ lift $
OpenAPI.encode conf dbStructure OpenAPI.encode conf dbStructure
@@ -519,9 +511,9 @@ writeQuery identifier@QualifiedIdentifier{..} isInsert pkCols context@RequestCon
Statements.createWriteStatement Statements.createWriteStatement
(QueryBuilder.readRequestToQuery readReq) (QueryBuilder.readRequestToQuery readReq)
(QueryBuilder.mutateRequestToQuery mutateReq) (QueryBuilder.mutateRequestToQuery mutateReq)
(ctxContentType == CTSingularJSON) (iAcceptContentType ctxApiRequest == CTSingularJSON)
isInsert isInsert
(ctxContentType == CTTextCSV) (iAcceptContentType ctxApiRequest == CTTextCSV)
(iPreferRepresentation ctxApiRequest) (iPreferRepresentation ctxApiRequest)
pkCols pkCols
(pgVersion ctxDbStructure) (pgVersion ctxDbStructure)
@@ -562,7 +554,7 @@ returnsScalar (TargetProc proc _) = Proc.procReturnsScalar proc
returnsScalar _ = False returnsScalar _ = False
readRequest :: Monad m => QualifiedIdentifier -> RequestContext -> Handler m ReadRequest readRequest :: Monad m => QualifiedIdentifier -> RequestContext -> Handler m ReadRequest
readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) = readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest) =
liftEither $ liftEither $
ReqBuilder.readRequest qiSchema qiName configDbMaxRows ReqBuilder.readRequest qiSchema qiName configDbMaxRows
(dbRelationships dbStructure) (dbRelationships dbStructure)
@@ -570,32 +562,16 @@ readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure ap
contentTypeHeaders :: RequestContext -> [HTTP.Header] contentTypeHeaders :: RequestContext -> [HTTP.Header]
contentTypeHeaders RequestContext{..} = contentTypeHeaders RequestContext{..} =
ContentType.toHeader ctxContentType : maybeToList (profileHeader ctxApiRequest) ContentType.toHeader (iAcceptContentType ctxApiRequest) : maybeToList (profileHeader ctxApiRequest)
requestContentTypes :: AppConfig -> ApiRequest -> [ContentType] -- | If raw(binary) output is requested, check that ContentType is one of the
requestContentTypes conf ApiRequest{..} = -- admitted rawContentTypes and that`?select=...` contains only one field other
case iAction of -- than `*`
ActionRead _ -> defaultContentTypes ++ rawContentTypes conf
ActionInvoke _ -> invokeContentTypes
ActionInspect _ -> [CTOpenAPI, CTApplicationJSON]
ActionInfo -> [CTTextCSV]
_ -> defaultContentTypes
where
invokeContentTypes =
defaultContentTypes
++ rawContentTypes conf
++ [CTOpenAPI | ApiRequest.tpIsRootSpec iTarget]
defaultContentTypes =
[CTApplicationJSON, CTSingularJSON, CTTextCSV]
-- |
-- If raw(binary) output is requested, check that ContentType is one of the admitted
-- rawContentTypes and that`?select=...` contains only one field other than `*`
binaryField :: Monad m => RequestContext -> ReadRequest -> Handler m (Maybe FieldName) binaryField :: Monad m => RequestContext -> ReadRequest -> Handler m (Maybe FieldName)
binaryField RequestContext{..} readReq binaryField RequestContext{..} readReq
| returnsScalar (iTarget ctxApiRequest) && ctxContentType `elem` rawContentTypes ctxConfig = | returnsScalar (iTarget ctxApiRequest) && iAcceptContentType ctxApiRequest `elem` rawContentTypes ctxConfig =
return $ Just "pgrst_scalar" return $ Just "pgrst_scalar"
| ctxContentType `elem` rawContentTypes ctxConfig = | iAcceptContentType ctxApiRequest `elem` rawContentTypes ctxConfig =
let let
fldNames = fstFieldNames readReq fldNames = fstFieldNames readReq
fieldName = headMay fldNames fieldName = headMay fldNames
@@ -603,7 +579,7 @@ binaryField RequestContext{..} readReq
if length fldNames == 1 && fieldName /= Just "*" then if length fldNames == 1 && fieldName /= Just "*" then
return fieldName return fieldName
else else
throwError $ Error.BinaryFieldError ctxContentType throwError $ Error.BinaryFieldError (iAcceptContentType ctxApiRequest)
| otherwise = | otherwise =
return Nothing return Nothing
+4 -4
View File
@@ -59,6 +59,7 @@ data ApiRequestError
| AmbiguousRelBetween Text Text [Relationship] | AmbiguousRelBetween Text Text [Relationship]
| InvalidFilters | InvalidFilters
| UnacceptableSchema [Text] | UnacceptableSchema [Text]
| ContentTypeError [ByteString]
| UnsupportedVerb -- Unreachable? | UnsupportedVerb -- Unreachable?
instance PgrstError ApiRequestError where instance PgrstError ApiRequestError where
@@ -71,6 +72,7 @@ instance PgrstError ApiRequestError where
status (NoRelBetween _ _) = HT.status400 status (NoRelBetween _ _) = HT.status400
status AmbiguousRelBetween{} = HT.status300 status AmbiguousRelBetween{} = HT.status300
status (UnacceptableSchema _) = HT.status406 status (UnacceptableSchema _) = HT.status406
status (ContentTypeError _) = HT.status415
headers _ = [ContentType.toHeader CTApplicationJSON] headers _ = [ContentType.toHeader CTApplicationJSON]
@@ -95,6 +97,8 @@ instance JSON.ToJSON ApiRequestError where
"message" .= ("Filters must include all and only primary key columns with 'eq' operators" :: Text)] "message" .= ("Filters must include all and only primary key columns with 'eq' operators" :: Text)]
toJSON (UnacceptableSchema schemas) = JSON.object [ toJSON (UnacceptableSchema schemas) = JSON.object [
"message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas)] "message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas)]
toJSON (ContentTypeError cts) = JSON.object [
"message" .= ("None of these Content-Types are available: " <> (toS . intercalate ", " . map toS) cts :: Text)]
compressedRel :: Relationship -> JSON.Value compressedRel :: Relationship -> JSON.Value
compressedRel Relationship{..} = compressedRel Relationship{..} =
@@ -237,7 +241,6 @@ data Error
| JwtTokenMissing | JwtTokenMissing
| JwtTokenInvalid Text | JwtTokenInvalid Text
| SingularityError Integer | SingularityError Integer
| ContentTypeError [ByteString]
| NotFound | NotFound
| ApiRequestError ApiRequestError | ApiRequestError ApiRequestError
| PgErr PgError | PgErr PgError
@@ -252,7 +255,6 @@ instance PgrstError Error where
status JwtTokenMissing = HT.status500 status JwtTokenMissing = HT.status500
status (JwtTokenInvalid _) = HT.unauthorized401 status (JwtTokenInvalid _) = HT.unauthorized401
status (SingularityError _) = HT.status406 status (SingularityError _) = HT.status406
status (ContentTypeError _) = HT.status415
status NotFound = HT.status404 status NotFound = HT.status404
status (PgErr err) = status err status (PgErr err) = status err
status (ApiRequestError err) = status err status (ApiRequestError err) = status err
@@ -278,8 +280,6 @@ instance JSON.ToJSON Error where
toJSON PutMatchingPkError = JSON.object [ toJSON PutMatchingPkError = JSON.object [
"message" .= ("Payload values do not match URL in primary key column(s)" :: Text)] "message" .= ("Payload values do not match URL in primary key column(s)" :: Text)]
toJSON (ContentTypeError cts) = JSON.object [
"message" .= ("None of these Content-Types are available: " <> (toS . intercalate ", " . map toS) cts :: Text)]
toJSON (SingularityError n) = JSON.object [ toJSON (SingularityError n) = JSON.object [
"message" .= ("JSON object requested, multiple (or no) rows returned" :: Text), "message" .= ("JSON object requested, multiple (or no) rows returned" :: Text),
"details" .= T.unwords ["Results contain", show n, "rows,", toS (ContentType.toMime CTSingularJSON), "requires 1 row"]] "details" .= T.unwords ["Results contain", show n, "rows,", toS (ContentType.toMime CTSingularJSON), "requires 1 row"]]
+46 -14
View File
@@ -2,9 +2,10 @@
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 MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module PostgREST.Request.ApiRequest module PostgREST.Request.ApiRequest
( ApiRequest(..) ( ApiRequest(..)
@@ -13,7 +14,6 @@ module PostgREST.Request.ApiRequest
, Action(..) , Action(..)
, Target(..) , Target(..)
, PayloadJSON(..) , PayloadJSON(..)
, mutuallyAgreeable
, userApiRequest , userApiRequest
) where ) where
@@ -30,7 +30,7 @@ import qualified Data.Vector as V
import Control.Arrow ((***)) import Control.Arrow ((***))
import Data.Aeson.Types (emptyArray, emptyObject) import Data.Aeson.Types (emptyArray, emptyObject)
import Data.List (last, lookup, partition) import Data.List (last, lookup, partition, union)
import Data.List.NonEmpty (head) import Data.List.NonEmpty (head)
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Data.Ranged.Boundaries (Boundary (..)) import Data.Ranged.Boundaries (Boundary (..))
@@ -44,6 +44,7 @@ import Network.Wai (Request (..))
import Network.Wai.Parse (parseHttpAccept) import Network.Wai.Parse (parseHttpAccept)
import Web.Cookie (parseCookiesText) import Web.Cookie (parseCookiesText)
import PostgREST.Config (AppConfig (..))
import PostgREST.ContentType (ContentType (..)) import PostgREST.ContentType (ContentType (..))
import PostgREST.DbStructure (DbStructure (..)) import PostgREST.DbStructure (DbStructure (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
@@ -137,7 +138,6 @@ data ApiRequest = ApiRequest {
, iRange :: M.HashMap ByteString NonnegRange -- ^ Requested range of rows within response , iRange :: M.HashMap ByteString 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 , iTarget :: Target -- ^ The target, be it calling a proc or accessing a table
, iAccepts :: [ContentType] -- ^ Content types the client will accept, [CTAny] if no Accept header
, iPayload :: Maybe PayloadJSON -- ^ Data sent by client and used for mutation actions , iPayload :: Maybe PayloadJSON -- ^ Data sent by client and used for mutation actions
, iPreferRepresentation :: PreferRepresentation -- ^ If client wants created items echoed back , iPreferRepresentation :: PreferRepresentation -- ^ If client wants created items echoed back
, iPreferParameters :: Maybe PreferParameters -- ^ How to pass parameters to a stored procedure , iPreferParameters :: Maybe PreferParameters -- ^ How to pass parameters to a stored procedure
@@ -158,22 +158,24 @@ data ApiRequest = ApiRequest {
, iMethod :: ByteString -- ^ Raw request method , iMethod :: ByteString -- ^ Raw request method
, iProfile :: Maybe Schema -- ^ The request profile for enabling use of multiple schemas. Follows the spec in hhttps://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/. , iProfile :: Maybe Schema -- ^ The request profile for enabling use of multiple schemas. Follows the spec in hhttps://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/.
, iSchema :: Schema -- ^ The request schema. Can vary depending on iProfile. , iSchema :: Schema -- ^ The request schema. Can vary depending on iProfile.
, iAcceptContentType :: ContentType
} }
-- | Examines HTTP request and translates it into user intent. -- | Examines HTTP request and translates it into user intent.
userApiRequest :: NonEmpty Schema -> Maybe Text -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest userApiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest
userApiRequest confSchemas rootSpec dbStructure req reqBody userApiRequest conf@AppConfig{..} dbStructure req reqBody
| isJust profile && fromJust profile `notElem` confSchemas = Left $ UnacceptableSchema $ toList confSchemas | isJust profile && fromJust profile `notElem` configDbSchemas = Left $ UnacceptableSchema $ toList configDbSchemas
| 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 | isLeft parsedColumns = either Left witness parsedColumns
| otherwise = Right ApiRequest { | otherwise = do
acceptContentType <- findAcceptContentType conf action target accepts
return ApiRequest {
iAction = action iAction = action
, iTarget = target , iTarget = target
, iRange = ranges , iRange = ranges
, iTopLevelRange = topLevelRange , iTopLevelRange = topLevelRange
, iAccepts = maybe [CTAny] (map ContentType.decodeContentType . parseHttpAccept) $ lookupHeader "accept"
, iPayload = relevantPayload , iPayload = relevantPayload
, iPreferRepresentation = representation , iPreferRepresentation = representation
, iPreferParameters = if | hasPrefer (show SingleObject) -> Just SingleObject , iPreferParameters = if | hasPrefer (show SingleObject) -> Just SingleObject
@@ -206,8 +208,10 @@ userApiRequest confSchemas rootSpec dbStructure req reqBody
, iMethod = method , iMethod = method
, iProfile = profile , iProfile = profile
, iSchema = schema , iSchema = schema
, iAcceptContentType = acceptContentType
} }
where where
accepts = maybe [CTAny] (map ContentType.decodeContentType . parseHttpAccept) $ lookupHeader "accept"
-- queryString with '+' converted to ' '(space) -- queryString with '+' converted to ' '(space)
qString = parseQueryReplacePlus True $ rawQueryString req qString = parseQueryReplacePlus True $ rawQueryString req
-- rpcQParams = Rpc query params e.g. /rpc/name?param1=val1, similar to filter but with no operator(eq, lt..) -- rpcQParams = Rpc query params e.g. /rpc/name?param1=val1, similar to filter but with no operator(eq, lt..)
@@ -287,9 +291,9 @@ userApiRequest confSchemas rootSpec dbStructure req reqBody
"OPTIONS" -> ActionInfo "OPTIONS" -> ActionInfo
_ -> ActionInspect{isHead=False} _ -> ActionInspect{isHead=False}
defaultSchema = head confSchemas defaultSchema = head configDbSchemas
profile profile
| length confSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config | length configDbSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config
= Nothing = Nothing
| otherwise = case action of | otherwise = case action of
-- POST/PATCH/PUT/DELETE don't use the same header as per the spec -- POST/PATCH/PUT/DELETE don't use the same header as per the spec
@@ -308,7 +312,7 @@ userApiRequest confSchemas rootSpec dbStructure req reqBody
callFindProc proc = findProc (QualifiedIdentifier schema proc) payloadColumns (hasPrefer (show SingleObject)) $ dbProcs dbStructure callFindProc proc = findProc (QualifiedIdentifier schema proc) payloadColumns (hasPrefer (show SingleObject)) $ dbProcs dbStructure
in in
case path of case path of
[] -> case rootSpec of [] -> case configDbRootSpec of
Just pName -> TargetProc (callFindProc pName) True Just pName -> TargetProc (callFindProc pName) True
Nothing -> TargetDefaultSpec schema Nothing -> TargetDefaultSpec schema
[table] -> TargetIdent $ QualifiedIdentifier schema table [table] -> TargetIdent $ QualifiedIdentifier schema table
@@ -423,3 +427,31 @@ payloadAttributes raw json =
_ -> Just emptyPJArray _ -> Just emptyPJArray
where where
emptyPJArray = ProcessedJSON (JSON.encode emptyArray) S.empty emptyPJArray = ProcessedJSON (JSON.encode emptyArray) S.empty
findAcceptContentType :: AppConfig -> Action -> Target -> [ContentType] -> Either ApiRequestError ContentType
findAcceptContentType conf action target accepts =
case mutuallyAgreeable (requestContentTypes conf action target) accepts of
Just ct ->
Right ct
Nothing ->
Left . ContentTypeError $ map ContentType.toMime accepts
requestContentTypes :: AppConfig -> Action -> Target -> [ContentType]
requestContentTypes conf action target =
case action of
ActionRead _ -> defaultContentTypes ++ rawContentTypes conf
ActionInvoke _ -> invokeContentTypes
ActionInspect _ -> [CTOpenAPI, CTApplicationJSON]
ActionInfo -> [CTTextCSV]
_ -> defaultContentTypes
where
invokeContentTypes =
defaultContentTypes
++ rawContentTypes conf
++ [CTOpenAPI | tpIsRootSpec target]
defaultContentTypes =
[CTApplicationJSON, CTSingularJSON, CTTextCSV]
rawContentTypes :: AppConfig -> [ContentType]
rawContentTypes AppConfig{..} =
(ContentType.decodeContentType <$> configRawMediaTypes) `union` [CTOctetStream, CTTextPlain]