fix: no empty tx on bad HTTP method
This commit is contained in:
committed by
Steve Chavez
parent
6d7bf9faa9
commit
8230128ff6
+10
-11
@@ -54,12 +54,12 @@ class (JSON.ToJSON a) => PgrstError a where
|
||||
errorResponseFor err = responseLBS (status err) (headers err) $ errorPayload err
|
||||
|
||||
instance PgrstError ApiRequestError where
|
||||
status ActionInappropriate = HTTP.status405
|
||||
status AmbiguousRelBetween{} = HTTP.status300
|
||||
status AmbiguousRpc{} = HTTP.status300
|
||||
status MediaTypeError{} = HTTP.status415
|
||||
status InvalidBody{} = HTTP.status400
|
||||
status InvalidFilters = HTTP.status405
|
||||
status InvalidRpcMethod{} = HTTP.status405
|
||||
status InvalidRange = HTTP.status416
|
||||
status NotFound = HTTP.status404
|
||||
status NoRelBetween{} = HTTP.status400
|
||||
@@ -69,6 +69,7 @@ instance PgrstError ApiRequestError where
|
||||
status PutRangeNotAllowedError = HTTP.status400
|
||||
status QueryParamError{} = HTTP.status400
|
||||
status UnacceptableSchema{} = HTTP.status406
|
||||
status UnsupportedMethod{} = HTTP.status405
|
||||
status LimitNoOrderError = HTTP.status400
|
||||
|
||||
headers _ = [MediaType.toContentType MTApplicationJSON]
|
||||
@@ -79,9 +80,9 @@ instance JSON.ToJSON ApiRequestError where
|
||||
"message" .= message,
|
||||
"details" .= details,
|
||||
"hint" .= JSON.Null]
|
||||
toJSON ActionInappropriate = JSON.object [
|
||||
toJSON (InvalidRpcMethod method) = JSON.object [
|
||||
"code" .= ApiRequestErrorCode01,
|
||||
"message" .= ("Bad Request" :: Text),
|
||||
"message" .= ("Cannot use the " <> T.decodeUtf8 method <> " method on RPC"),
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= JSON.Null]
|
||||
toJSON (InvalidBody errorMessage) = JSON.object [
|
||||
@@ -133,6 +134,12 @@ instance JSON.ToJSON ApiRequestError where
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (UnsupportedMethod method) = JSON.object [
|
||||
"code" .= ApiRequestErrorCode17,
|
||||
"message" .= ("Unsupported HTTP method: " <> T.decodeUtf8 method),
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (NoRelBetween parent child schema) = JSON.object [
|
||||
"code" .= SchemaCacheErrorCode00,
|
||||
"message" .= ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache" :: Text),
|
||||
@@ -317,7 +324,6 @@ data Error
|
||||
| PgErr PgError
|
||||
| PutMatchingPkError
|
||||
| SingularityError Integer
|
||||
| UnsupportedVerb Text
|
||||
|
||||
instance PgrstError Error where
|
||||
status (ApiRequestError err) = status err
|
||||
@@ -332,7 +338,6 @@ instance PgrstError Error where
|
||||
status (PgErr err) = status err
|
||||
status PutMatchingPkError = HTTP.status400
|
||||
status SingularityError{} = HTTP.status406
|
||||
status UnsupportedVerb{} = HTTP.status405
|
||||
|
||||
headers (ApiRequestError err) = headers err
|
||||
headers (JwtTokenInvalid m) = [MediaType.toContentType MTApplicationJSON, invalidTokenHeader m]
|
||||
@@ -398,12 +403,6 @@ instance JSON.ToJSON Error where
|
||||
"details" .= T.unwords ["Results contain", show n, "rows,", T.decodeUtf8 (MediaType.toMime MTSingularJSON), "requires 1 row"],
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (UnsupportedVerb verb) = JSON.object [
|
||||
"code" .= ApiRequestErrorCode17,
|
||||
"message" .= ("Unsupported HTTP verb: " <> verb),
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (PgErr err) = JSON.toJSON err
|
||||
toJSON (ApiRequestError err) = JSON.toJSON err
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ data Action
|
||||
| ActionInvoke InvokeMethod
|
||||
| ActionInfo
|
||||
| ActionInspect {isHead :: Bool}
|
||||
| ActionUnknown Text
|
||||
deriving Eq
|
||||
-- | The path info that will be mapped to a target (used to handle validations and errors before defining the Target)
|
||||
data PathInfo
|
||||
@@ -152,7 +151,7 @@ targetToJsonRpcParams target params =
|
||||
if it is an action we are able to perform.
|
||||
-}
|
||||
data ApiRequest = ApiRequest {
|
||||
iAction :: Action -- ^ Similar but not identical to HTTP verb, e.g. Create/Invoke both POST
|
||||
iAction :: Action -- ^ Similar but not identical to HTTP method, e.g. Create/Invoke both POST
|
||||
, iRange :: HM.HashMap Text NonnegRange -- ^ Requested range of rows within response
|
||||
, iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
|
||||
, iTarget :: Target -- ^ The target, be it calling a proc or accessing a table
|
||||
@@ -176,9 +175,10 @@ data ApiRequest = ApiRequest {
|
||||
-- | Examines HTTP request and translates it into user intent.
|
||||
userApiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest
|
||||
userApiRequest conf dbStructure req reqBody = do
|
||||
qPrms <- first QueryParamError (QueryParams.parse (rawQueryString req))
|
||||
qPrms <- first QueryParamError $ QueryParams.parse $ rawQueryString req
|
||||
pInfo <- getPathInfo conf $ pathInfo req
|
||||
apiRequest conf dbStructure req reqBody qPrms pInfo
|
||||
act <- getAction pInfo $ requestMethod req
|
||||
apiRequest conf dbStructure req reqBody qPrms pInfo act
|
||||
|
||||
getPathInfo :: AppConfig -> [Text] -> Either ApiRequestError PathInfo
|
||||
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
||||
@@ -191,10 +191,30 @@ getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
||||
["rpc", pName] -> Right $ PathInfo pName True False False
|
||||
_ -> Left NotFound
|
||||
|
||||
apiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> QueryParams.QueryParams -> PathInfo -> Either ApiRequestError ApiRequest
|
||||
apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..} path@PathInfo{pathName, pathIsProc, pathIsRootSpec, pathIsDefSpec}
|
||||
getAction :: PathInfo -> ByteString -> Either ApiRequestError Action
|
||||
getAction PathInfo{pathIsProc, pathIsDefSpec} method =
|
||||
if pathIsProc && method `notElem` ["HEAD", "GET", "POST"]
|
||||
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
|
||||
|
||||
apiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> QueryParams.QueryParams -> PathInfo -> Action -> Either ApiRequestError ApiRequest
|
||||
apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..} path@PathInfo{pathName, pathIsProc, pathIsRootSpec, pathIsDefSpec} action
|
||||
| isJust profile && fromJust profile `notElem` configDbSchemas = Left $ UnacceptableSchema $ toList configDbSchemas
|
||||
| pathIsProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate
|
||||
| isInvalidRange = Left InvalidRange
|
||||
| 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)
|
||||
@@ -266,24 +286,6 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
||||
(MTOctetStream, True) -> Right $ RawPay reqBody
|
||||
(ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct
|
||||
topLevelRange = fromMaybe allRange $ HM.lookup "limit" ranges -- if no limit is specified, get all the request rows
|
||||
action =
|
||||
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 -> ActionInspect{isHead=True}
|
||||
| pathIsProc -> ActionInvoke InvHead
|
||||
| otherwise -> ActionRead{isHead=True}
|
||||
"GET" | pathIsDefSpec -> ActionInspect{isHead=False}
|
||||
| pathIsProc -> ActionInvoke InvGet
|
||||
| otherwise -> ActionRead{isHead=False}
|
||||
"POST" -> if pathIsProc
|
||||
then ActionInvoke InvPost
|
||||
else ActionMutate MutationCreate
|
||||
"PATCH" -> ActionMutate MutationUpdate
|
||||
"PUT" -> ActionMutate MutationSingleUpsert
|
||||
"DELETE" -> ActionMutate MutationDelete
|
||||
"OPTIONS" -> ActionInfo
|
||||
_ -> ActionUnknown $ T.decodeUtf8 method
|
||||
|
||||
defaultSchema = NonEmptyList.head configDbSchemas
|
||||
profile
|
||||
@@ -305,7 +307,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
||||
target
|
||||
| pathIsProc = (`TargetProc` pathIsRootSpec) <$> callFindProc schema pathName
|
||||
| pathIsDefSpec = Right $ TargetDefaultSpec schema
|
||||
| otherwise = Right $ TargetIdent $ QualifiedIdentifier schema pathName
|
||||
| otherwise = Right $ TargetIdent $ QualifiedIdentifier schema pathName
|
||||
where
|
||||
callFindProc procSch procNam = findProc
|
||||
(QualifiedIdentifier procSch procNam) payloadColumns (preferParameters == Just SingleObject) (dbProcs dbStructure)
|
||||
|
||||
@@ -47,13 +47,13 @@ import Protolude
|
||||
|
||||
|
||||
data ApiRequestError
|
||||
= ActionInappropriate
|
||||
| AmbiguousRelBetween Text Text [Relationship]
|
||||
= AmbiguousRelBetween Text Text [Relationship]
|
||||
| AmbiguousRpc [ProcDescription]
|
||||
| MediaTypeError [ByteString]
|
||||
| InvalidBody ByteString
|
||||
| InvalidFilters
|
||||
| InvalidRange
|
||||
| InvalidRpcMethod ByteString
|
||||
| LimitNoOrderError
|
||||
| NotFound
|
||||
| NoRelBetween Text Text Text
|
||||
@@ -63,6 +63,7 @@ data ApiRequestError
|
||||
| PutRangeNotAllowedError
|
||||
| QueryParamError QPError
|
||||
| UnacceptableSchema [Text]
|
||||
| UnsupportedMethod ByteString
|
||||
|
||||
data QPError = QPError Text Text
|
||||
|
||||
|
||||
Reference in New Issue
Block a user