@@ -87,8 +87,14 @@ module Servant.Utils.Links (
88 -- * Adding custom types
88 -- * Adding custom types
89 , HasLink(..)
89 , HasLink(..)
93+ , LinkArrayElementStyle (..)
94import Data.List
100import Data.List
@@ -118,21 +124,24 @@ import Servant.API.Experimental.Auth ( AuthProtect )
118-- The only way of constructing a 'Link' is using 'safeLink', which means any
124-- The only way of constructing a 'Link' is using 'safeLink', which means any
119-- 'Link' is guaranteed to be part of the mentioned API.
125-- 'Link' is guaranteed to be part of the mentioned API.
120data Link = Link
126data Link = Link
121− { _segments :: [String] -- ^ Segments of "foo/bar" would be ["foo", "bar"]
127+ { _segments :: [String] -- ^ Segments of "foo/bar" would be ["foo", "bar"]
122− , _queryParams :: [Param Query]
128+ , _queryParams :: [Param]
123 } deriving Show
129 } deriving Show
131+linkSegments :: Link -> [String]
132+linkSegments = _segments
134+linkQueryParams :: Link -> [Param]
135+linkQueryParams = _queryParams
125instance ToHttpApiData Link where
137instance ToHttpApiData Link where
126 toHeader = TE.encodeUtf8 . toUrlPiece
138 toHeader = TE.encodeUtf8 . toUrlPiece
127 toUrlPiece l =
139 toUrlPiece l =
128 let uri = linkURI l
140 let uri = linkURI l
129 in Text.pack $ uriPath uri ++ uriQuery uri
141 in Text.pack $ uriPath uri ++ uriQuery uri
131−-- Phantom types for Param
134−-- | Query param
143+-- | Query parameter.
135−data Param a
144+data Param
136 = SingleParam String Text.Text
145 = SingleParam String Text.Text
137 | ArrayElemParam String Text.Text
146 | ArrayElemParam String Text.Text
138 | FlagParam String
147 | FlagParam String
@@ -141,27 +150,40 @@ data Param a
141addSegment :: String -> Link -> Link
150addSegment :: String -> Link -> Link
142addSegment seg l = l { _segments = _segments l <> [seg] }
151addSegment seg l = l { _segments = _segments l <> [seg] }
144−addQueryParam :: Param Query -> Link -> Link
153+addQueryParam :: Param -> Link -> Link
145addQueryParam qp l =
154addQueryParam qp l =
146 l { _queryParams = _queryParams l <> [qp] }
155 l { _queryParams = _queryParams l <> [qp] }
148linkURI :: Link -> URI
157linkURI :: Link -> URI
158+linkURI = linkURI' LinkArrayElementBracket
160+-- | How to encode array query elements.
161+data LinkArrayElementStyle
162+ = LinkArrayElementBracket -- ^ @foo[]=1&foo[]=2@
163+ | LinkArrayElementPlain -- ^ @foo=1&foo=2@
164+ deriving (Eq, Ord, Show, Enum, Bounded)
166+linkURI' :: LinkArrayElementStyle -> Link -> URI
149−linkURI (Link segments q_params) =
167+linkURI' addBrackets (Link segments q_params) =
150 URI mempty -- No scheme (relative)
168 URI mempty -- No scheme (relative)
151 Nothing -- Or authority (relative)
169 Nothing -- Or authority (relative)
152 (intercalate "/" segments)
170 (intercalate "/" segments)
153 (makeQueries q_params) mempty
171 (makeQueries q_params) mempty
155− makeQueries :: [Param Query] -> String
173+ makeQueries :: [Param] -> String
156 makeQueries [] = ""
174 makeQueries [] = ""
157 makeQueries xs =
175 makeQueries xs =
158 "?" <> intercalate "&" (fmap makeQuery xs)
176 "?" <> intercalate "&" (fmap makeQuery xs)
160− makeQuery :: Param Query -> String
178+ makeQuery :: Param -> String
161− makeQuery (ArrayElemParam k v) = escape k <> "[]=" <> escape (Text.unpack v)
179+ makeQuery (ArrayElemParam k v) = escape k <> style <> escape (Text.unpack v)
162 makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
180 makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
163 makeQuery (FlagParam k) = escape k
181 makeQuery (FlagParam k) = escape k
183+ style = case addBrackets of
184+ LinkArrayElementBracket -> "[]="
185+ LinkArrayElementPlain -> "="
165escape :: String -> String
187escape :: String -> String
166escape = escapeURIString isUnreserved
188escape = escapeURIString isUnreserved