@@ -1,27 +1,15 @@
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)
2411import Data.Proxy
12+import Data.String
2513import Data.Text
2614import Data.Text.Encoding (decodeUtf8)
2715import GHC.Exts (Constraint)
@@ -30,36 +18,38 @@ import qualified Network.HTTP.Types as HTTP
3018import Prelude hiding (concat)
3119import Servant.API
3220
33−type FunctionName = [Text]
3421
35−-- | Function name builder that simply concat each part together
36−concatCase :: FunctionName -> Text
37−concatCase = concat
22+newtype FunctionName = FunctionName { unFunctionName :: [Text] }
23+ deriving (Show, Eq, Monoid)
3824
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 "_"
25+makePrisms ''FunctionName
4326
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
27+newtype ForeignType = ForeignType { unForeignType :: Text }
28+ deriving (Show, Eq, IsString, Monoid)
5229
53−type ForeignType = Text
30+makePrisms ''ForeignType
5431
55−type Arg = (Text, ForeignType)
32+newtype PathSegment = PathSegment { unPathSegment :: Text }
33+ deriving (Show, Eq, IsString, Monoid)
34+
35+makePrisms ''PathSegment
36+
37+data Arg = Arg
38+ { _aName :: PathSegment
39+ , _aType :: ForeignType }
40+ deriving (Show, Eq)
41+
42+makeLenses ''Arg
43+
44+aPath :: Getter Arg Text
45+aPath = aName . _PathSegment
5646
5747data SegmentType
58− = Static Text
48+ = Static PathSegment
5949 -- ^ a static path segment. like "/foo"
6050 | Cap Arg
6151 -- ^ a capture. like "/:userid"
62− deriving (Eq, Show)
52+ deriving (Show, Eq)
6353
6454makePrisms ''SegmentType
6555
@@ -68,6 +58,14 @@ newtype Segment = Segment { unSegment :: SegmentType }
6858
6959makePrisms ''Segment
7060
61+isCapture :: Segment -> Bool
62+isCapture (Segment (Cap _)) = True
63+isCapture _ = False
64+
65+captureArg :: Segment -> Arg
66+captureArg (Segment (Cap s)) = s
67+captureArg _ = error "captureArg called on non capture"
68+
7169type Path = [Segment]
7270
7371data ArgType
@@ -86,10 +84,10 @@ data QueryArg = QueryArg
8684makeLenses ''QueryArg
8785
8886data HeaderArg = HeaderArg
89− { headerArg :: Arg }
87+ { _headerArg :: Arg }
9088 | ReplaceHeaderArg
91− { headerArg :: Arg
92− , headerPattern :: Text
89+ { _headerArg :: Arg
90+ , _headerPattern :: Text
9391 } deriving (Eq, Show)
9492
9593makeLenses ''HeaderArg
@@ -117,16 +115,8 @@ data Req = Req
117115
118116makeLenses ''Req
119117
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−
128118defReq :: Req
129−defReq = Req defUrl "GET" [] Nothing "" []
119+defReq = Req defUrl "GET" [] Nothing (ForeignType "") (FunctionName [])
130120
131121-- | To be used exclusively as a "negative" return type/constraint
132122-- by @'Elem`@ type family.
@@ -173,7 +163,7 @@ class HasForeignType lang a where
173163data NoTypes
174164
175165instance HasForeignType NoTypes ftype where
176− typeFor _ _ = empty
166+ typeFor _ _ = ForeignType empty
177167
178168type HasNoForeignType = HasForeignType NoTypes
179169
@@ -195,18 +185,21 @@ instance (KnownSymbol sym, HasForeignType lang ftype, HasForeign lang sublayout)
195185
196186 foreignFor lang Proxy req =
197187 foreignFor lang (Proxy :: Proxy sublayout) $
198− req & reqUrl.path <>~ [Segment (Cap arg)]
199− & reqFuncName %~ (++ ["by", str])
188+ req & reqUrl . path <>~ [Segment (Cap arg)]
189+ & reqFuncName . _FunctionName %~ (++ ["by", str])
200190 where
201− str = pack . symbolVal $ (Proxy :: Proxy sym)
202− arg = (str, typeFor lang (Proxy :: Proxy ftype))
191+ str = pack . symbolVal $ (Proxy :: Proxy sym)
192+ ftype = typeFor lang (Proxy :: Proxy ftype)
193+ arg = Arg
194+ { _aName = PathSegment str
195+ , _aType = ftype }
203196
204197instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
205198 => HasForeign lang (Verb method status list a) where
206199 type Foreign (Verb method status list a) = Req
207200
208201 foreignFor lang Proxy req =
209− req & reqFuncName %~ (methodLC :)
202+ req & reqFuncName . _FunctionName %~ (methodLC :)
210203 & reqMethod .~ method
211204 & reqReturnType .~ retType
212205 where
@@ -219,12 +212,13 @@ instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
219212 type Foreign (Header sym a :> sublayout) = Foreign sublayout
220213
221214 foreignFor lang Proxy req =
222− foreignFor lang subP $ req
223− & reqHeaders <>~ [HeaderArg arg]
215+ foreignFor lang subP $ req & reqHeaders <>~ [HeaderArg arg]
224216 where
225217 hname = pack . symbolVal $ (Proxy :: Proxy sym)
226− arg = (hname, typeFor lang (Proxy :: Proxy a))
227− subP = Proxy :: Proxy sublayout
218+ arg = Arg
219+ { _aName = PathSegment hname
220+ , _aType = typeFor lang (Proxy :: Proxy a) }
221+ subP = Proxy :: Proxy sublayout
228222
229223instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
230224 => HasForeign lang (QueryParam sym a :> sublayout) where
@@ -233,10 +227,11 @@ instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
233227 foreignFor lang Proxy req =
234228 foreignFor lang (Proxy :: Proxy sublayout) $
235229 req & reqUrl.queryStr <>~ [QueryArg arg Normal]
236−
237230 where
238231 str = pack . symbolVal $ (Proxy :: Proxy sym)
239− arg = (str, typeFor lang (Proxy :: Proxy a))
232+ arg = Arg
233+ { _aName = PathSegment str
234+ , _aType = typeFor lang (Proxy :: Proxy a) }
240235
241236instance
242237 (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout)
@@ -247,7 +242,9 @@ instance
247242 req & reqUrl.queryStr <>~ [QueryArg arg List]
248243 where
249244 str = pack . symbolVal $ (Proxy :: Proxy sym)
250− arg = (str, typeFor lang (Proxy :: Proxy [a]))
245+ arg = Arg
246+ { _aName = PathSegment str
247+ , _aType = typeFor lang (Proxy :: Proxy [a]) }
251248
252249instance
253250 (KnownSymbol sym, HasForeignType lang Bool, HasForeign lang sublayout)
@@ -259,13 +256,15 @@ instance
259256 req & reqUrl.queryStr <>~ [QueryArg arg Flag]
260257 where
261258 str = pack . symbolVal $ (Proxy :: Proxy sym)
262− arg = (str, typeFor lang (Proxy :: Proxy Bool))
259+ arg = Arg
260+ { _aName = PathSegment str
261+ , _aType = typeFor lang (Proxy :: Proxy Bool) }
263262
264263instance HasForeign lang Raw where
265264 type Foreign Raw = HTTP.Method -> Req
266265
267266 foreignFor _ Proxy req method =
268− req & reqFuncName %~ ((toLower $ decodeUtf8 method) :)
267+ req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
269268 & reqMethod .~ method
270269
271270instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout)
@@ -282,8 +281,8 @@ instance (KnownSymbol path, HasForeign lang sublayout)
282281
283282 foreignFor lang Proxy req =
284283 foreignFor lang (Proxy :: Proxy sublayout) $
285− req & reqUrl.path <>~ [Segment (Static str)]
286− & reqFuncName %~ (++ [str])
284+ req & reqUrl . path <>~ [Segment (Static (PathSegment str))]
285+ & reqFuncName . _FunctionName %~ (++ [str])
287286 where
288287 str =
289288 Data.Text.map (\c -> if c == '.' then '_' else c)