git.iko.soy/typeable/servant
on master help sign in
[servant-foreign] Parameterise Req with a foreign type
commit207f05e759468595a33f716d527af3dd4af09ebf authorSteve Purcell <steve@sanityinc.com> dateSun 13 Mar 2016, 18:35 (11 years ago) parentbfe812f
4 files changed+75−50
servant-foreign/servant-foreign.cabal+2 servant-foreign/src/Servant/Foreign/Internal.hs+62−40 servant-foreign/test/Servant/ForeignSpec.hs+2−1 servant-js/src/Servant/JS/Internal.hs+9−9
servant-foreign/servant-foreign.cabal+2
@@ -46,6 +46,7 @@ library
4646 , GeneralizedNewtypeDeriving
4747 , MultiParamTypeClasses
4848 , ScopedTypeVariables
49+ , StandaloneDeriving
4950 , TemplateHaskell
5051 , TypeFamilies
5152 , TypeOperators
@@ -64,6 +65,7 @@ test-suite spec
6465 build-depends: base
6566 , hspec >= 2.1.8
6667 , servant-foreign
68+ , text >= 1.2 && < 1.3
6769 default-language: Haskell2010
6870 default-extensions: ConstraintKinds
6971 , DataKinds
servant-foreign/src/Servant/Foreign/Internal.hs+62−40
@@ -27,8 +27,12 @@ newtype FunctionName = FunctionName { unFunctionName :: [Text] }
2727
2828makePrisms ''FunctionName
2929
30−newtype ForeignType = ForeignType { unForeignType :: Text }
31− deriving (Show, Eq, IsString, Monoid)
30+newtype ForeignType f = ForeignType { unForeignType :: f }
31+
32+deriving instance Show f => Show (ForeignType f)
33+deriving instance Eq f => Eq (ForeignType f)
34+deriving instance IsString f => IsString (ForeignType f)
35+deriving instance Monoid f => Monoid (ForeignType f)
3236
3337makePrisms ''ForeignType
3438
@@ -37,39 +41,45 @@ newtype PathSegment = PathSegment { unPathSegment :: Text }
3741
3842makePrisms ''PathSegment
3943
40−data Arg = Arg
44+data Arg f = Arg
4145 { _argName :: PathSegment
42− , _argType :: ForeignType }
43− deriving (Show, Eq)
46+ , _argType :: ForeignType f }
47+
48+deriving instance Eq f => Eq (Arg f)
49+deriving instance Show f => Show (Arg f)
4450
4551makeLenses ''Arg
4652
47−argPath :: Getter Arg Text
53+argPath :: Getter (Arg f) Text
4854argPath = argName . _PathSegment
4955
50−data SegmentType
56+data SegmentType f
5157 = Static PathSegment
5258 -- ^ a static path segment. like "/foo"
53− | Cap Arg
59+ | Cap (Arg f)
5460 -- ^ a capture. like "/:userid"
55− deriving (Show, Eq)
61+
62+deriving instance Eq f => Eq (SegmentType f)
63+deriving instance Show f => Show (SegmentType f)
5664
5765makePrisms ''SegmentType
5866
59−newtype Segment = Segment { unSegment :: SegmentType }
60− deriving (Eq, Show)
67+newtype Segment f = Segment { unSegment :: SegmentType f }
68+
69+deriving instance Eq f => Eq (Segment f)
70+deriving instance Show f => Show (Segment f)
6171
6272makePrisms ''Segment
6373
64−isCapture :: Segment -> Bool
74+isCapture :: Segment f -> Bool
6575isCapture (Segment (Cap _)) = True
6676isCapture _ = False
6777
68−captureArg :: Segment -> Arg
78+captureArg :: Segment f -> Arg f
6979captureArg (Segment (Cap s)) = s
7080captureArg _ = error "captureArg called on non capture"
7181
72−type Path = [Segment]
82+type Path f = [Segment f]
7383
7484data ArgType
7585 = Normal
@@ -79,46 +89,58 @@ data ArgType
7989
8090makePrisms ''ArgType
8191
82−data QueryArg = QueryArg
83− { _queryArgName :: Arg
92+data QueryArg f = QueryArg
93+ { _queryArgName :: Arg f
8494 , _queryArgType :: ArgType
85− } deriving (Eq, Show)
95+ }
96+
97+deriving instance Eq f => Eq (QueryArg f)
98+deriving instance Show f => Show (QueryArg f)
8699
87100makeLenses ''QueryArg
88101
89−data HeaderArg = HeaderArg
90− { _headerArg :: Arg }
102+data HeaderArg f = HeaderArg
103+ { _headerArg :: Arg f }
91104 | ReplaceHeaderArg
92− { _headerArg :: Arg
105+ { _headerArg :: Arg f
93106 , _headerPattern :: Text
94− } deriving (Eq, Show)
107+ }
108+
109+deriving instance Eq f => Eq (HeaderArg f)
110+deriving instance Show f => Show (HeaderArg f)
95111
96112makeLenses ''HeaderArg
97113
98114makePrisms ''HeaderArg
99115
100−data Url = Url
101− { _path :: Path
102− , _queryStr :: [QueryArg]
103− } deriving (Eq, Show)
116+data Url f = Url
117+ { _path :: Path f
118+ , _queryStr :: [QueryArg f]
119+ }
104120
105−defUrl :: Url
121+deriving instance Eq f => Eq (Url f)
122+deriving instance Show f => Show (Url f)
123+
124+defUrl :: Url f
106125defUrl = Url [] []
107126
108127makeLenses ''Url
109128
110−data Req = Req
111− { _reqUrl :: Url
129+data Req f = Req
130+ { _reqUrl :: Url f
112131 , _reqMethod :: HTTP.Method
113− , _reqHeaders :: [HeaderArg]
114− , _reqBody :: Maybe ForeignType
115− , _reqReturnType :: ForeignType
132+ , _reqHeaders :: [HeaderArg f]
133+ , _reqBody :: Maybe (ForeignType f)
134+ , _reqReturnType :: ForeignType f
116135 , _reqFuncName :: FunctionName
117− } deriving (Eq, Show)
136+ }
137+
138+deriving instance Eq f => Eq (Req f)
139+deriving instance Show f => Show (Req f)
118140
119141makeLenses ''Req
120142
121−defReq :: Req
143+defReq :: Req Text
122144defReq = Req defUrl "GET" [] Nothing (ForeignType "") (FunctionName [])
123145
124146-- | To be used exclusively as a "negative" return type/constraint
@@ -161,7 +183,7 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where
161183-- >
162184--
163185class HasForeignType lang a where
164− typeFor :: Proxy lang -> Proxy a -> ForeignType
186+ typeFor :: Proxy lang -> Proxy a -> ForeignType Text
165187
166188data NoTypes
167189
@@ -172,7 +194,7 @@ type HasNoForeignType = HasForeignType NoTypes
172194
173195class HasForeign lang (layout :: *) where
174196 type Foreign layout :: *
175− foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout
197+ foreignFor :: Proxy lang -> Proxy layout -> Req Text -> Foreign layout
176198
177199instance (HasForeign lang a, HasForeign lang b)
178200 => HasForeign lang (a :<|> b) where
@@ -199,7 +221,7 @@ instance (KnownSymbol sym, HasForeignType lang ftype, HasForeign lang sublayout)
199221
200222instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
201223 => HasForeign lang (Verb method status list a) where
202− type Foreign (Verb method status list a) = Req
224+ type Foreign (Verb method status list a) = Req Text
203225
204226 foreignFor lang Proxy req =
205227 req & reqFuncName . _FunctionName %~ (methodLC :)
@@ -264,7 +286,7 @@ instance
264286 , _argType = typeFor lang (Proxy :: Proxy Bool) }
265287
266288instance HasForeign lang Raw where
267− type Foreign Raw = HTTP.Method -> Req
289+ type Foreign Raw = HTTP.Method -> Req Text
268290
269291 foreignFor _ Proxy req method =
270292 req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
@@ -329,9 +351,9 @@ instance HasForeign lang sublayout
329351-- the data needed to generate a function for each endpoint
330352-- and hands it all back in a list.
331353class GenerateList reqs where
332− generateList :: reqs -> [Req]
354+ generateList :: reqs -> [Req Text]
333355
334−instance GenerateList Req where
356+instance GenerateList (Req Text) where
335357 generateList r = [r]
336358
337359instance (GenerateList start, GenerateList rest)
@@ -344,5 +366,5 @@ listFromAPI
344366 :: (HasForeign lang api, GenerateList (Foreign api))
345367 => Proxy lang
346368 -> Proxy api
347− -> [Req]
369+ -> [Req Text]
348370listFromAPI lang p = generateList (foreignFor lang p defReq)
servant-foreign/test/Servant/ForeignSpec.hs+2−1
@@ -6,6 +6,7 @@ module Servant.ForeignSpec where
66import Data.Monoid ((<>))
77import Data.Proxy
88import Servant.Foreign
9+import Data.Text (Text(..))
910
1011import Test.Hspec
1112
@@ -47,7 +48,7 @@ type TestApi
4748 :<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] ()
4849 :<|> "test" :> Capture "id" Int :> Delete '[JSON] ()
4950
50−testApi :: [Req]
51+testApi :: [Req Text]
5152testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy TestApi)
5253
5354listFromAPISpec :: Spec
servant-js/src/Servant/JS/Internal.hs+9−9
@@ -57,12 +57,12 @@ import qualified Data.Text as T
5757import Data.Text (Text)
5858import Servant.Foreign
5959
60−type AjaxReq = Req
60+type AjaxReq = Req Text
6161
6262-- A 'JavascriptGenerator' just takes the data found in the API type
6363-- for each endpoint and generates Javascript code in a Text. Several
6464-- generators are available in this package.
65−type JavaScriptGenerator = [Req] -> Text
65+type JavaScriptGenerator = [Req Text] -> Text
6666
6767-- | This structure is used by specific implementations to let you
6868-- customize the output
@@ -139,7 +139,7 @@ toValidFunctionName t =
139139 , Set.connectorPunctuation
140140 ]
141141
142−toJSHeader :: HeaderArg -> Text
142+toJSHeader :: HeaderArg f -> Text
143143toJSHeader (HeaderArg n)
144144 = toValidFunctionName ("header" <> n ^. argName . _PathSegment)
145145toJSHeader (ReplaceHeaderArg n p)
@@ -153,29 +153,29 @@ toJSHeader (ReplaceHeaderArg n p)
153153 pn = "{" <> n ^. argName . _PathSegment <> "}"
154154 rp = T.replace pn "" p
155155
156−jsSegments :: [Segment] -> Text
156+jsSegments :: [Segment f] -> Text
157157jsSegments [] = ""
158158jsSegments [x] = "/" <> segmentToStr x False
159159jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
160160
161−segmentToStr :: Segment -> Bool -> Text
161+segmentToStr :: Segment f -> Bool -> Text
162162segmentToStr (Segment st) notTheEnd =
163163 segmentTypeToStr st <> if notTheEnd then "" else "'"
164164
165−segmentTypeToStr :: SegmentType -> Text
165+segmentTypeToStr :: SegmentType f -> Text
166166segmentTypeToStr (Static s) = s ^. _PathSegment
167167segmentTypeToStr (Cap s) =
168168 "' + encodeURIComponent(" <> s ^. argName . _PathSegment <> ") + '"
169169
170−jsGParams :: Text -> [QueryArg] -> Text
170+jsGParams :: Text -> [QueryArg f] -> Text
171171jsGParams _ [] = ""
172172jsGParams _ [x] = paramToStr x False
173173jsGParams s (x:xs) = paramToStr x True <> s <> jsGParams s xs
174174
175−jsParams :: [QueryArg] -> Text
175+jsParams :: [QueryArg f] -> Text
176176jsParams = jsGParams "&"
177177
178−paramToStr :: QueryArg -> Bool -> Text
178+paramToStr :: QueryArg f -> Bool -> Text
179179paramToStr qarg notTheEnd =
180180 case qarg ^. queryArgType of
181181 Normal -> name