diff --git a/CHANGELOG.md b/CHANGELOG.md index a01135930..a63fc1e0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2236, Support POSIX regular expression operators for row filtering - @enote-kane - #2202, Allow returning XML from RPCs - @fjf2002 - #2269, Allow `limit=0` in the request query to return an empty array - @gautam1168, @laurenceisla - - #2268, Allow returning XML from single-column queries #2268 - @fjf2002 + - #2268, Allow returning XML from single-column queries - @fjf2002 + - #2300, RPC POST for function w/single unnamed XML param #2300 - @fjf2002 ### Fixed diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index e1a1949f2..33e3b2098 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -263,7 +263,7 @@ procsSqlQuery pgVer = [q| ) ORDER BY idx) AS args, CASE COUNT(*) - COUNT(name) -- number of unnamed arguments WHEN 0 THEN true - WHEN 1 THEN (array_agg(type))[1] IN ('bytea'::regtype, 'json'::regtype, 'jsonb'::regtype, 'text'::regtype) + WHEN 1 THEN (array_agg(type))[1] IN ('bytea'::regtype, 'json'::regtype, 'jsonb'::regtype, 'text'::regtype, 'xml'::regtype) ELSE false END AS callable FROM pg_proc, diff --git a/src/PostgREST/Error.hs b/src/PostgREST/Error.hs index 12f73b6f1..00b9c5d47 100644 --- a/src/PostgREST/Error.hs +++ b/src/PostgREST/Error.hs @@ -148,6 +148,7 @@ instance JSON.ToJSON ApiRequestError where (case (hasPreferSingleObject, isInvPost, contentType) of (True, _, _) -> " function with a single json or jsonb parameter" (_, True, CTTextPlain) -> " function with a single unnamed text parameter" + (_, True, CTTextXML) -> " function with a single unnamed xml parameter" (_, True, CTOctetStream) -> " function with a single unnamed bytea parameter" (_, True, CTApplicationJSON) -> prms <> " function or the " <> schema <> "." <> procName <>" function with a single unnamed json or jsonb parameter" _ -> prms <> " function") <> diff --git a/src/PostgREST/Request/ApiRequest.hs b/src/PostgREST/Request/ApiRequest.hs index 85422a1fd..ef82ba25e 100644 --- a/src/PostgREST/Request/ApiRequest.hs +++ b/src/PostgREST/Request/ApiRequest.hs @@ -255,6 +255,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{.. let paramsMap = M.fromList $ (T.decodeUtf8 *** JSON.String . T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) in Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (M.keys paramsMap) (CTTextPlain, True) -> Right $ RawPay reqBody + (CTTextXML, True) -> Right $ RawPay reqBody (CTOctetStream, True) -> Right $ RawPay reqBody (ct, _) -> Left $ "Content-Type not acceptable: " <> ContentType.toMime ct topLevelRange = fromMaybe allRange $ M.lookup "limit" ranges -- if no limit is specified, get all the request rows @@ -462,6 +463,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost = (CTApplicationJSON, "json") -> True (CTApplicationJSON, "jsonb") -> True (CTTextPlain, "text") -> True + (CTTextXML, "xml") -> True (CTOctetStream, "bytea") -> True _ -> False hasSingleUnnamedParam _ = False @@ -475,7 +477,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost = then length params == 1 && (firstType == Just "json" || firstType == Just "jsonb") -- If the function has no parameters, the arguments keys must be empty as well else if null params - then null argumentsKeys && not (isInvPost && contentType `elem` [CTTextPlain, CTOctetStream]) + then null argumentsKeys && not (isInvPost && contentType `elem` [CTOctetStream, CTTextPlain, CTTextXML]) -- A function has optional and required parameters. Optional parameters have a default value and -- don't require arguments for the function to be executed, required parameters must have an argument present. else case L.partition ppReq params of diff --git a/test/spec/Feature/Query/RpcSpec.hs b/test/spec/Feature/Query/RpcSpec.hs index 85bce16ae..36d0ce21f 100644 --- a/test/spec/Feature/Query/RpcSpec.hs +++ b/test/spec/Feature/Query/RpcSpec.hs @@ -1202,6 +1202,13 @@ spec actualPgVersion = `shouldRespondWith` [str|unnamed text arg|] + it "can insert xml directly" $ + request methodPost "/rpc/unnamed_xml_param" + [("Content-Type", "text/xml"), ("Accept", "text/xml")] + [str|JohnJaneRemember me|] + `shouldRespondWith` + [str|JohnJaneRemember me|] + it "can insert bytea directly" $ do let file = unsafePerformIO $ BL.readFile "test/spec/fixtures/image.png" r <- request methodPost "/rpc/unnamed_bytea_param" @@ -1240,6 +1247,21 @@ spec actualPgVersion = , matchHeaders = [ matchContentTypeJson ] } + it "will err when no function with single unnamed text parameter exists and text/xml is specified" $ + request methodPost "/rpc/unnamed_int_param" + [("Content-Type", "text/xml")] + [str|a simple text|] + `shouldRespondWith` + [json|{ + "hint": "If a new function was created in the database with this name and parameters, try reloading the schema cache.", + "message": "Could not find the test.unnamed_int_param function with a single unnamed xml parameter in the schema cache", + "code":"PGRST202", + "details":null + }|] + { matchStatus = 404 + , matchHeaders = [ matchContentTypeJson ] + } + it "will err when no function with single unnamed bytea parameter exists and application/octet-stream is specified" $ let file = unsafePerformIO $ BL.readFile "test/spec/fixtures/image.png" in request methodPost "/rpc/unnamed_int_param" @@ -1329,3 +1351,18 @@ spec actualPgVersion = { matchStatus = 300 , matchHeaders = [matchContentTypeJson] } + + it "should fail on /rpc/unnamed_xml_param when posting invalid xml" $ do + request methodPost "/rpc/unnamed_xml_param" + [("Content-Type", "text/xml"), ("Accept", "text/xml")] + [str|<|] + `shouldRespondWith` + [json| { + "hint":null, + "message":"invalid XML content", + "code":"2200N", + "details":"line 1: StartTag: invalid element name\n<\n ^" + }|] + { matchStatus = 400 + , matchHeaders = [matchContentTypeJson] + } diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index dcc334e8b..f616cc034 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2312,6 +2312,10 @@ create or replace function test.unnamed_text_param(text) returns text as $$ select $1; $$ language sql; +create or replace function test.unnamed_xml_param(pg_catalog.xml) returns pg_catalog.xml as $$ + select $1; +$$ language sql; + create or replace function test.unnamed_bytea_param(bytea) returns bytea as $$ select $1::bytea; $$ language sql;