@@ -6,44 +6,30 @@ Description : PostgREST functions to translate HTTP request to a domain type cal
66{-# LANGUAGE NamedFieldPuns #-}
77module PostgREST.ApiRequest
88 ( ApiRequest (.. )
9- , InvokeMethod (.. )
10- , Mutation (.. )
11- , MediaType (.. )
12- , Action (.. )
13- , DbAction (.. )
14- , Payload (.. )
159 , userApiRequest
1610 , userPreferences
1711 ) where
1812
19- import qualified Data.Aeson as JSON
20- import qualified Data.Aeson.Key as K
21- import qualified Data.Aeson.KeyMap as KM
22- import qualified Data.ByteString.Char8 as BS
23- import qualified Data.ByteString.Lazy as LBS
24- import qualified Data.CaseInsensitive as CI
25- import qualified Data.Csv as CSV
26- import qualified Data.HashMap.Strict as HM
27- import qualified Data.List.NonEmpty as NonEmptyList
28- import qualified Data.Map.Strict as M
29- import qualified Data.Set as S
30- import qualified Data.Text.Encoding as T
31- import qualified Data.Vector as V
13+ import qualified Data.CaseInsensitive as CI
14+ import qualified Data.HashMap.Strict as HM
15+ import qualified Data.List.NonEmpty as NonEmptyList
16+ import qualified Data.Set as S
17+ import qualified Data.Text.Encoding as T
3218
33- import Data.Either.Combinators (mapBoth )
34-
35- import Control.Arrow ((***) )
36- import Data.Aeson.Types (emptyArray , emptyObject )
3719import Data.List (lookup )
3820import Data.Ranged.Ranges (emptyRange , rangeIntersection ,
3921 rangeIsEmpty )
4022import Network.HTTP.Types.Header (RequestHeaders , hCookie )
41- import Network.HTTP.Types.URI (parseSimpleQuery )
4223import Network.Wai (Request (.. ))
4324import Network.Wai.Parse (parseHttpAccept )
4425import Web.Cookie (parseCookies )
4526
27+ import PostgREST.ApiRequest.Payload (getPayload )
4628import PostgREST.ApiRequest.QueryParams (QueryParams (.. ))
29+ import PostgREST.ApiRequest.Types (Action (.. ), DbAction (.. ),
30+ InvokeMethod (.. ),
31+ Mutation (.. ), Payload (.. ),
32+ RequestBody , Resource (.. ))
4733import PostgREST.Config (AppConfig (.. ),
4834 OpenAPIMode (.. ))
4935import PostgREST.Config.Database (TimezoneNames )
@@ -64,44 +50,6 @@ import qualified PostgREST.MediaType as MediaType
6450
6551import Protolude
6652
67-
68- type RequestBody = LBS. ByteString
69-
70- data Payload
71- = ProcessedJSON -- ^ Cached attributes of a JSON payload
72- { payRaw :: LBS. ByteString
73- -- ^ This is the raw ByteString that comes from the request body. We
74- -- cache this instead of an Aeson Value because it was detected that for
75- -- large payloads the encoding had high memory usage, see
76- -- https://github.com/PostgREST/postgrest/pull/1005 for more details
77- , payKeys :: S. Set Text
78- -- ^ Keys of the object or if it's an array these keys are guaranteed to
79- -- be the same across all its objects
80- }
81- | ProcessedUrlEncoded { payArray :: [(Text , Text )], payKeys :: S. Set Text }
82- | RawJSON { payRaw :: LBS. ByteString }
83- | RawPay { payRaw :: LBS. ByteString }
84-
85- data InvokeMethod = Inv | InvRead Bool deriving Eq
86- data Mutation = MutationCreate | MutationDelete | MutationSingleUpsert | MutationUpdate deriving Eq
87-
88- data Resource
89- = ResourceRelation Text
90- | ResourceRoutine Text
91- | ResourceSchema
92-
93- data DbAction
94- = ActRelationRead { dbActQi :: QualifiedIdentifier , actHeadersOnly :: Bool }
95- | ActRelationMut { dbActQi :: QualifiedIdentifier , actMutation :: Mutation }
96- | ActRoutine { dbActQi :: QualifiedIdentifier , actInvMethod :: InvokeMethod }
97- | ActSchemaRead Schema Bool
98-
99- data Action
100- = ActDb DbAction
101- | ActRelationInfo QualifiedIdentifier
102- | ActRoutineInfo QualifiedIdentifier InvokeMethod
103- | ActSchemaInfo
104-
10553{-|
10654 Describes what the user wants to do. This data type is a
10755 translation of the raw elements of an HTTP request into domain
@@ -240,103 +188,3 @@ getRanges method QueryParams{qsRanges} hdrs
240188 -- The only emptyRange allowed is the limit zero range
241189 isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange)
242190 topLevelRange = fromMaybe allRange $ HM. lookup " limit" ranges -- if no limit is specified, get all the request rows
243-
244- getPayload :: RequestBody -> MediaType -> QueryParams. QueryParams -> Action -> Either ApiRequestError (Maybe Payload , S. Set FieldName )
245- getPayload reqBody contentMediaType QueryParams {qsColumns} action = do
246- checkedPayload <- if shouldParsePayload then payload else Right Nothing
247- let cols = case (checkedPayload, columns) of
248- (Just ProcessedJSON {payKeys}, _) -> payKeys
249- (Just ProcessedUrlEncoded {payKeys}, _) -> payKeys
250- (Just RawJSON {}, Just cls) -> cls
251- _ -> S. empty
252- return (checkedPayload, cols)
253- where
254- payload :: Either ApiRequestError (Maybe Payload )
255- payload = mapBoth InvalidBody Just $ case (contentMediaType, isProc) of
256- (MTApplicationJSON , _) ->
257- if isJust columns
258- then Right $ RawJSON reqBody
259- else note " All object keys must match" . payloadAttributes reqBody
260- =<< if LBS. null reqBody && isProc
261- then Right emptyObject
262- else first BS. pack $
263- -- Drop parsing error message in favor of generic one (https://github.com/PostgREST/postgrest/issues/2344)
264- maybe (Left " Empty or invalid json" ) Right $ JSON. decode reqBody
265- (MTTextCSV , _) -> do
266- json <- csvToJson <$> first BS. pack (CSV. decodeByName reqBody)
267- note " All lines must have same number of fields" $ payloadAttributes (JSON. encode json) json
268- (MTUrlEncoded , True ) ->
269- Right $ ProcessedUrlEncoded params (S. fromList $ fst <$> params)
270- (MTUrlEncoded , False ) ->
271- let paramsMap = HM. fromList $ (identity *** JSON. String ) <$> params in
272- Right $ ProcessedJSON (JSON. encode paramsMap) $ S. fromList (HM. keys paramsMap)
273- (MTTextPlain , True ) -> Right $ RawPay reqBody
274- (MTTextXML , True ) -> Right $ RawPay reqBody
275- (MTOctetStream , True ) -> Right $ RawPay reqBody
276- (ct, _) -> Left $ " Content-Type not acceptable: " <> MediaType. toMime ct
277-
278- shouldParsePayload = case action of
279- ActDb (ActRelationMut _ MutationDelete ) -> False
280- ActDb (ActRelationMut _ _) -> True
281- ActDb (ActRoutine _ Inv ) -> True
282- _ -> False
283-
284- columns = case action of
285- ActDb (ActRelationMut _ MutationCreate ) -> qsColumns
286- ActDb (ActRelationMut _ MutationUpdate ) -> qsColumns
287- ActDb (ActRoutine _ Inv ) -> qsColumns
288- _ -> Nothing
289-
290- isProc = case action of
291- ActDb (ActRoutine _ _) -> True
292- _ -> False
293- params = (T. decodeUtf8 *** T. decodeUtf8) <$> parseSimpleQuery (LBS. toStrict reqBody)
294-
295- type CsvData = V. Vector (M. Map Text LBS. ByteString )
296-
297- {-|
298- Converts CSV like
299- a,b
300- 1,hi
301- 2,bye
302-
303- into a JSON array like
304- [ {"a": "1", "b": "hi"}, {"a": 2, "b": "bye"} ]
305-
306- The reason for its odd signature is so that it can compose
307- directly with CSV.decodeByName
308- -}
309- csvToJson :: (CSV. Header , CsvData ) -> JSON. Value
310- csvToJson (_, vals) =
311- JSON. Array $ V. map rowToJsonObj vals
312- where
313- rowToJsonObj = JSON. Object . KM. fromMapText .
314- M. map (\ str ->
315- if str == " NULL"
316- then JSON. Null
317- else JSON. String . T. decodeUtf8 $ LBS. toStrict str
318- )
319-
320- payloadAttributes :: RequestBody -> JSON. Value -> Maybe Payload
321- payloadAttributes raw json =
322- -- Test that Array contains only Objects having the same keys
323- case json of
324- JSON. Array arr ->
325- case arr V. !? 0 of
326- Just (JSON. Object o) ->
327- let canonicalKeys = S. fromList $ K. toText <$> KM. keys o
328- areKeysUniform = all (\ case
329- JSON. Object x -> S. fromList (K. toText <$> KM. keys x) == canonicalKeys
330- _ -> False ) arr in
331- if areKeysUniform
332- then Just $ ProcessedJSON raw canonicalKeys
333- else Nothing
334- Just _ -> Nothing
335- Nothing -> Just emptyPJArray
336-
337- JSON. Object o -> Just $ ProcessedJSON raw (S. fromList $ K. toText <$> KM. keys o)
338-
339- -- truncate everything else to an empty array.
340- _ -> Just emptyPJArray
341- where
342- emptyPJArray = ProcessedJSON (JSON. encode emptyArray) S. empty
0 commit comments