1
1
{-# LANGUAGE DeriveAnyClass #-}
2
2
{-# LANGUAGE DeriveGeneric #-}
3
3
{-# LANGUAGE DuplicateRecordFields #-}
4
-
4
+ {-# OPTIONS_GHC -Wno-unused-do-bind #-}
5
5
module PostgREST.MediaType
6
6
( MediaType (.. )
7
7
, MTVndPlanOption (.. )
@@ -11,17 +11,16 @@ module PostgREST.MediaType
11
11
, decodeMediaType
12
12
) where
13
13
14
- import qualified Data.Aeson as JSON
15
- import qualified Data.ByteString as BS
14
+ import qualified Data.Aeson as JSON
15
+ import qualified Data.ByteString as BS
16
+ import qualified Data.Text as T
17
+ import qualified Text.ParserCombinators.Parsec as P
16
18
19
+ import Data.Map (fromList , (!?) )
20
+ import Data.Text.Encoding (decodeLatin1 )
17
21
import Network.HTTP.Types.Header (Header , hContentType )
18
22
19
- import Data.Map (fromList , (!?) )
20
- import qualified Data.Text as T (break , drop , dropWhile ,
21
- dropWhileEnd , null , splitOn ,
22
- toLower )
23
- import Data.Text.Encoding (decodeLatin1 )
24
- import Protolude
23
+ import Protolude
25
24
26
25
-- | Enumeration of currently supported media types
27
26
data MediaType
@@ -104,6 +103,9 @@ toMimePlanFormat PlanText = "text"
104
103
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
105
104
-- MTVndPlan MTApplicationJSON PlanText []
106
105
--
106
+ -- >>> decodeMediaType "application/vnd.pgrst.plan ; for=\"text/xml\" ; options=analyze"
107
+ -- MTVndPlan MTTextXML PlanText [PlanAnalyze]
108
+ --
107
109
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
108
110
-- MTVndPlan MTTextCSV PlanJSON []
109
111
--
@@ -150,7 +152,10 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
150
152
(" *" ," *" ,_) -> MTAny
151
153
_ -> MTOther mt'
152
154
where
153
- (mainType, subType, params') = tokenizeMediaType mt'
155
+ mediaTypeOrError = P. parse tokenizeMediaType " parsec: tokenizeMediaType failed" $ T. unpack mt'
156
+ (mainType, subType, params') = case mediaTypeOrError of
157
+ Right mt'' -> mt''
158
+ Left _ -> (" *" , " *" , [] ) -- TODO: Throw mediatype error, would need refactoring because currently Error module depend on MediaType module
154
159
params = fromList $ map (first T. toLower) params' -- normalize parameter names to lowercase, per RFC 7321
155
160
getPlan fmt = MTVndPlan mtFor fmt $
156
161
[PlanAnalyze | inOpts " analyze" ] ++
@@ -166,21 +171,39 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
166
171
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
167
172
168
173
-- | Split a Media Type string into components
169
- -- >>> tokenizeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
170
- -- ("application","vnd.pgrst.plan+json",[("for","text/csv")])
171
- -- >>> tokenizeMediaType "*/*"
172
- -- ("*","*",[])
173
- -- >>> tokenizeMediaType "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
174
- -- ("application","vnd.pgrst.plan",[("wat","application/json"),("text/csv\"","")])
175
- tokenizeMediaType :: Text -> (Text , Text , [(Text , Text )])
176
- tokenizeMediaType t = (mainType, subType, params)
177
- where
178
- (mainType, rest) = T. break (== ' /' ) t
179
- (subType, restParams) = T. break (== ' ;' ) $ T. drop 1 rest
180
- params =
181
- let rp = T. drop 1 restParams
182
- in if T. null rp then [] else map param $ T. splitOn " ;" rp -- FIXME: breaks if there's a ';' in a quoted value
183
- param p =
184
- let (k, v) = T. break (== ' =' ) p
185
- in (k, dropAround (== ' "' ) $ T. drop 1 v) -- FIXME: doesn't unescape quotes in values
186
- dropAround p = T. dropWhile p . T. dropWhileEnd p
174
+ -- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+json;for=\"text/csv\""
175
+ -- Right ("application","vnd.pgrst.plan+json",[("for","text/csv")])
176
+ --
177
+ -- >>> P.parse tokenizeMediaType "" "*/*"
178
+ -- Right ("*","*",[])
179
+ --
180
+ -- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
181
+ -- Right ("application","vnd.pgrst.plan",[("wat","application/json;text/csv")])
182
+ --
183
+ -- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal"
184
+ -- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")])
185
+
186
+ tokenizeMediaType :: P. Parser (Text , Text , [(Text , Text )])
187
+ tokenizeMediaType = do
188
+ mainType <- P. many1 (P. alphaNum <|> P. oneOf " .*" )
189
+ P. char ' /'
190
+ subType <- P. many1 (P. alphaNum <|> P. oneOf " .*+-" )
191
+ params <- P. many pSemicolonSeparatedKeyVals
192
+ P. optional $ P. try $ P. spaces *> P. char ' ;' -- ending semicolon
193
+ P. eof
194
+ return (T. pack mainType, T. pack subType, params)
195
+ where
196
+ pSemicolonSeparatedKeyVals :: P. Parser (Text , Text )
197
+ pSemicolonSeparatedKeyVals = P. try $ P. spaces *> P. char ' ;' *> P. spaces *> pKeyVal
198
+ where
199
+ pKeyVal :: P. Parser (Text , Text )
200
+ pKeyVal = do
201
+ key <- P. many1 P. alphaNum
202
+ P. spaces
203
+ P. char ' ='
204
+ P. spaces
205
+ val <- P. try pQuoted <|> P. try pUnQuoted
206
+ return (T. pack key, T. pack val)
207
+ where
208
+ pUnQuoted = P. many1 (P. alphaNum <|> P. oneOf " |" )
209
+ pQuoted = P. char ' \" ' *> P. manyTill P. anyChar (P. char ' \" ' )
0 commit comments