feat: support OPTIONS on RPC and root path

This commit is contained in:
steve-chavez
2022-07-17 18:40:14 -05:00
committed by Steve Chavez
parent 71a5748718
commit 2eb7c803e3
6 changed files with 63 additions and 27 deletions
+1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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):
+29
View File
@@ -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"
-4
View File
@@ -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