@@ -1,27 +1,18 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
1+{-# LANGUAGE CPP #-}
62#if !MIN_VERSION_base(4,8,0)
7−{-# LANGUAGE NullaryTypeClasses #-}
3+{-# LANGUAGE NullaryTypeClasses #-}
84#endif
9−{-# LANGUAGE MultiParamTypeClasses #-}
10−{-# LANGUAGE ScopedTypeVariables #-}
11−{-# LANGUAGE TemplateHaskell #-}
12−{-# LANGUAGE TypeFamilies #-}
13−{-# LANGUAGE TypeOperators #-}
14−{-# LANGUAGE UndecidableInstances #-}
15−{-# LANGUAGE OverloadedStrings #-}
16−{-# LANGUAGE PolyKinds #-}
175
186-- | Generalizes all the data needed to make code generation work with
197-- arbitrary programming languages.
208module Servant.Foreign.Internal where
219
22−import Control.Lens (makeLenses, makePrisms, (%~), (&), (.~), (<>~))
23−import qualified Data.Char as C
10+import Control.Lens hiding (cons, List)
11+#if !MIN_VERSION_base(4,8,0)
12+import Data.Monoid
13+#endif
2414import Data.Proxy
15+import Data.String
2516import Data.Text
2617import Data.Text.Encoding (decodeUtf8)
2718import GHC.Exts (Constraint)
@@ -30,36 +21,38 @@ import qualified Network.HTTP.Types as HTTP
3021import Prelude hiding (concat)
3122import Servant.API
3223
33−type FunctionName = [Text]
3424
35−-- | Function name builder that simply concat each part together
36−concatCase :: FunctionName -> Text
37−concatCase = concat
25+newtype FunctionName = FunctionName { unFunctionName :: [Text] }
26+ deriving (Show, Eq, Monoid)
27+
28+makePrisms ''FunctionName
29+
30+newtype ForeignType = ForeignType { unForeignType :: Text }
31+ deriving (Show, Eq, IsString, Monoid)
3832
39−-- | Function name builder using the snake_case convention.
40−-- each part is separated by a single underscore character.
41−snakeCase :: FunctionName -> Text
42−snakeCase = intercalate "_"
33+makePrisms ''ForeignType
4334
44−-- | Function name builder using the CamelCase convention.
45−-- each part begins with an upper case character.
46−camelCase :: FunctionName -> Text
47−camelCase = camelCase' . Prelude.map (replace "-" "")
48− where camelCase' [] = ""
49− camelCase' (p:ps) = concat $ p : Prelude.map capitalize ps
50− capitalize "" = ""
51− capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name
35+newtype PathSegment = PathSegment { unPathSegment :: Text }
36+ deriving (Show, Eq, IsString, Monoid)
5237
53−type ForeignType = Text
38+makePrisms ''PathSegment
5439
55−type Arg = (Text, ForeignType)
40+data Arg = Arg
41+ { _aName :: PathSegment
42+ , _aType :: ForeignType }
43+ deriving (Show, Eq)
44+
45+makeLenses ''Arg
46+
47+aPath :: Getter Arg Text
48+aPath = aName . _PathSegment
5649
5750data SegmentType
58− = Static Text
51+ = Static PathSegment
5952 -- ^ a static path segment. like "/foo"
6053 | Cap Arg
6154 -- ^ a capture. like "/:userid"
62− deriving (Eq, Show)
55+ deriving (Show, Eq)
6356
6457makePrisms ''SegmentType
6558
@@ -68,6 +61,14 @@ newtype Segment = Segment { unSegment :: SegmentType }
6861
6962makePrisms ''Segment
7063
64+isCapture :: Segment -> Bool
65+isCapture (Segment (Cap _)) = True
66+isCapture _ = False
67+
68+captureArg :: Segment -> Arg
69+captureArg (Segment (Cap s)) = s
70+captureArg _ = error "captureArg called on non capture"
71+
7172type Path = [Segment]
7273
7374data ArgType
@@ -86,10 +87,10 @@ data QueryArg = QueryArg
8687makeLenses ''QueryArg
8788
8889data HeaderArg = HeaderArg
89− { headerArg :: Arg }
90+ { _headerArg :: Arg }
9091 | ReplaceHeaderArg
91− { headerArg :: Arg
92− , headerPattern :: Text
92+ { _headerArg :: Arg
93+ , _headerPattern :: Text
9394 } deriving (Eq, Show)
9495
9596makeLenses ''HeaderArg
@@ -117,16 +118,8 @@ data Req = Req
117118
118119makeLenses ''Req
119120
120−isCapture :: Segment -> Bool
121−isCapture (Segment (Cap _)) = True
122−isCapture _ = False
123−
124−captureArg :: Segment -> Arg
125−captureArg (Segment (Cap s)) = s
126−captureArg _ = error "captureArg called on non capture"
127−
128121defReq :: Req
129−defReq = Req defUrl "GET" [] Nothing "" []
122+defReq = Req defUrl "GET" [] Nothing (ForeignType "") (FunctionName [])
130123
131124-- | To be used exclusively as a "negative" return type/constraint
132125-- by @'Elem`@ type family.
@@ -173,7 +166,7 @@ class HasForeignType lang a where
173166data NoTypes
174167
175168instance HasForeignType NoTypes ftype where
176− typeFor _ _ = empty
169+ typeFor _ _ = ForeignType empty
177170
178171type HasNoForeignType = HasForeignType NoTypes
179172
@@ -195,18 +188,21 @@ instance (KnownSymbol sym, HasForeignType lang ftype, HasForeign lang sublayout)
195188
196189 foreignFor lang Proxy req =
197190 foreignFor lang (Proxy :: Proxy sublayout) $
198− req & reqUrl.path <>~ [Segment (Cap arg)]
199− & reqFuncName %~ (++ ["by", str])
191+ req & reqUrl . path <>~ [Segment (Cap arg)]
192+ & reqFuncName . _FunctionName %~ (++ ["by", str])
200193 where
201− str = pack . symbolVal $ (Proxy :: Proxy sym)
202− arg = (str, typeFor lang (Proxy :: Proxy ftype))
194+ str = pack . symbolVal $ (Proxy :: Proxy sym)
195+ ftype = typeFor lang (Proxy :: Proxy ftype)
196+ arg = Arg
197+ { _aName = PathSegment str
198+ , _aType = ftype }
203199
204200instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
205201 => HasForeign lang (Verb method status list a) where
206202 type Foreign (Verb method status list a) = Req
207203
208204 foreignFor lang Proxy req =
209− req & reqFuncName %~ (methodLC :)
205+ req & reqFuncName . _FunctionName %~ (methodLC :)
210206 & reqMethod .~ method
211207 & reqReturnType .~ retType
212208 where
@@ -219,12 +215,13 @@ instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
219215 type Foreign (Header sym a :> sublayout) = Foreign sublayout
220216
221217 foreignFor lang Proxy req =
222− foreignFor lang subP $ req
223− & reqHeaders <>~ [HeaderArg arg]
218+ foreignFor lang subP $ req & reqHeaders <>~ [HeaderArg arg]
224219 where
225220 hname = pack . symbolVal $ (Proxy :: Proxy sym)
226− arg = (hname, typeFor lang (Proxy :: Proxy a))
227− subP = Proxy :: Proxy sublayout
221+ arg = Arg
222+ { _aName = PathSegment hname
223+ , _aType = typeFor lang (Proxy :: Proxy a) }
224+ subP = Proxy :: Proxy sublayout
228225
229226instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
230227 => HasForeign lang (QueryParam sym a :> sublayout) where
@@ -233,10 +230,11 @@ instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
233230 foreignFor lang Proxy req =
234231 foreignFor lang (Proxy :: Proxy sublayout) $
235232 req & reqUrl.queryStr <>~ [QueryArg arg Normal]
236−
237233 where
238234 str = pack . symbolVal $ (Proxy :: Proxy sym)
239− arg = (str, typeFor lang (Proxy :: Proxy a))
235+ arg = Arg
236+ { _aName = PathSegment str
237+ , _aType = typeFor lang (Proxy :: Proxy a) }
240238
241239instance
242240 (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout)
@@ -247,7 +245,9 @@ instance
247245 req & reqUrl.queryStr <>~ [QueryArg arg List]
248246 where
249247 str = pack . symbolVal $ (Proxy :: Proxy sym)
250− arg = (str, typeFor lang (Proxy :: Proxy [a]))
248+ arg = Arg
249+ { _aName = PathSegment str
250+ , _aType = typeFor lang (Proxy :: Proxy [a]) }
251251
252252instance
253253 (KnownSymbol sym, HasForeignType lang Bool, HasForeign lang sublayout)
@@ -259,13 +259,15 @@ instance
259259 req & reqUrl.queryStr <>~ [QueryArg arg Flag]
260260 where
261261 str = pack . symbolVal $ (Proxy :: Proxy sym)
262− arg = (str, typeFor lang (Proxy :: Proxy Bool))
262+ arg = Arg
263+ { _aName = PathSegment str
264+ , _aType = typeFor lang (Proxy :: Proxy Bool) }
263265
264266instance HasForeign lang Raw where
265267 type Foreign Raw = HTTP.Method -> Req
266268
267269 foreignFor _ Proxy req method =
268− req & reqFuncName %~ ((toLower $ decodeUtf8 method) :)
270+ req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
269271 & reqMethod .~ method
270272
271273instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout)
@@ -282,8 +284,8 @@ instance (KnownSymbol path, HasForeign lang sublayout)
282284
283285 foreignFor lang Proxy req =
284286 foreignFor lang (Proxy :: Proxy sublayout) $
285− req & reqUrl.path <>~ [Segment (Static str)]
286− & reqFuncName %~ (++ [str])
287+ req & reqUrl . path <>~ [Segment (Static (PathSegment str))]
288+ & reqFuncName . _FunctionName %~ (++ [str])
287289 where
288290 str =
289291 Data.Text.map (\c -> if c == '.' then '_' else c)