Merge pull request #375 from calebmer/feature/plurality-singular

Implement single route selection
This commit is contained in:
Joe Nelson
2015-11-18 14:39:33 -08:00
3 changed files with 49 additions and 22 deletions
+1
View File
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Full text operators `@>`,`<@` - @ruslantalpa
- Shaping of the response body (filter columns, embed relations) with &select parameter for POST/PATCH - @ruslantalpa
- Detect relationships between public views and private tables - @calebmer
- `Prefer: plurality=singular` for selecting single objects - @calebmer
### Removed
- API versioning feature - @calebmer
+30 -22
View File
@@ -19,7 +19,7 @@ import qualified Data.HashMap.Strict as HM
import Data.List (find, sortBy, delete, transpose)
import Data.Maybe (fromMaybe, fromJust, isJust, isNothing, mapMaybe)
import Data.Ord (comparing)
import Data.Ranged.Ranges (emptyRange)
import Data.Ranged.Ranges (emptyRange, singletonRange)
import Data.String.Conversions (cs)
import Data.Text (Text, replace, strip)
import Data.Tree
@@ -90,25 +90,30 @@ app dbStructure conf reqBody req =
if range == Just emptyRange
then return $ errResponse status416 "HTTP Range error"
else do
let q = createReadStatement selectQuery range (not $ hasPrefer "count=none") isCsv
let q = createReadStatement selectQuery (if singular then Nothing else range) singular (not $ hasPrefer "count=none") isCsv
row <- H.maybeEx q
let (tableTotal, queryTotal, _ , body) = extractQueryResult row
frm = fromMaybe 0 $ rangeOffset <$> range
to = frm+queryTotal-1
contentRange = contentRangeH frm to tableTotal
status = rangeStatus frm to tableTotal
canonical = urlEncodeVars -- should this be moved to the dbStructure (location)?
. sortBy (comparing fst)
. map (join (***) cs)
. parseSimpleQuery
$ rawQueryString req
return $ responseLBS status
[contentTypeH, contentRange,
("Content-Location",
"/" <> cs table <>
if Prelude.null canonical then "" else "?" <> cs canonical
)
] (fromMaybe "[]" body)
if singular
then return $ if queryTotal <= 0
then responseLBS status404 [] ""
else responseLBS status200 [contentTypeH] (fromMaybe "{}" body)
else do
let frm = fromMaybe 0 $ rangeOffset <$> range
to = frm+queryTotal-1
contentRange = contentRangeH frm to tableTotal
status = rangeStatus frm to tableTotal
canonical = urlEncodeVars -- should this be moved to the dbStructure (location)?
. sortBy (comparing fst)
. map (join (***) cs)
. parseSimpleQuery
$ rawQueryString req
return $ responseLBS status
[contentTypeH, contentRange,
("Content-Location",
"/" <> cs table <>
if Prelude.null canonical then "" else "?" <> cs canonical
)
] (fromMaybe "[]" body)
Right (selectQuery, Just (mutateQuery, isSingle)) ->
case verb of
"POST" -> do
@@ -186,6 +191,7 @@ app dbStructure conf reqBody req =
isCsv = contentType == csvMT
contentTypeH = (hContentType, contentType)
echoRequested = hasPrefer "return=representation"
singular = hasPrefer "plurality=singular"
request = parseRequest schema (dbRelations dbStructure) (head path) req reqBody --TODO! is head safe?
rangeStatus :: Int -> Int -> Maybe Int -> Status
@@ -415,15 +421,17 @@ parseRequest schema allRels rootTableName httpRequest reqBody =
selectQuery = requestToQuery schema <$> selectApiRequest
mutateQuery = requestToQuery schema <$> mutateApiRequest
createReadStatement :: SqlQuery -> Maybe NonnegRange -> Bool -> Bool -> B.Stmt P.Postgres
createReadStatement selectQuery range countTable asCsv =
createReadStatement :: SqlQuery -> Maybe NonnegRange -> Bool -> Bool -> Bool -> B.Stmt P.Postgres
createReadStatement selectQuery range isSingle countTable asCsv =
B.Stmt (
wrapQuery selectQuery [
if countTable then countAllF else countNoneF,
countF,
"null", -- location header can not be calucalted
if asCsv then asCsvF else asJsonF
] selectStarF range
if asCsv
then asCsvF
else if isSingle then asJsonSingleF else asJsonF
] selectStarF (if isNothing range && isSingle then Just $ singletonRange 0 else range)
) V.empty True
createWriteStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> [Text] -> Bool -> B.Stmt P.Postgres
+18
View File
@@ -214,6 +214,24 @@ spec =
get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments(content)" `shouldRespondWith`
"[{\"user_id\":2,\"task_id\":6,\"comments\":[{\"content\":\"Needs to be delivered ASAP\"}]}]"
describe "Plurality singular" $ do
it "will select an existing object" $
request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] ""
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| {"id":5} |]
, matchStatus = 200
, matchHeaders = []
}
it "will respond with 404 when not found" $
request methodGet "/items?id=eq.9999" [("Prefer","plurality=singular")] ""
`shouldRespondWith` 404
it "can shape plurality singular object routes" $
request methodGet "/projects_view?id=eq.1&select=id,name,clients(*),tasks(id,name)" [("Prefer","plurality=singular")] ""
`shouldRespondWith`
"{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}"
describe "ordering response" $ do
it "by a column asc" $