fix: no empty tx on bad HTTP method
This commit is contained in:
committed by
Steve Chavez
parent
6d7bf9faa9
commit
8230128ff6
+1
-1
@@ -54,7 +54,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #1959, An accidental full table PATCH(without filters) is not possible anymore, it requires filters or a `limit` parameter - @steve-chavez, @laurenceisla
|
- #1959, An accidental full table PATCH(without filters) is not possible anymore, it requires filters or a `limit` parameter - @steve-chavez, @laurenceisla
|
||||||
- #2317, Increase the `db-pool-timeout` to 1 hour to prevent frequent high connection latency - @steve-chavez
|
- #2317, Increase the `db-pool-timeout` to 1 hour to prevent frequent high connection latency - @steve-chavez
|
||||||
- #2341, The search path now correctly identifies schemas with uppercase and special characters in their names (regression) - @laurenceisla
|
- #2341, The search path now correctly identifies schemas with uppercase and special characters in their names (regression) - @laurenceisla
|
||||||
- #2364, "404 Not Found" on nested routes doesn't start an empty database transaction - @steve-chavez
|
- #2364, "404 Not Found" on nested routes and "405 Method Not Allowed" errors no longer start an empty database transaction - @steve-chavez
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ name: postgrest
|
|||||||
version: 9.0.1.20220630
|
version: 9.0.1.20220630
|
||||||
synopsis: REST API for any Postgres database
|
synopsis: REST API for any Postgres database
|
||||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||||
for tables, views, and functions, supporting all HTTP verbs that security
|
for tables, views, and functions, supporting all HTTP methods that security
|
||||||
permits.
|
permits.
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
|||||||
+10
-11
@@ -54,12 +54,12 @@ class (JSON.ToJSON a) => PgrstError a where
|
|||||||
errorResponseFor err = responseLBS (status err) (headers err) $ errorPayload err
|
errorResponseFor err = responseLBS (status err) (headers err) $ errorPayload err
|
||||||
|
|
||||||
instance PgrstError ApiRequestError where
|
instance PgrstError ApiRequestError where
|
||||||
status ActionInappropriate = HTTP.status405
|
|
||||||
status AmbiguousRelBetween{} = HTTP.status300
|
status AmbiguousRelBetween{} = HTTP.status300
|
||||||
status AmbiguousRpc{} = HTTP.status300
|
status AmbiguousRpc{} = HTTP.status300
|
||||||
status MediaTypeError{} = HTTP.status415
|
status MediaTypeError{} = HTTP.status415
|
||||||
status InvalidBody{} = HTTP.status400
|
status InvalidBody{} = HTTP.status400
|
||||||
status InvalidFilters = HTTP.status405
|
status InvalidFilters = HTTP.status405
|
||||||
|
status InvalidRpcMethod{} = HTTP.status405
|
||||||
status InvalidRange = HTTP.status416
|
status InvalidRange = HTTP.status416
|
||||||
status NotFound = HTTP.status404
|
status NotFound = HTTP.status404
|
||||||
status NoRelBetween{} = HTTP.status400
|
status NoRelBetween{} = HTTP.status400
|
||||||
@@ -69,6 +69,7 @@ instance PgrstError ApiRequestError where
|
|||||||
status PutRangeNotAllowedError = HTTP.status400
|
status PutRangeNotAllowedError = HTTP.status400
|
||||||
status QueryParamError{} = HTTP.status400
|
status QueryParamError{} = HTTP.status400
|
||||||
status UnacceptableSchema{} = HTTP.status406
|
status UnacceptableSchema{} = HTTP.status406
|
||||||
|
status UnsupportedMethod{} = HTTP.status405
|
||||||
status LimitNoOrderError = HTTP.status400
|
status LimitNoOrderError = HTTP.status400
|
||||||
|
|
||||||
headers _ = [MediaType.toContentType MTApplicationJSON]
|
headers _ = [MediaType.toContentType MTApplicationJSON]
|
||||||
@@ -79,9 +80,9 @@ instance JSON.ToJSON ApiRequestError where
|
|||||||
"message" .= message,
|
"message" .= message,
|
||||||
"details" .= details,
|
"details" .= details,
|
||||||
"hint" .= JSON.Null]
|
"hint" .= JSON.Null]
|
||||||
toJSON ActionInappropriate = JSON.object [
|
toJSON (InvalidRpcMethod method) = JSON.object [
|
||||||
"code" .= ApiRequestErrorCode01,
|
"code" .= ApiRequestErrorCode01,
|
||||||
"message" .= ("Bad Request" :: Text),
|
"message" .= ("Cannot use the " <> T.decodeUtf8 method <> " method on RPC"),
|
||||||
"details" .= JSON.Null,
|
"details" .= JSON.Null,
|
||||||
"hint" .= JSON.Null]
|
"hint" .= JSON.Null]
|
||||||
toJSON (InvalidBody errorMessage) = JSON.object [
|
toJSON (InvalidBody errorMessage) = JSON.object [
|
||||||
@@ -133,6 +134,12 @@ instance JSON.ToJSON ApiRequestError where
|
|||||||
"details" .= JSON.Null,
|
"details" .= JSON.Null,
|
||||||
"hint" .= 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 [
|
toJSON (NoRelBetween parent child schema) = JSON.object [
|
||||||
"code" .= SchemaCacheErrorCode00,
|
"code" .= SchemaCacheErrorCode00,
|
||||||
"message" .= ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache" :: Text),
|
"message" .= ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache" :: Text),
|
||||||
@@ -317,7 +324,6 @@ data Error
|
|||||||
| PgErr PgError
|
| PgErr PgError
|
||||||
| PutMatchingPkError
|
| PutMatchingPkError
|
||||||
| SingularityError Integer
|
| SingularityError Integer
|
||||||
| UnsupportedVerb Text
|
|
||||||
|
|
||||||
instance PgrstError Error where
|
instance PgrstError Error where
|
||||||
status (ApiRequestError err) = status err
|
status (ApiRequestError err) = status err
|
||||||
@@ -332,7 +338,6 @@ instance PgrstError Error where
|
|||||||
status (PgErr err) = status err
|
status (PgErr err) = status err
|
||||||
status PutMatchingPkError = HTTP.status400
|
status PutMatchingPkError = HTTP.status400
|
||||||
status SingularityError{} = HTTP.status406
|
status SingularityError{} = HTTP.status406
|
||||||
status UnsupportedVerb{} = HTTP.status405
|
|
||||||
|
|
||||||
headers (ApiRequestError err) = headers err
|
headers (ApiRequestError err) = headers err
|
||||||
headers (JwtTokenInvalid m) = [MediaType.toContentType MTApplicationJSON, invalidTokenHeader m]
|
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"],
|
"details" .= T.unwords ["Results contain", show n, "rows,", T.decodeUtf8 (MediaType.toMime MTSingularJSON), "requires 1 row"],
|
||||||
"hint" .= JSON.Null]
|
"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 (PgErr err) = JSON.toJSON err
|
||||||
toJSON (ApiRequestError err) = JSON.toJSON err
|
toJSON (ApiRequestError err) = JSON.toJSON err
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ data Action
|
|||||||
| ActionInvoke InvokeMethod
|
| ActionInvoke InvokeMethod
|
||||||
| ActionInfo
|
| ActionInfo
|
||||||
| ActionInspect {isHead :: Bool}
|
| ActionInspect {isHead :: Bool}
|
||||||
| ActionUnknown Text
|
|
||||||
deriving Eq
|
deriving Eq
|
||||||
-- | The path info that will be mapped to a target (used to handle validations and errors before defining the Target)
|
-- | The path info that will be mapped to a target (used to handle validations and errors before defining the Target)
|
||||||
data PathInfo
|
data PathInfo
|
||||||
@@ -152,7 +151,7 @@ targetToJsonRpcParams target params =
|
|||||||
if it is an action we are able to perform.
|
if it is an action we are able to perform.
|
||||||
-}
|
-}
|
||||||
data ApiRequest = ApiRequest {
|
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
|
, iRange :: HM.HashMap Text 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
|
||||||
@@ -176,9 +175,10 @@ data ApiRequest = ApiRequest {
|
|||||||
-- | Examines HTTP request and translates it into user intent.
|
-- | Examines HTTP request and translates it into user intent.
|
||||||
userApiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest
|
userApiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> Either ApiRequestError ApiRequest
|
||||||
userApiRequest conf dbStructure req reqBody = do
|
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
|
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 -> [Text] -> Either ApiRequestError PathInfo
|
||||||
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
||||||
@@ -191,10 +191,30 @@ getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
|||||||
["rpc", pName] -> Right $ PathInfo pName True False False
|
["rpc", pName] -> Right $ PathInfo pName True False False
|
||||||
_ -> Left NotFound
|
_ -> Left NotFound
|
||||||
|
|
||||||
apiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> QueryParams.QueryParams -> PathInfo -> Either ApiRequestError ApiRequest
|
getAction :: PathInfo -> ByteString -> Either ApiRequestError Action
|
||||||
apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..} path@PathInfo{pathName, pathIsProc, pathIsRootSpec, pathIsDefSpec}
|
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
|
| isJust profile && fromJust profile `notElem` configDbSchemas = Left $ UnacceptableSchema $ toList configDbSchemas
|
||||||
| pathIsProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate
|
|
||||||
| isInvalidRange = Left InvalidRange
|
| isInvalidRange = Left InvalidRange
|
||||||
| 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)
|
||||||
@@ -266,24 +286,6 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
|||||||
(MTOctetStream, True) -> Right $ RawPay reqBody
|
(MTOctetStream, True) -> Right $ RawPay reqBody
|
||||||
(ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct
|
(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
|
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
|
defaultSchema = NonEmptyList.head configDbSchemas
|
||||||
profile
|
profile
|
||||||
@@ -305,7 +307,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
|||||||
target
|
target
|
||||||
| pathIsProc = (`TargetProc` pathIsRootSpec) <$> callFindProc schema pathName
|
| pathIsProc = (`TargetProc` pathIsRootSpec) <$> callFindProc schema pathName
|
||||||
| pathIsDefSpec = Right $ TargetDefaultSpec schema
|
| pathIsDefSpec = Right $ TargetDefaultSpec schema
|
||||||
| otherwise = Right $ TargetIdent $ QualifiedIdentifier schema pathName
|
| otherwise = Right $ TargetIdent $ QualifiedIdentifier schema pathName
|
||||||
where
|
where
|
||||||
callFindProc procSch procNam = findProc
|
callFindProc procSch procNam = findProc
|
||||||
(QualifiedIdentifier procSch procNam) payloadColumns (preferParameters == Just SingleObject) (dbProcs dbStructure)
|
(QualifiedIdentifier procSch procNam) payloadColumns (preferParameters == Just SingleObject) (dbProcs dbStructure)
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ import Protolude
|
|||||||
|
|
||||||
|
|
||||||
data ApiRequestError
|
data ApiRequestError
|
||||||
= ActionInappropriate
|
= AmbiguousRelBetween Text Text [Relationship]
|
||||||
| AmbiguousRelBetween Text Text [Relationship]
|
|
||||||
| AmbiguousRpc [ProcDescription]
|
| AmbiguousRpc [ProcDescription]
|
||||||
| MediaTypeError [ByteString]
|
| MediaTypeError [ByteString]
|
||||||
| InvalidBody ByteString
|
| InvalidBody ByteString
|
||||||
| InvalidFilters
|
| InvalidFilters
|
||||||
| InvalidRange
|
| InvalidRange
|
||||||
|
| InvalidRpcMethod ByteString
|
||||||
| LimitNoOrderError
|
| LimitNoOrderError
|
||||||
| NotFound
|
| NotFound
|
||||||
| NoRelBetween Text Text Text
|
| NoRelBetween Text Text Text
|
||||||
@@ -63,6 +63,7 @@ data ApiRequestError
|
|||||||
| PutRangeNotAllowedError
|
| PutRangeNotAllowedError
|
||||||
| QueryParamError QPError
|
| QueryParamError QPError
|
||||||
| UnacceptableSchema [Text]
|
| UnacceptableSchema [Text]
|
||||||
|
| UnsupportedMethod ByteString
|
||||||
|
|
||||||
data QPError = QPError Text Text
|
data QPError = QPError Text Text
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import SpecHelper
|
|||||||
spec :: PgVersion -> SpecWith ((), Application)
|
spec :: PgVersion -> SpecWith ((), Application)
|
||||||
spec actualPgVersion = describe "Allow header" $ do
|
spec actualPgVersion = describe "Allow header" $ do
|
||||||
context "a table" $ do
|
context "a table" $ do
|
||||||
it "includes read/write verbs for writeable table" $ do
|
it "includes read/write methods for writeable table" $ do
|
||||||
r <- request methodOptions "/items" [] ""
|
r <- request methodOptions "/items" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
@@ -24,7 +24,7 @@ spec actualPgVersion = describe "Allow header" $ do
|
|||||||
|
|
||||||
when (actualPgVersion >= pgVersion100) $
|
when (actualPgVersion >= pgVersion100) $
|
||||||
context "a partitioned table" $ do
|
context "a partitioned table" $ do
|
||||||
it "includes read/write verbs for writeable partitioned tables" $ do
|
it "includes read/write methods for writeable partitioned tables" $ do
|
||||||
r <- request methodOptions "/car_models" [] ""
|
r <- request methodOptions "/car_models" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
@@ -37,50 +37,50 @@ spec actualPgVersion = describe "Allow header" $ do
|
|||||||
|
|
||||||
context "a view" $ do
|
context "a view" $ do
|
||||||
context "auto updatable" $ do
|
context "auto updatable" $ do
|
||||||
it "includes read/write verbs for auto updatable views with pk" $ do
|
it "includes read/write methods for auto updatable views with pk" $ do
|
||||||
r <- request methodOptions "/projects_auto_updatable_view_with_pk" [] ""
|
r <- request methodOptions "/projects_auto_updatable_view_with_pk" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||||
|
|
||||||
it "includes read/write verbs for auto updatable views without pk" $ do
|
it "includes read/write methods for auto updatable views without pk" $ do
|
||||||
r <- request methodOptions "/projects_auto_updatable_view_without_pk" [] ""
|
r <- request methodOptions "/projects_auto_updatable_view_without_pk" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PATCH,DELETE"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PATCH,DELETE"
|
||||||
|
|
||||||
context "non auto updatable" $ do
|
context "non auto updatable" $ do
|
||||||
it "includes read verbs for non auto updatable views" $ do
|
it "includes read methods for non auto updatable views" $ do
|
||||||
r <- request methodOptions "/projects_view_without_triggers" [] ""
|
r <- request methodOptions "/projects_view_without_triggers" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD"
|
matchHeader "Allow" "OPTIONS,GET,HEAD"
|
||||||
|
|
||||||
it "includes read/write verbs for insertable, updatable and deletable views with pk" $ do
|
it "includes read/write methods for insertable, updatable and deletable views with pk" $ do
|
||||||
r <- request methodOptions "/projects_view_with_all_triggers_with_pk" [] ""
|
r <- request methodOptions "/projects_view_with_all_triggers_with_pk" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||||
|
|
||||||
it "includes read/write verbs for insertable, updatable and deletable views without pk" $ do
|
it "includes read/write methods for insertable, updatable and deletable views without pk" $ do
|
||||||
r <- request methodOptions "/projects_view_with_all_triggers_without_pk" [] ""
|
r <- request methodOptions "/projects_view_with_all_triggers_without_pk" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PATCH,DELETE"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PATCH,DELETE"
|
||||||
|
|
||||||
it "includes read and insert verbs for insertable views" $ do
|
it "includes read and insert methods for insertable views" $ do
|
||||||
r <- request methodOptions "/projects_view_with_insert_trigger" [] ""
|
r <- request methodOptions "/projects_view_with_insert_trigger" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST"
|
||||||
|
|
||||||
it "includes read and update verbs for updatable views" $ do
|
it "includes read and update methods for updatable views" $ do
|
||||||
r <- request methodOptions "/projects_view_with_update_trigger" [] ""
|
r <- request methodOptions "/projects_view_with_update_trigger" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,PATCH"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,PATCH"
|
||||||
|
|
||||||
it "includes read and delete verbs for deletable views" $ do
|
it "includes read and delete methods for deletable views" $ do
|
||||||
r <- request methodOptions "/projects_view_with_delete_trigger" [] ""
|
r <- request methodOptions "/projects_view_with_delete_trigger" [] ""
|
||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ spec = do
|
|||||||
{"hint": null,
|
{"hint": null,
|
||||||
"details": null,
|
"details": null,
|
||||||
"code": "PGRST117",
|
"code": "PGRST117",
|
||||||
"message":"Unsupported HTTP verb: CONNECT"}|]
|
"message":"Unsupported HTTP method: CONNECT"}|]
|
||||||
{ matchStatus = 405 }
|
{ matchStatus = 405 }
|
||||||
|
|
||||||
it "should return 405 for TRACE method" $
|
it "should return 405 for TRACE method" $
|
||||||
@@ -47,7 +47,7 @@ spec = do
|
|||||||
{"hint": null,
|
{"hint": null,
|
||||||
"details": null,
|
"details": null,
|
||||||
"code": "PGRST117",
|
"code": "PGRST117",
|
||||||
"message":"Unsupported HTTP verb: TRACE"}|]
|
"message":"Unsupported HTTP method: TRACE"}|]
|
||||||
{ matchStatus = 405 }
|
{ matchStatus = 405 }
|
||||||
|
|
||||||
it "should return 405 for OTHER method" $
|
it "should return 405 for OTHER method" $
|
||||||
@@ -59,5 +59,5 @@ spec = do
|
|||||||
{"hint": null,
|
{"hint": null,
|
||||||
"details": null,
|
"details": null,
|
||||||
"code": "PGRST117",
|
"code": "PGRST117",
|
||||||
"message":"Unsupported HTTP verb: OTHER"}|]
|
"message":"Unsupported HTTP method: OTHER"}|]
|
||||||
{ matchStatus = 405 }
|
{ matchStatus = 405 }
|
||||||
|
|||||||
@@ -516,11 +516,11 @@ spec actualPgVersion =
|
|||||||
simpleStatus p `shouldBe` internalServerError500
|
simpleStatus p `shouldBe` internalServerError500
|
||||||
isErrorFormat (simpleBody p) `shouldBe` True
|
isErrorFormat (simpleBody p) `shouldBe` True
|
||||||
|
|
||||||
context "unsupported verbs" $ do
|
context "unsupported method" $ do
|
||||||
it "DELETE fails" $
|
it "DELETE fails" $
|
||||||
request methodDelete "/rpc/sayhello" [] ""
|
request methodDelete "/rpc/sayhello" [] ""
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|{"message":"Bad Request","code":"PGRST101","details":null,"hint":null}|]
|
[json|{"message":"Cannot use the DELETE method on RPC","code":"PGRST101","details":null,"hint":null}|]
|
||||||
{ matchStatus = 405
|
{ matchStatus = 405
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user