refactor: move binaryField to ApiRequest
This commit is contained in:
committed by
Steve Chavez
parent
858e4405ec
commit
11385bbd9f
+3
-39
@@ -20,7 +20,6 @@ module PostgREST.App
|
|||||||
|
|
||||||
import Control.Monad.Except (liftEither)
|
import Control.Monad.Except (liftEither)
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft)
|
||||||
import Data.List (union)
|
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
||||||
@@ -58,15 +57,13 @@ import PostgREST.DbStructure.Identifiers (FieldName,
|
|||||||
import PostgREST.DbStructure.Proc (ProcDescription (..))
|
import PostgREST.DbStructure.Proc (ProcDescription (..))
|
||||||
import PostgREST.DbStructure.Table (Table (..))
|
import PostgREST.DbStructure.Table (Table (..))
|
||||||
import PostgREST.Error (Error)
|
import PostgREST.Error (Error)
|
||||||
import PostgREST.MediaType (MTPlanAttrs (..),
|
|
||||||
MediaType (..))
|
|
||||||
import PostgREST.Query (DbHandler)
|
import PostgREST.Query (DbHandler)
|
||||||
import PostgREST.Request.ApiRequest (Action (..),
|
import PostgREST.Request.ApiRequest (Action (..),
|
||||||
ApiRequest (..),
|
ApiRequest (..),
|
||||||
InvokeMethod (..),
|
InvokeMethod (..),
|
||||||
Mutation (..), Target (..))
|
Mutation (..), Target (..))
|
||||||
import PostgREST.Request.Preferences (PreferRepresentation (..))
|
import PostgREST.Request.Preferences (PreferRepresentation (..))
|
||||||
import PostgREST.Request.ReadQuery (ReadRequest, fstFieldNames)
|
import PostgREST.Request.ReadQuery (ReadRequest)
|
||||||
import PostgREST.Version (prettyVersion)
|
import PostgREST.Version (prettyVersion)
|
||||||
import PostgREST.Workers (connectionWorker, listener)
|
import PostgREST.Workers (connectionWorker, listener)
|
||||||
|
|
||||||
@@ -227,9 +224,8 @@ handleRequest context@(RequestContext _ _ ApiRequest{..} _) =
|
|||||||
handleRead :: Bool -> QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
handleRead :: Bool -> QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
||||||
handleRead headersOnly identifier context@RequestContext{..} = do
|
handleRead headersOnly identifier context@RequestContext{..} = do
|
||||||
req <- liftEither $ readRequest identifier context
|
req <- liftEither $ readRequest identifier context
|
||||||
bField <- binaryField context req
|
|
||||||
|
|
||||||
(resultSet, total) <- Query.readQuery req ctxConfig ctxApiRequest bField
|
(resultSet, total) <- Query.readQuery req ctxConfig ctxApiRequest
|
||||||
|
|
||||||
pure $ Response.readResponse headersOnly identifier ctxApiRequest total resultSet
|
pure $ Response.readResponse headersOnly identifier ctxApiRequest total resultSet
|
||||||
|
|
||||||
@@ -275,10 +271,9 @@ handleInvoke invMethod proc context@RequestContext{..} = do
|
|||||||
(fromMaybe (pdName proc) $ Proc.procTableName proc)
|
(fromMaybe (pdName proc) $ Proc.procTableName proc)
|
||||||
|
|
||||||
readReq <- liftEither $ readRequest identifier context
|
readReq <- liftEither $ readRequest identifier context
|
||||||
bField <- binaryField context readReq
|
|
||||||
let callReq = ReqBuilder.callRequest proc ctxApiRequest readReq
|
let callReq = ReqBuilder.callRequest proc ctxApiRequest readReq
|
||||||
|
|
||||||
resultSet <- Query.invokeQuery proc callReq readReq ctxApiRequest bField ctxConfig
|
resultSet <- Query.invokeQuery proc callReq readReq ctxApiRequest ctxConfig
|
||||||
|
|
||||||
pure $ Response.invokeResponse invMethod proc ctxApiRequest resultSet
|
pure $ Response.invokeResponse invMethod proc ctxApiRequest resultSet
|
||||||
|
|
||||||
@@ -293,39 +288,8 @@ writeRequest mutation identifier@QualifiedIdentifier{..} context@RequestContext{
|
|||||||
mutateReq <- ReqBuilder.mutateRequest mutation qiSchema qiName ctxApiRequest pkCols readReq
|
mutateReq <- ReqBuilder.mutateRequest mutation qiSchema qiName ctxApiRequest pkCols readReq
|
||||||
pure (mutateReq, readReq)
|
pure (mutateReq, readReq)
|
||||||
|
|
||||||
returnsScalar :: ApiRequest.Target -> Bool
|
|
||||||
returnsScalar (TargetProc proc _) = Proc.procReturnsScalar proc
|
|
||||||
returnsScalar _ = False
|
|
||||||
|
|
||||||
readRequest :: QualifiedIdentifier -> RequestContext -> Either Error ReadRequest
|
readRequest :: QualifiedIdentifier -> RequestContext -> Either Error ReadRequest
|
||||||
readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) =
|
readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) =
|
||||||
ReqBuilder.readRequest qiSchema qiName configDbMaxRows
|
ReqBuilder.readRequest qiSchema qiName configDbMaxRows
|
||||||
(dbRelationships dbStructure)
|
(dbRelationships dbStructure)
|
||||||
apiRequest
|
apiRequest
|
||||||
|
|
||||||
-- | If raw(binary) output is requested, check that MediaType is one of the
|
|
||||||
-- admitted rawMediaTypes and that`?select=...` contains only one field other
|
|
||||||
-- than `*`
|
|
||||||
binaryField :: Monad m => RequestContext -> ReadRequest -> Handler m (Maybe FieldName)
|
|
||||||
binaryField RequestContext{..} readReq
|
|
||||||
| returnsScalar (iTarget ctxApiRequest) && isRawMediaType =
|
|
||||||
return $ Just "pgrst_scalar"
|
|
||||||
| isRawMediaType =
|
|
||||||
let
|
|
||||||
fldNames = fstFieldNames readReq
|
|
||||||
fieldName = headMay fldNames
|
|
||||||
in
|
|
||||||
if length fldNames == 1 && fieldName /= Just "*" then
|
|
||||||
return fieldName
|
|
||||||
else
|
|
||||||
throwError $ Error.BinaryFieldError mediaType
|
|
||||||
| otherwise =
|
|
||||||
return Nothing
|
|
||||||
where
|
|
||||||
mediaType = iAcceptMediaType ctxApiRequest
|
|
||||||
isRawMediaType = mediaType `elem` configRawMediaTypes ctxConfig `union` [MTOctetStream, MTTextPlain, MTTextXML] || isRawPlan mediaType
|
|
||||||
isRawPlan mt = case mt of
|
|
||||||
MTPlan (MTPlanAttrs (Just MTOctetStream) _ _) -> True
|
|
||||||
MTPlan (MTPlanAttrs (Just MTTextPlain) _ _) -> True
|
|
||||||
MTPlan (MTPlanAttrs (Just MTTextXML) _ _) -> True
|
|
||||||
_ -> False
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class (JSON.ToJSON a) => PgrstError a where
|
|||||||
instance PgrstError ApiRequestError where
|
instance PgrstError ApiRequestError where
|
||||||
status AmbiguousRelBetween{} = HTTP.status300
|
status AmbiguousRelBetween{} = HTTP.status300
|
||||||
status AmbiguousRpc{} = HTTP.status300
|
status AmbiguousRpc{} = HTTP.status300
|
||||||
|
status BinaryFieldError{} = HTTP.status406
|
||||||
status MediaTypeError{} = HTTP.status415
|
status MediaTypeError{} = HTTP.status415
|
||||||
status InvalidBody{} = HTTP.status400
|
status InvalidBody{} = HTTP.status400
|
||||||
status InvalidFilters = HTTP.status405
|
status InvalidFilters = HTTP.status405
|
||||||
@@ -132,6 +133,12 @@ instance JSON.ToJSON ApiRequestError where
|
|||||||
"details" .= JSON.Null,
|
"details" .= JSON.Null,
|
||||||
"hint" .= ("Apply an 'order' using unique column(s)" :: Text)]
|
"hint" .= ("Apply an 'order' using unique column(s)" :: Text)]
|
||||||
|
|
||||||
|
toJSON (BinaryFieldError ct) = JSON.object [
|
||||||
|
"code" .= ApiRequestErrorCode13,
|
||||||
|
"message" .= ((T.decodeUtf8 (MediaType.toMime ct) <> " requested but more than one column was selected") :: Text),
|
||||||
|
"details" .= JSON.Null,
|
||||||
|
"hint" .= JSON.Null]
|
||||||
|
|
||||||
toJSON PutRangeNotAllowedError = JSON.object [
|
toJSON PutRangeNotAllowedError = JSON.object [
|
||||||
"code" .= ApiRequestErrorCode14,
|
"code" .= ApiRequestErrorCode14,
|
||||||
"message" .= ("Range header and limit/offset querystring parameters are not allowed for PUT" :: Text),
|
"message" .= ("Range header and limit/offset querystring parameters are not allowed for PUT" :: Text),
|
||||||
@@ -333,7 +340,6 @@ checkIsFatal _ = Nothing
|
|||||||
|
|
||||||
data Error
|
data Error
|
||||||
= ApiRequestError ApiRequestError
|
= ApiRequestError ApiRequestError
|
||||||
| BinaryFieldError MediaType
|
|
||||||
| GucHeadersError
|
| GucHeadersError
|
||||||
| GucStatusError
|
| GucStatusError
|
||||||
| JwtTokenInvalid Text
|
| JwtTokenInvalid Text
|
||||||
@@ -347,7 +353,6 @@ data Error
|
|||||||
|
|
||||||
instance PgrstError Error where
|
instance PgrstError Error where
|
||||||
status (ApiRequestError err) = status err
|
status (ApiRequestError err) = status err
|
||||||
status BinaryFieldError{} = HTTP.status406
|
|
||||||
status GucHeadersError = HTTP.status500
|
status GucHeadersError = HTTP.status500
|
||||||
status GucStatusError = HTTP.status500
|
status GucStatusError = HTTP.status500
|
||||||
status JwtTokenInvalid{} = HTTP.unauthorized401
|
status JwtTokenInvalid{} = HTTP.unauthorized401
|
||||||
@@ -405,11 +410,6 @@ instance JSON.ToJSON Error where
|
|||||||
"message" .= ("response.status guc must be a valid status code" :: Text),
|
"message" .= ("response.status guc must be a valid status code" :: Text),
|
||||||
"details" .= JSON.Null,
|
"details" .= JSON.Null,
|
||||||
"hint" .= JSON.Null]
|
"hint" .= JSON.Null]
|
||||||
toJSON (BinaryFieldError ct) = JSON.object [
|
|
||||||
"code" .= ApiRequestErrorCode13,
|
|
||||||
"message" .= ((T.decodeUtf8 (MediaType.toMime ct) <> " requested but more than one column was selected") :: Text),
|
|
||||||
"details" .= JSON.Null,
|
|
||||||
"hint" .= JSON.Null]
|
|
||||||
|
|
||||||
toJSON PutMatchingPkError = JSON.object [
|
toJSON PutMatchingPkError = JSON.object [
|
||||||
"code" .= ApiRequestErrorCode15,
|
"code" .= ApiRequestErrorCode15,
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ import Protolude hiding (Handler)
|
|||||||
|
|
||||||
type DbHandler = ExceptT Error SQL.Transaction
|
type DbHandler = ExceptT Error SQL.Transaction
|
||||||
|
|
||||||
readQuery :: ReadRequest -> AppConfig -> ApiRequest -> Maybe FieldName -> DbHandler (ResultSet, Maybe Int64)
|
readQuery :: ReadRequest -> AppConfig -> ApiRequest -> DbHandler (ResultSet, Maybe Int64)
|
||||||
readQuery req conf@AppConfig{..} apiReq@ApiRequest{..} bField = do
|
readQuery req conf@AppConfig{..} apiReq@ApiRequest{..} = do
|
||||||
let countQuery = QueryBuilder.readRequestToCountQuery req
|
let countQuery = QueryBuilder.readRequestToCountQuery req
|
||||||
resultSet <-
|
resultSet <-
|
||||||
lift . SQL.statement mempty $
|
lift . SQL.statement mempty $
|
||||||
@@ -82,7 +82,7 @@ readQuery req conf@AppConfig{..} apiReq@ApiRequest{..} bField = do
|
|||||||
)
|
)
|
||||||
(shouldCount iPreferCount)
|
(shouldCount iPreferCount)
|
||||||
iAcceptMediaType
|
iAcceptMediaType
|
||||||
bField
|
iBinaryField
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
total <- readTotal conf apiReq resultSet countQuery
|
total <- readTotal conf apiReq resultSet countQuery
|
||||||
@@ -144,8 +144,8 @@ deleteQuery mutateReq readReq apiReq@ApiRequest{..} conf = do
|
|||||||
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
invokeQuery :: ProcDescription -> ApiRequestTypes.CallRequest -> ReadRequest -> ApiRequest -> Maybe FieldName -> AppConfig -> DbHandler ResultSet
|
invokeQuery :: ProcDescription -> ApiRequestTypes.CallRequest -> ReadRequest -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
invokeQuery proc callReq readReq ApiRequest{..} bField AppConfig{..} = do
|
invokeQuery proc callReq readReq ApiRequest{..} AppConfig{..} = do
|
||||||
resultSet <-
|
resultSet <-
|
||||||
lift . SQL.statement mempty $
|
lift . SQL.statement mempty $
|
||||||
Statements.prepareCall
|
Statements.prepareCall
|
||||||
@@ -157,7 +157,7 @@ invokeQuery proc callReq readReq ApiRequest{..} bField AppConfig{..} = do
|
|||||||
(shouldCount iPreferCount)
|
(shouldCount iPreferCount)
|
||||||
iAcceptMediaType
|
iAcceptMediaType
|
||||||
(iPreferParameters == Just MultipleObjects)
|
(iPreferParameters == Just MultipleObjects)
|
||||||
bField
|
iBinaryField
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
|
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ import PostgREST.MediaType (MTPlanFormat (..),
|
|||||||
MTPlanOption (..))
|
MTPlanOption (..))
|
||||||
import PostgREST.RangeQuery (NonnegRange, allRange,
|
import PostgREST.RangeQuery (NonnegRange, allRange,
|
||||||
rangeLimit, rangeOffset)
|
rangeLimit, rangeOffset)
|
||||||
import PostgREST.Request.ReadQuery (SelectItem)
|
|
||||||
import PostgREST.Request.Types (Alias, Field, Filter (..),
|
import PostgREST.Request.Types (Alias, Field, Filter (..),
|
||||||
FtsOperator (..),
|
FtsOperator (..),
|
||||||
JoinCondition (..),
|
JoinCondition (..),
|
||||||
@@ -74,7 +73,7 @@ import PostgREST.Request.Types (Alias, Field, Filter (..),
|
|||||||
Operation (..),
|
Operation (..),
|
||||||
OrderDirection (..),
|
OrderDirection (..),
|
||||||
OrderNulls (..),
|
OrderNulls (..),
|
||||||
OrderTerm (..),
|
OrderTerm (..), SelectItem,
|
||||||
SimpleOperator (..),
|
SimpleOperator (..),
|
||||||
TrileanVal (..))
|
TrileanVal (..))
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ import Data.Aeson.Types (emptyArray, emptyObject)
|
|||||||
import Data.List (lookup, union)
|
import Data.List (lookup, union)
|
||||||
import Data.Ranged.Ranges (emptyRange, rangeIntersection,
|
import Data.Ranged.Ranges (emptyRange, rangeIntersection,
|
||||||
rangeIsEmpty)
|
rangeIsEmpty)
|
||||||
import Network.HTTP.Types.Header (hCookie, RequestHeaders)
|
import Data.Tree (Tree (..))
|
||||||
|
import Network.HTTP.Types.Header (RequestHeaders, hCookie)
|
||||||
import Network.HTTP.Types.URI (parseSimpleQuery)
|
import Network.HTTP.Types.URI (parseSimpleQuery)
|
||||||
import Network.Wai (Request (..))
|
import Network.Wai (Request (..))
|
||||||
import Network.Wai.Parse (parseHttpAccept)
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
@@ -50,7 +51,8 @@ import PostgREST.DbStructure.Identifiers (FieldName,
|
|||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
Schema)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcParam (..), ProcsMap)
|
ProcParam (..), ProcsMap,
|
||||||
|
procReturnsScalar)
|
||||||
import PostgREST.MediaType (MTPlanAttrs (..),
|
import PostgREST.MediaType (MTPlanAttrs (..),
|
||||||
MTPlanFormat (..),
|
MTPlanFormat (..),
|
||||||
MediaType (..))
|
MediaType (..))
|
||||||
@@ -65,7 +67,7 @@ import PostgREST.Request.Preferences (PreferCount (..),
|
|||||||
PreferTransaction (..))
|
PreferTransaction (..))
|
||||||
import PostgREST.Request.QueryParams (QueryParams (..))
|
import PostgREST.Request.QueryParams (QueryParams (..))
|
||||||
import PostgREST.Request.Types (ApiRequestError (..),
|
import PostgREST.Request.Types (ApiRequestError (..),
|
||||||
RangeError (..))
|
RangeError (..), SelectItem)
|
||||||
|
|
||||||
import qualified PostgREST.MediaType as MediaType
|
import qualified PostgREST.MediaType as MediaType
|
||||||
import qualified PostgREST.Request.Preferences as Preferences
|
import qualified PostgREST.Request.Preferences as Preferences
|
||||||
@@ -172,7 +174,8 @@ data ApiRequest = ApiRequest {
|
|||||||
, iMethod :: ByteString -- ^ Raw request method
|
, iMethod :: ByteString -- ^ Raw request method
|
||||||
, iSchema :: Schema -- ^ The request schema. Can vary depending on profile headers.
|
, iSchema :: Schema -- ^ The request schema. Can vary depending on profile headers.
|
||||||
, iNegotiatedByProfile :: Bool -- ^ If schema was was chosen according to the profile spec https://www.w3.org/TR/dx-prof-conneg/
|
, iNegotiatedByProfile :: Bool -- ^ If schema was was chosen according to the profile spec https://www.w3.org/TR/dx-prof-conneg/
|
||||||
, iAcceptMediaType :: MediaType
|
, iAcceptMediaType :: MediaType -- ^ The media type in the Accept header
|
||||||
|
, iBinaryField :: Maybe FieldName -- ^ field used for raw output
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Examines HTTP request and translates it into user intent.
|
-- | Examines HTTP request and translates it into user intent.
|
||||||
@@ -183,7 +186,7 @@ userApiRequest conf dbStructure req reqBody = do
|
|||||||
act <- getAction pInfo $ requestMethod req
|
act <- getAction pInfo $ requestMethod req
|
||||||
mediaTypes <- getMediaTypes conf (requestHeaders req) act pInfo
|
mediaTypes <- getMediaTypes conf (requestHeaders req) act pInfo
|
||||||
negotiatedSchema <- getSchema conf (requestHeaders req) (requestMethod req)
|
negotiatedSchema <- getSchema conf (requestHeaders req) (requestMethod req)
|
||||||
apiRequest dbStructure req reqBody qPrms pInfo act mediaTypes negotiatedSchema
|
apiRequest conf dbStructure req reqBody qPrms pInfo act mediaTypes negotiatedSchema
|
||||||
|
|
||||||
getPathInfo :: AppConfig -> [Text] -> Either ApiRequestError PathInfo
|
getPathInfo :: AppConfig -> [Text] -> Either ApiRequestError PathInfo
|
||||||
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
||||||
@@ -220,7 +223,7 @@ getAction PathInfo{pathIsProc, pathIsDefSpec} method =
|
|||||||
getMediaTypes :: AppConfig -> RequestHeaders -> Action -> PathInfo -> Either ApiRequestError (MediaType, MediaType)
|
getMediaTypes :: AppConfig -> RequestHeaders -> Action -> PathInfo -> Either ApiRequestError (MediaType, MediaType)
|
||||||
getMediaTypes conf hdrs action path = do
|
getMediaTypes conf hdrs action path = do
|
||||||
acceptMediaType <- findAcceptMediaType conf action path accepts
|
acceptMediaType <- findAcceptMediaType conf action path accepts
|
||||||
pure $ (acceptMediaType, contentMediaType)
|
pure (acceptMediaType, contentMediaType)
|
||||||
where
|
where
|
||||||
accepts = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
|
accepts = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
|
||||||
contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
|
contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
|
||||||
@@ -245,8 +248,8 @@ getSchema AppConfig{configDbSchemas} hdrs method = do
|
|||||||
acceptProfile = T.decodeUtf8 <$> lookupHeader "Accept-Profile"
|
acceptProfile = T.decodeUtf8 <$> lookupHeader "Accept-Profile"
|
||||||
lookupHeader = flip lookup hdrs
|
lookupHeader = flip lookup hdrs
|
||||||
|
|
||||||
apiRequest :: DbStructure -> Request -> RequestBody -> QueryParams.QueryParams -> PathInfo -> Action -> (MediaType, MediaType) -> (Schema, Bool) -> Either ApiRequestError ApiRequest
|
apiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> QueryParams.QueryParams -> PathInfo -> Action -> (MediaType, MediaType) -> (Schema, Bool) -> Either ApiRequestError ApiRequest
|
||||||
apiRequest dbStructure req reqBody queryparams@QueryParams{..} PathInfo{pathName, pathIsProc, pathIsRootSpec, pathIsDefSpec} action (acceptMediaType, contentMediaType) (schema, negotiatedByProfile)
|
apiRequest conf dbStructure req reqBody queryparams@QueryParams{..} PathInfo{pathName, pathIsProc, pathIsRootSpec, pathIsDefSpec} action (acceptMediaType, contentMediaType) (schema, negotiatedByProfile)
|
||||||
| isInvalidRange = Left $ InvalidRange (if rangeIsEmpty headerRange then LowerGTUpper else NegativeLimit)
|
| isInvalidRange = Left $ InvalidRange (if rangeIsEmpty headerRange then LowerGTUpper else NegativeLimit)
|
||||||
| shouldParsePayload && isLeft payload = either (Left . InvalidBody) witness payload
|
| shouldParsePayload && isLeft payload = either (Left . InvalidBody) witness payload
|
||||||
| not expectParams && not (L.null qsParams) = Left $ ParseRequestError "Unexpected param or filter missing operator" ("Failed to parse " <> show qsParams)
|
| not expectParams && not (L.null qsParams) = Left $ ParseRequestError "Unexpected param or filter missing operator" ("Failed to parse " <> show qsParams)
|
||||||
@@ -254,6 +257,7 @@ apiRequest dbStructure req reqBody queryparams@QueryParams{..} PathInfo{pathName
|
|||||||
| method == "PUT" && topLevelRange /= allRange = Left PutRangeNotAllowedError
|
| method == "PUT" && topLevelRange /= allRange = Left PutRangeNotAllowedError
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
checkedTarget <- target
|
checkedTarget <- target
|
||||||
|
bField <- binaryField conf acceptMediaType checkedTarget queryparams
|
||||||
return ApiRequest {
|
return ApiRequest {
|
||||||
iAction = action
|
iAction = action
|
||||||
, iTarget = checkedTarget
|
, iTarget = checkedTarget
|
||||||
@@ -274,6 +278,7 @@ apiRequest dbStructure req reqBody queryparams@QueryParams{..} PathInfo{pathName
|
|||||||
, iSchema = schema
|
, iSchema = schema
|
||||||
, iNegotiatedByProfile = negotiatedByProfile
|
, iNegotiatedByProfile = negotiatedByProfile
|
||||||
, iAcceptMediaType = acceptMediaType
|
, iAcceptMediaType = acceptMediaType
|
||||||
|
, iBinaryField = bField
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
expectParams = pathIsProc && method /= "POST"
|
expectParams = pathIsProc && method /= "POST"
|
||||||
@@ -494,3 +499,35 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPo
|
|||||||
-- If the function has required and optional parameters, the arguments keys have to match the required parameters
|
-- If the function has required and optional parameters, the arguments keys have to match the required parameters
|
||||||
-- and can match any or none of the default parameters.
|
-- and can match any or none of the default parameters.
|
||||||
(reqParams, optParams) -> argumentsKeys `S.difference` S.fromList (ppName <$> optParams) == S.fromList (ppName <$> reqParams)
|
(reqParams, optParams) -> argumentsKeys `S.difference` S.fromList (ppName <$> optParams) == S.fromList (ppName <$> reqParams)
|
||||||
|
|
||||||
|
-- | If raw(binary) output is requested, check that MediaType is one of the
|
||||||
|
-- admitted rawMediaTypes and that`?select=...` contains only one field other
|
||||||
|
-- than `*`
|
||||||
|
binaryField :: AppConfig -> MediaType -> Target -> QueryParams -> Either ApiRequestError (Maybe FieldName)
|
||||||
|
binaryField AppConfig{configRawMediaTypes} acceptMediaType target QueryParams{qsSelect}
|
||||||
|
| returnsScalar target && isRawMediaType =
|
||||||
|
Right $ Just "pgrst_scalar"
|
||||||
|
| isRawMediaType =
|
||||||
|
let
|
||||||
|
fieldName = fstFieldName qsSelect
|
||||||
|
in
|
||||||
|
case fieldName of
|
||||||
|
Just fld -> Right $ Just fld
|
||||||
|
Nothing -> Left $ BinaryFieldError acceptMediaType
|
||||||
|
| otherwise =
|
||||||
|
Right Nothing
|
||||||
|
where
|
||||||
|
isRawMediaType = acceptMediaType `elem` configRawMediaTypes `union` [MTOctetStream, MTTextPlain, MTTextXML] || isRawPlan acceptMediaType
|
||||||
|
isRawPlan mt = case mt of
|
||||||
|
MTPlan (MTPlanAttrs (Just MTOctetStream) _ _) -> True
|
||||||
|
MTPlan (MTPlanAttrs (Just MTTextPlain) _ _) -> True
|
||||||
|
MTPlan (MTPlanAttrs (Just MTTextXML) _ _) -> True
|
||||||
|
_ -> False
|
||||||
|
returnsScalar :: Target -> Bool
|
||||||
|
returnsScalar (TargetProc proc _) = procReturnsScalar proc
|
||||||
|
returnsScalar _ = False
|
||||||
|
|
||||||
|
fstFieldName :: [Tree SelectItem] -> Maybe FieldName
|
||||||
|
fstFieldName [Node (("*", _), Nothing, Nothing, Nothing, Nothing) []] = Nothing
|
||||||
|
fstFieldName [Node ((fld, _), Nothing, Nothing, Nothing, Nothing) []] = Just fld
|
||||||
|
fstFieldName _ = Nothing
|
||||||
|
|||||||
@@ -44,18 +44,16 @@ import PostgREST.RangeQuery (NonnegRange, allRange,
|
|||||||
rangeGeq, rangeLimit,
|
rangeGeq, rangeLimit,
|
||||||
rangeOffset, restrictRange)
|
rangeOffset, restrictRange)
|
||||||
|
|
||||||
import PostgREST.Request.ReadQuery (SelectItem)
|
import PostgREST.Request.Types (EmbedParam (..), EmbedPath, Field,
|
||||||
import PostgREST.Request.Types (EmbedParam (..), EmbedPath, Field,
|
Filter (..), FtsOperator (..),
|
||||||
Filter (..), FtsOperator (..),
|
JoinType (..), JsonOperand (..),
|
||||||
JoinType (..), JsonOperand (..),
|
JsonOperation (..), JsonPath, ListVal,
|
||||||
JsonOperation (..), JsonPath,
|
LogicOperator (..), LogicTree (..),
|
||||||
ListVal, LogicOperator (..),
|
OpExpr (..), Operation (..),
|
||||||
LogicTree (..), OpExpr (..),
|
OrderDirection (..), OrderNulls (..),
|
||||||
Operation (..),
|
OrderTerm (..), QPError (..),
|
||||||
OrderDirection (..),
|
SelectItem, SimpleOperator (..),
|
||||||
OrderNulls (..), OrderTerm (..),
|
SingleVal, TrileanVal (..))
|
||||||
QPError (..), SimpleOperator (..),
|
|
||||||
SingleVal, TrileanVal (..))
|
|
||||||
|
|
||||||
import Protolude hiding (try)
|
import Protolude hiding (try)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ module PostgREST.Request.ReadQuery
|
|||||||
( ReadNode
|
( ReadNode
|
||||||
, ReadQuery(..)
|
, ReadQuery(..)
|
||||||
, ReadRequest
|
, ReadRequest
|
||||||
, SelectItem
|
|
||||||
, fstFieldNames
|
, fstFieldNames
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@@ -12,10 +11,10 @@ import PostgREST.DbStructure.Identifiers (FieldName,
|
|||||||
QualifiedIdentifier)
|
QualifiedIdentifier)
|
||||||
import PostgREST.DbStructure.Relationship (Relationship)
|
import PostgREST.DbStructure.Relationship (Relationship)
|
||||||
import PostgREST.RangeQuery (NonnegRange)
|
import PostgREST.RangeQuery (NonnegRange)
|
||||||
import PostgREST.Request.Types (Alias, Cast, Depth, Field,
|
import PostgREST.Request.Types (Alias, Depth, Hint,
|
||||||
Hint, JoinCondition,
|
JoinCondition, JoinType,
|
||||||
JoinType, LogicTree,
|
LogicTree, NodeName,
|
||||||
NodeName, OrderTerm)
|
OrderTerm, SelectItem)
|
||||||
|
|
||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
@@ -25,9 +24,6 @@ type ReadRequest = Tree ReadNode
|
|||||||
type ReadNode =
|
type ReadNode =
|
||||||
(ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe Hint, Maybe JoinType, Depth))
|
(ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe Hint, Maybe JoinType, Depth))
|
||||||
|
|
||||||
-- | The select value in `/tbl?select=alias:field::cast`
|
|
||||||
type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe Hint, Maybe JoinType)
|
|
||||||
|
|
||||||
data ReadQuery = Select
|
data ReadQuery = Select
|
||||||
{ select :: [SelectItem]
|
{ select :: [SelectItem]
|
||||||
, from :: QualifiedIdentifier
|
, from :: QualifiedIdentifier
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ module PostgREST.Request.Types
|
|||||||
, TrileanVal(..)
|
, TrileanVal(..)
|
||||||
, SimpleOperator(..)
|
, SimpleOperator(..)
|
||||||
, FtsOperator(..)
|
, FtsOperator(..)
|
||||||
|
, SelectItem
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
@@ -45,11 +46,13 @@ import PostgREST.MediaType (MediaType (..))
|
|||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
|
-- | The select value in `/tbl?select=alias:field::cast`
|
||||||
|
type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe Hint, Maybe JoinType)
|
||||||
|
|
||||||
data ApiRequestError
|
data ApiRequestError
|
||||||
= AmbiguousRelBetween Text Text [Relationship]
|
= AmbiguousRelBetween Text Text [Relationship]
|
||||||
| AmbiguousRpc [ProcDescription]
|
| AmbiguousRpc [ProcDescription]
|
||||||
|
| BinaryFieldError MediaType
|
||||||
| MediaTypeError [ByteString]
|
| MediaTypeError [ByteString]
|
||||||
| InvalidBody ByteString
|
| InvalidBody ByteString
|
||||||
| InvalidFilters
|
| InvalidFilters
|
||||||
|
|||||||
Reference in New Issue
Block a user