feat: support OPTIONS on RPC and root path
This commit is contained in:
committed by
Steve Chavez
parent
71a5748718
commit
2eb7c803e3
@@ -35,6 +35,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
+ Resource embedding works and the embedded rows will go into the `properties` key
|
+ Resource embedding works and the embedded rows will go into the `properties` key
|
||||||
+ In case of multiple geometries in the same table, you can choose which one will go into the `geometry` key with the usual `?select` query parameter.
|
+ In case of multiple geometries in the same table, you can choose which one will go into the `geometry` key with the usual `?select` query parameter.
|
||||||
- #1082, Add security definitions to the OpenAPI output - @laurenceisla
|
- #1082, Add security definitions to the OpenAPI output - @laurenceisla
|
||||||
|
- #2378, Support http OPTIONS method on RPC and root path - @steve-chavez
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
+17
-19
@@ -417,28 +417,26 @@ handleDelete identifier context@(RequestContext _ _ ApiRequest{..} _) = do
|
|||||||
|
|
||||||
handleInfo :: Monad m => Target -> RequestContext -> Handler m Wai.Response
|
handleInfo :: Monad m => Target -> RequestContext -> Handler m Wai.Response
|
||||||
handleInfo target RequestContext{..} =
|
handleInfo target RequestContext{..} =
|
||||||
case tbl of
|
case target of
|
||||||
Just table ->
|
TargetIdent identifier ->
|
||||||
return $ Wai.responseLBS HTTP.status200 [allOrigins, allowH table] mempty
|
case HM.lookup identifier (dbTables ctxDbStructure) of
|
||||||
Nothing ->
|
Just tbl -> infoResponse $ allowH tbl
|
||||||
-- TODO is this right? When no tbl is found on the schema cache we disallow OPTIONS?
|
Nothing -> throwError $ Error.ApiRequestError ApiRequestTypes.NotFound
|
||||||
throwError $ Error.ApiRequestError ApiRequestTypes.NotFound
|
TargetProc pd _
|
||||||
|
| pdVolatility pd == Volatile -> infoResponse "OPTIONS,POST"
|
||||||
|
| otherwise -> infoResponse "OPTIONS,GET,HEAD,POST"
|
||||||
|
TargetDefaultSpec _ -> infoResponse "OPTIONS,GET,HEAD"
|
||||||
where
|
where
|
||||||
tbl = case target of
|
infoResponse allowHeader = return $ Wai.responseLBS HTTP.status200 [allOrigins, (HTTP.hAllow, allowHeader)] mempty
|
||||||
TargetIdent identifier -> HM.lookup identifier (dbTables ctxDbStructure)
|
|
||||||
_ -> Nothing
|
|
||||||
allOrigins = ("Access-Control-Allow-Origin", "*")
|
allOrigins = ("Access-Control-Allow-Origin", "*")
|
||||||
allowH table =
|
allowH table =
|
||||||
( HTTP.hAllow
|
let hasPK = not . null $ tablePKCols table in
|
||||||
, BS.intercalate "," $
|
BS.intercalate "," $
|
||||||
["OPTIONS,GET,HEAD"]
|
["OPTIONS,GET,HEAD"] ++
|
||||||
++ ["POST" | tableInsertable table]
|
["POST" | tableInsertable table] ++
|
||||||
++ ["PUT" | tableInsertable table && tableUpdatable table && hasPK]
|
["PUT" | tableInsertable table && tableUpdatable table && hasPK] ++
|
||||||
++ ["PATCH" | tableUpdatable table]
|
["PATCH" | tableUpdatable table] ++
|
||||||
++ ["DELETE" | tableDeletable table]
|
["DELETE" | tableDeletable table]
|
||||||
)
|
|
||||||
hasPK =
|
|
||||||
not $ null $ maybe mempty tablePKCols tbl
|
|
||||||
|
|
||||||
handleInvoke :: InvokeMethod -> ProcDescription -> RequestContext -> DbHandler Wai.Response
|
handleInvoke :: InvokeMethod -> ProcDescription -> RequestContext -> DbHandler Wai.Response
|
||||||
handleInvoke invMethod proc context@RequestContext{..} = do
|
handleInvoke invMethod proc context@RequestContext{..} = do
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ getPathInfo AppConfig{configOpenApiMode, configDbRootSpec} path =
|
|||||||
|
|
||||||
getAction :: PathInfo -> ByteString -> Either ApiRequestError Action
|
getAction :: PathInfo -> ByteString -> Either ApiRequestError Action
|
||||||
getAction PathInfo{pathIsProc, pathIsDefSpec} method =
|
getAction PathInfo{pathIsProc, pathIsDefSpec} method =
|
||||||
if pathIsProc && method `notElem` ["HEAD", "GET", "POST"]
|
if pathIsProc && method `notElem` ["HEAD", "GET", "POST", "OPTIONS"]
|
||||||
then Left $ InvalidRpcMethod method
|
then Left $ InvalidRpcMethod method
|
||||||
else case method of
|
else case method of
|
||||||
-- The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response
|
-- The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response
|
||||||
|
|||||||
+15
-3
@@ -140,7 +140,14 @@ def dumpconfig(configpath=None, env=None, stdin=None):
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def run(configpath=None, stdin=None, env=None, port=None, host=None, no_pool_connection_available=False):
|
def run(
|
||||||
|
configpath=None,
|
||||||
|
stdin=None,
|
||||||
|
env=None,
|
||||||
|
port=None,
|
||||||
|
host=None,
|
||||||
|
no_pool_connection_available=False,
|
||||||
|
):
|
||||||
"Run PostgREST and yield an endpoint that is ready for connections."
|
"Run PostgREST and yield an endpoint that is ready for connections."
|
||||||
env = env or {}
|
env = env or {}
|
||||||
env["PGRST_DB_POOL"] = "1"
|
env["PGRST_DB_POOL"] = "1"
|
||||||
@@ -244,6 +251,7 @@ def sleep_pool_connection(url, seconds):
|
|||||||
except requests.exceptions.ReadTimeout:
|
except requests.exceptions.ReadTimeout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def authheader(token):
|
def authheader(token):
|
||||||
"Bearer token HTTP authorization header."
|
"Bearer token HTTP authorization header."
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
@@ -982,9 +990,13 @@ def test_no_pool_connection_required_on_options(defaultenv):
|
|||||||
response = postgrest.session.options("/projects")
|
response = postgrest.session.options("/projects")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# OPTIONS on RPC is not implemented yet, still it shouldn't require opening a connection
|
# OPTIONS on RPC shouldn't require opening a connection
|
||||||
response = postgrest.session.options("/rpc/hello")
|
response = postgrest.session.options("/rpc/hello")
|
||||||
assert response.status_code == 405
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# OPTIONS on root shouldn't require opening a connection
|
||||||
|
response = postgrest.session.options("/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_no_pool_connection_required_on_bad_jwt_claim(defaultenv):
|
def test_no_pool_connection_required_on_bad_jwt_claim(defaultenv):
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ spec actualPgVersion = describe "Allow header" $ do
|
|||||||
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 "fails with 404 for an unknown table" $
|
||||||
|
request methodOptions "/unknown" [] "" `shouldRespondWith` 404
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion100) $
|
when (actualPgVersion >= pgVersion100) $
|
||||||
context "a partitioned table" $ do
|
context "a partitioned table" $ do
|
||||||
it "includes read/write methods for writeable partitioned tables" $ do
|
it "includes read/write methods for writeable partitioned tables" $ do
|
||||||
@@ -85,3 +88,29 @@ spec actualPgVersion = describe "Allow header" $ do
|
|||||||
liftIO $
|
liftIO $
|
||||||
simpleHeaders r `shouldSatisfy`
|
simpleHeaders r `shouldSatisfy`
|
||||||
matchHeader "Allow" "OPTIONS,GET,HEAD,DELETE"
|
matchHeader "Allow" "OPTIONS,GET,HEAD,DELETE"
|
||||||
|
|
||||||
|
context "a function" $ do
|
||||||
|
it "includes the POST method for a volatile function" $ do
|
||||||
|
r <- request methodOptions "/rpc/reset_items_tables" [] ""
|
||||||
|
liftIO $
|
||||||
|
simpleHeaders r `shouldSatisfy`
|
||||||
|
matchHeader "Allow" "OPTIONS,POST"
|
||||||
|
|
||||||
|
it "includes the GET/HEAD/POST method for a stable function" $ do
|
||||||
|
r <- request methodOptions "/rpc/getallusers" [] ""
|
||||||
|
liftIO $
|
||||||
|
simpleHeaders r `shouldSatisfy`
|
||||||
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST"
|
||||||
|
|
||||||
|
it "includes the GET/HEAD/POST method for a immutable function" $ do
|
||||||
|
r <- request methodOptions "/rpc/jwt_test" [] ""
|
||||||
|
liftIO $
|
||||||
|
simpleHeaders r `shouldSatisfy`
|
||||||
|
matchHeader "Allow" "OPTIONS,GET,HEAD,POST"
|
||||||
|
|
||||||
|
context "root endpoint" $ do
|
||||||
|
it "includes the GET/HEAD method " $ do
|
||||||
|
r <- request methodOptions "/" [] ""
|
||||||
|
liftIO $
|
||||||
|
simpleHeaders r `shouldSatisfy`
|
||||||
|
matchHeader "Allow" "OPTIONS,GET,HEAD"
|
||||||
|
|||||||
@@ -541,10 +541,6 @@ spec actualPgVersion =
|
|||||||
it "PATCH fails" $
|
it "PATCH fails" $
|
||||||
request methodPatch "/rpc/sayhello" [] ""
|
request methodPatch "/rpc/sayhello" [] ""
|
||||||
`shouldRespondWith` 405
|
`shouldRespondWith` 405
|
||||||
it "OPTIONS fails" $
|
|
||||||
-- TODO: should return info about the function
|
|
||||||
request methodOptions "/rpc/sayhello" [] ""
|
|
||||||
`shouldRespondWith` 405
|
|
||||||
|
|
||||||
it "executes the proc exactly once per request" $ do
|
it "executes the proc exactly once per request" $ do
|
||||||
-- callcounter is persistent even with rollback, because it uses a sequence
|
-- callcounter is persistent even with rollback, because it uses a sequence
|
||||||
|
|||||||
Reference in New Issue
Block a user