Deprecate '.' for disambiguating resource embedding
'+' should be used instead.
This commit is contained in:
committed by
Steve Chávez
parent
181b608c04
commit
3c7738a8c7
@@ -26,6 +26,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- #1288, Change server-host default of 127.0.0.1 to !4
|
- #1288, Change server-host default of 127.0.0.1 to !4
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- #1288, Deprecate `.` symbol for disambiguating resource embedding(added in #918). '+' should be used instead. Though '+' is url safe, certain clients might need to encode it to '%2B'.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- #1288, Removed support for schema reloading with SIGHUP, SIGUSR1 should be used instead - @steve-chavez
|
- #1288, Removed support for schema reloading with SIGHUP, SIGUSR1 should be used instead - @steve-chavez
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import qualified Data.Text as T
|
|||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import Network.HTTP.Base (urlEncodeVars)
|
import Network.HTTP.Base (urlEncodeVars)
|
||||||
import Network.HTTP.Types.Header (hAuthorization, hCookie)
|
import Network.HTTP.Types.Header (hAuthorization, hCookie)
|
||||||
import Network.HTTP.Types.URI (parseSimpleQuery)
|
import Network.HTTP.Types.URI (parseSimpleQuery, parseQueryReplacePlus)
|
||||||
import Network.Wai (Request (..))
|
import Network.Wai (Request (..))
|
||||||
import Network.Wai.Parse (parseHttpAccept)
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
import PostgREST.RangeQuery (NonnegRange, rangeRequested, restrictRange, rangeGeq, allRange, rangeLimit, rangeOffset)
|
import PostgREST.RangeQuery (NonnegRange, rangeRequested, restrictRange, rangeGeq, allRange, rangeLimit, rangeOffset)
|
||||||
@@ -127,14 +127,15 @@ userApiRequest schema req reqBody
|
|||||||
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
||||||
, iCanonicalQS = toS $ urlEncodeVars
|
, iCanonicalQS = toS $ urlEncodeVars
|
||||||
. L.sortBy (comparing fst)
|
. L.sortBy (comparing fst)
|
||||||
. map (join (***) toS)
|
. map (join (***) toS . second (fromMaybe BS.empty))
|
||||||
. parseSimpleQuery
|
$ queryStringWPlus
|
||||||
$ rawQueryString req
|
|
||||||
, iJWT = tokenStr
|
, iJWT = tokenStr
|
||||||
, iHeaders = [ (toS $ CI.foldedCase k, toS v) | (k,v) <- hdrs, k /= hAuthorization, k /= hCookie]
|
, iHeaders = [ (toS $ CI.foldedCase k, toS v) | (k,v) <- hdrs, k /= hAuthorization, k /= hCookie]
|
||||||
, iCookies = maybe [] parseCookiesText $ lookupHeader "Cookie"
|
, iCookies = maybe [] parseCookiesText $ lookupHeader "Cookie"
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
|
-- queryString with '+' not converted to ' '
|
||||||
|
queryStringWPlus = parseQueryReplacePlus False $ rawQueryString req
|
||||||
-- rpcQParams = Rpc query params e.g. /rpc/name?param1=val1, similar to filter but with no operator(eq, lt..)
|
-- rpcQParams = Rpc query params e.g. /rpc/name?param1=val1, similar to filter but with no operator(eq, lt..)
|
||||||
(filters, rpcQParams) =
|
(filters, rpcQParams) =
|
||||||
case action of
|
case action of
|
||||||
@@ -201,7 +202,7 @@ userApiRequest schema req reqBody
|
|||||||
path = pathInfo req
|
path = pathInfo req
|
||||||
method = requestMethod req
|
method = requestMethod req
|
||||||
hdrs = requestHeaders req
|
hdrs = requestHeaders req
|
||||||
qParams = [(toS k, v)|(k,v) <- queryString req]
|
qParams = [(toS k, v)|(k,v) <- queryStringWPlus]
|
||||||
lookupHeader = flip lookup hdrs
|
lookupHeader = flip lookup hdrs
|
||||||
hasPrefer :: Text -> Bool
|
hasPrefer :: Text -> Bool
|
||||||
hasPrefer val = any (\(h,v) -> h == "Prefer" && val `elem` split v) hdrs
|
hasPrefer val = any (\(h,v) -> h == "Prefer" && val `elem` split v) hdrs
|
||||||
|
|||||||
@@ -124,7 +124,10 @@ pRelationSelect :: Parser SelectItem
|
|||||||
pRelationSelect = lexeme $ try ( do
|
pRelationSelect = lexeme $ try ( do
|
||||||
alias <- optionMaybe ( try(pFieldName <* aliasSeparator) )
|
alias <- optionMaybe ( try(pFieldName <* aliasSeparator) )
|
||||||
fld <- pField
|
fld <- pField
|
||||||
relationDetail <- optionMaybe ( try( char '.' *> pFieldName ) )
|
relationDetail <- optionMaybe (
|
||||||
|
try ( char '+' *> pFieldName ) <|>
|
||||||
|
try ( char '.' *> pFieldName ) -- TODO deprecated, remove in next major version
|
||||||
|
)
|
||||||
|
|
||||||
return (fld, Nothing, alias, relationDetail)
|
return (fld, Nothing, alias, relationDetail)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ extra-deps:
|
|||||||
- text-builder-0.5.1.1
|
- text-builder-0.5.1.1
|
||||||
- jose-0.7.0.0
|
- jose-0.7.0.0
|
||||||
- postgresql-libpq-0.9.4.1
|
- postgresql-libpq-0.9.4.1
|
||||||
|
- http-types-0.12.3
|
||||||
|
- wai-middleware-static-0.8.2
|
||||||
ghc-options:
|
ghc-options:
|
||||||
postgrest: -O2 -Werror -Wall -fwarn-identities -fno-warn-redundant-constraints
|
postgrest: -O2 -Werror -Wall -fwarn-identities -fno-warn-redundant-constraints
|
||||||
nix:
|
nix:
|
||||||
packages: [postgresql, zlib]
|
packages: [postgresql, zlib]
|
||||||
|
# only added because of hjsonschema conflict with http-types
|
||||||
|
# once hjsonschema upper bounding on http-types is solved it can be removed
|
||||||
|
allow-newer: true
|
||||||
|
|||||||
@@ -443,12 +443,12 @@ spec = do
|
|||||||
|
|
||||||
describe "path fixed" $ do
|
describe "path fixed" $ do
|
||||||
it "works when requesting children 2 levels" $
|
it "works when requesting children 2 levels" $
|
||||||
get "/clients?id=eq.1&select=id,projects:projects.client_id(id,tasks(id))" `shouldRespondWith`
|
get "/clients?id=eq.1&select=id,projects:projects%2Bclient_id(id,tasks(id))" `shouldRespondWith`
|
||||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "works with parent relation" $
|
it "works with parent relation" $
|
||||||
get "/message?select=id,body,sender:person.sender(name),recipient:person.recipient(name)&id=lt.4" `shouldRespondWith`
|
get "/message?select=id,body,sender:person%2Bsender(name),recipient:person%2Brecipient(name)&id=lt.4" `shouldRespondWith`
|
||||||
[json|
|
[json|
|
||||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John"},"recipient":{"name":"Jane"}},
|
[{"id":1,"body":"Hello Jane","sender":{"name":"John"},"recipient":{"name":"Jane"}},
|
||||||
{"id":2,"body":"Hi John","sender":{"name":"Jane"},"recipient":{"name":"John"}},
|
{"id":2,"body":"Hi John","sender":{"name":"Jane"},"recipient":{"name":"John"}},
|
||||||
@@ -456,7 +456,7 @@ spec = do
|
|||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "works with a parent view relation" $
|
it "works with a parent view relation" $
|
||||||
get "/message?select=id,body,sender:person_detail.sender(name,sent),recipient:person_detail.recipient(name,received)&id=lt.4" `shouldRespondWith`
|
get "/message?select=id,body,sender:person_detail%2Bsender(name,sent),recipient:person_detail%2Brecipient(name,received)&id=lt.4" `shouldRespondWith`
|
||||||
[json|
|
[json|
|
||||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}},
|
[{"id":1,"body":"Hello Jane","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}},
|
||||||
{"id":2,"body":"Hi John","sender":{"name":"Jane","sent":1},"recipient":{"name":"John","received":1}},
|
{"id":2,"body":"Hi John","sender":{"name":"Jane","sent":1},"recipient":{"name":"John","received":1}},
|
||||||
@@ -464,10 +464,19 @@ spec = do
|
|||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "works with many<->many relation" $
|
it "works with many<->many relation" $
|
||||||
get "/tasks?select=id,users:users.users_tasks(id)" `shouldRespondWith`
|
get "/tasks?select=id,users:users%2Busers_tasks(id)" `shouldRespondWith`
|
||||||
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
describe "old dot '.' symbol, deprecated" $
|
||||||
|
it "still works" $ do
|
||||||
|
get "/clients?id=eq.1&select=id,projects:projects.client_id(id,tasks(id))" `shouldRespondWith`
|
||||||
|
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/tasks?select=id,users:users.users_tasks(id)" `shouldRespondWith`
|
||||||
|
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
describe "aliased embeds" $ do
|
describe "aliased embeds" $ do
|
||||||
it "works with child relation" $
|
it "works with child relation" $
|
||||||
get "/space?select=id,zones:zone(id,name),stores:zone(id,name)&zones.zone_type_id=eq.2&stores.zone_type_id=eq.3" `shouldRespondWith`
|
get "/space?select=id,zones:zone(id,name),stores:zone(id,name)&zones.zone_type_id=eq.2&stores.zone_type_id=eq.3" `shouldRespondWith`
|
||||||
@@ -536,7 +545,7 @@ spec = do
|
|||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds childs recursively" $
|
it "embeds childs recursively" $
|
||||||
get "/family_tree?id=eq.1&select=id,name, childs:family_tree.parent(id,name,childs:family_tree.parent(id,name))" `shouldRespondWith`
|
get "/family_tree?id=eq.1&select=id,name, childs:family_tree%2Bparent(id,name,childs:family_tree%2Bparent(id,name))" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": "1", "name": "Parental Unit", "childs": [
|
"id": "1", "name": "Parental Unit", "childs": [
|
||||||
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||||
@@ -545,7 +554,7 @@ spec = do
|
|||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds parent and then embeds childs" $
|
it "embeds parent and then embeds childs" $
|
||||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree.parent(id,name))" `shouldRespondWith`
|
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree%2Bparent(id,name))" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": "2", "name": "Kid One", "parent": {
|
"id": "2", "name": "Kid One", "parent": {
|
||||||
"id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
"id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
||||||
@@ -568,7 +577,7 @@ spec = do
|
|||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embeds childs" $ do
|
it "embeds childs" $ do
|
||||||
get "/organizations?select=id,name,refereeds:organizations.referee(id,name)&id=eq.1" `shouldRespondWith`
|
get "/organizations?select=id,name,refereeds:organizations%2Breferee(id,name)&id=eq.1" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": 1, "name": "Referee Org",
|
"id": 1, "name": "Referee Org",
|
||||||
"refereeds": [
|
"refereeds": [
|
||||||
@@ -582,7 +591,7 @@ spec = do
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
get "/organizations?select=id,name,auditees:organizations.auditor(id,name)&id=eq.2" `shouldRespondWith`
|
get "/organizations?select=id,name,auditees:organizations%2Bauditor(id,name)&id=eq.2" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"id": 2, "name": "Auditor Org",
|
"id": 2, "name": "Auditor Org",
|
||||||
"auditees": [
|
"auditees": [
|
||||||
@@ -616,7 +625,7 @@ spec = do
|
|||||||
"manager":{"name":"Referee Manager"}}}
|
"manager":{"name":"Referee Manager"}}}
|
||||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
get "/organizations?select=name,manager(name),auditees:organizations.auditor(name,manager(name),refereeds:organizations.referee(name,manager(name)))&id=eq.2" `shouldRespondWith`
|
get "/organizations?select=name,manager(name),auditees:organizations%2Bauditor(name,manager(name),refereeds:organizations%2Breferee(name,manager(name)))&id=eq.2" `shouldRespondWith`
|
||||||
[json|[{
|
[json|[{
|
||||||
"name":"Auditor Org",
|
"name":"Auditor Org",
|
||||||
"manager":{"name":"Auditor Manager"},
|
"manager":{"name":"Auditor Manager"},
|
||||||
|
|||||||
Reference in New Issue
Block a user