git.iko.soy/typeable/servant
on master help sign in
Extract common code-generation stuff to servant-foreign
ce3e4f7Denis Redozubov
servant-foreign/CHANGELOG.mdnew file+4
@@ -0,0 +1,4 @@
1+0.1
2+-----
3+
4+* Extract javascript-oblivious types and helpers to *servant-foreign*
servant-foreign/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2014, Zalora South East Asia Pte Ltd
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Zalora South East Asia Pte Ltd nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-foreign/README.mdnew file+5
@@ -0,0 +1,5 @@
1+# servant-foreign
2+
3+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
4+
5+Types and helpers for generating clients for *servant* servers in arbitrary programming languages.
servant-foreign/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
servant-foreign/servant-foreign.cabalnew file+29
@@ -0,0 +1,29 @@
1+name: servant-foreign
2+version: 0.1.0.0
3+synopsis: Abstract all necessary info for client code-generation in arbitrary programming language.
4+description: Consolidates such projects as servant-js and lackey.
5+license: BSD3
6+license-file: LICENSE
7+author: Denis Redozubov
8+maintainer: denis.redozubov@gmail.com
9+copyright: 2015 Denis Redozubov, Alp Mestanogullari
10+category: Web
11+build-type: Simple
12+cabal-version: >=1.10
13+extra-source-files:
14+ CHANGELOG.md
15+ README.md
16+source-repository head
17+ type: git
18+ location: http://github.com/haskell-servant/servant.git
19+
20+library
21+ exposed-modules: Servant.Foreign
22+ build-depends: base >=4.8 && <5
23+ , charset
24+ , lens >= 4
25+ , servant >= 0.5
26+ , text
27+ hs-source-dirs: src
28+ default-language: Haskell2010
29+ ghc-options: -Wall
servant-foreign/src/Servant/Foreign.hsnew file+409
@@ -0,0 +1,409 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE TypeOperators #-}
10+{-# LANGUAGE UndecidableInstances #-}
11+
12+-- | Generalizes all the data needed to make code generation work with
13+-- arbitrary programming languages.
14+module Servant.Foreign
15+ ( HasForeign(..)
16+ , Segment(..)
17+ , SegmentType(..)
18+ , MatrixArg
19+ , FunctionName
20+ , QueryArg(..)
21+ , HeaderArg(..)
22+ , ArgType(..)
23+ , Req
24+ , CommonGeneratorOptions(..)
25+ , defCommonGeneratorOptions
26+ , toValidFunctionName
27+ , captureArg
28+ , defReq
29+ , concatCase
30+ , snakeCase
31+ , camelCase
32+ -- lenses
33+ , argType
34+ , argName
35+ , isCapture
36+ , funcName
37+ , path
38+ , reqUrl
39+ , reqBody
40+ , reqHeaders
41+ , reqMethod
42+ , segment
43+ , queryStr
44+ -- re-exports
45+ , module Servant.API
46+ ) where
47+
48+#if !MIN_VERSION_base(4,8,0)
49+import Control.Applicative
50+#endif
51+import Control.Lens hiding (List)
52+import Data.Char (toLower, toUpper)
53+import qualified Data.CharSet as Set
54+import qualified Data.CharSet.Unicode.Category as Set
55+import Data.List
56+import Data.Monoid
57+import Data.Proxy
58+import qualified Data.Text as T
59+import GHC.Exts (Constraint)
60+import GHC.TypeLits
61+import Servant.API
62+
63+-- | This structure is used by specific implementations to let you
64+-- customize the output
65+data CommonGeneratorOptions = CommonGeneratorOptions
66+ {
67+ functionNameBuilder :: FunctionName -> String -- ^ function generating function names
68+ , requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
69+ , successCallback :: String -- ^ name of the callback parameter when the request was successful
70+ , errorCallback :: String -- ^ name of the callback parameter when the request reported an error
71+ , moduleName :: String -- ^ namespace on which we define the foreign function (empty mean local var)
72+ , urlPrefix :: String -- ^ a prefix we should add to the Url in the codegen
73+ }
74+
75+-- | Default options.
76+--
77+-- @
78+-- > defCommonGeneratorOptions = CommonGeneratorOptions
79+-- > { functionNameBuilder = camelCase
80+-- > , requestBody = "body"
81+-- > , successCallback = "onSuccess"
82+-- > , errorCallback = "onError"
83+-- > , moduleName = ""
84+-- > , urlPrefix = ""
85+-- > }
86+-- @
87+defCommonGeneratorOptions :: CommonGeneratorOptions
88+defCommonGeneratorOptions = CommonGeneratorOptions
89+ {
90+ functionNameBuilder = camelCase
91+ , requestBody = "body"
92+ , successCallback = "onSuccess"
93+ , errorCallback = "onError"
94+ , moduleName = ""
95+ , urlPrefix = ""
96+ }
97+
98+-- | Function name builder that simply concat each part together
99+concatCase :: FunctionName -> String
100+concatCase = concat
101+
102+-- | Function name builder using the snake_case convention.
103+-- each part is separated by a single underscore character.
104+snakeCase :: FunctionName -> String
105+snakeCase = intercalate "_"
106+
107+-- | Function name builder using the CamelCase convention.
108+-- each part begins with an upper case character.
109+camelCase :: FunctionName -> String
110+camelCase [] = ""
111+camelCase (p:ps) = concat $ p : camelCase' ps
112+ where camelCase' [] = []
113+ camelCase' (r:rs) = capitalize r : camelCase' rs
114+ capitalize [] = []
115+ capitalize (x:xs) = toUpper x : xs
116+
117+type Arg = String
118+
119+data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
120+ deriving (Eq, Show)
121+
122+data SegmentType = Static String -- ^ a static path segment. like "/foo"
123+ | Cap Arg -- ^ a capture. like "/:userid"
124+ deriving (Eq, Show)
125+
126+type Path = [Segment]
127+
128+data ArgType =
129+ Normal
130+ | Flag
131+ | List
132+ deriving (Eq, Show)
133+
134+data QueryArg = QueryArg
135+ { _argName :: Arg
136+ , _argType :: ArgType
137+ } deriving (Eq, Show)
138+
139+data HeaderArg = HeaderArg
140+ { headerArgName :: String
141+ }
142+ | ReplaceHeaderArg
143+ { headerArgName :: String
144+ , headerPattern :: String
145+ } deriving (Eq)
146+
147+
148+-- |
149+instance Show HeaderArg where
150+ show (HeaderArg n) = toValidFunctionName ("header" <> n)
151+ show (ReplaceHeaderArg n p)
152+ | pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
153+ | pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
154+ | pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
155+ <> "\""
156+ | otherwise = p
157+ where
158+ pv = toValidFunctionName ("header" <> n)
159+ pn = "{" <> n <> "}"
160+ rp = replace pn "" p
161+ -- Use replace method from Data.Text
162+ replace old new = T.unpack .
163+ T.replace (T.pack old) (T.pack new) .
164+ T.pack
165+
166+-- | Attempts to reduce the function name provided to that allowed by @'Foreign'@.
167+--
168+-- Here we are making an assumption that js identifiers are common enough.
169+-- https://mathiasbynens.be/notes/javascript-identifiers
170+-- Couldn't work out how to handle zero-width characters.
171+-- TODO: compare it with other generated languages(such as ruby via lackey)
172+-- and generalize.
173+--
174+-- @TODO: specify better default function name, or throw error?
175+toValidFunctionName :: String -> String
176+toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
177+ where
178+ setFirstChar c = if firstChar c
179+ then c
180+ else '_'
181+ firstChar c = prefixOK c || any (Set.member c) firstLetterOK
182+ remainder c = prefixOK c || any (Set.member c) remainderOK
183+ -- Valid prefixes
184+ prefixOK c = c `elem` ['$','_']
185+ -- Unicode character sets
186+ firstLetterOK = [ Set.lowercaseLetter
187+ , Set.uppercaseLetter
188+ , Set.titlecaseLetter
189+ , Set.modifierLetter
190+ , Set.otherLetter
191+ , Set.letterNumber ]
192+ remainderOK = firstLetterOK <> [ Set.nonSpacingMark
193+ , Set.spacingCombiningMark
194+ , Set.decimalNumber
195+ , Set.connectorPunctuation ]
196+toValidFunctionName [] = "_"
197+
198+type MatrixArg = QueryArg
199+
200+data Url = Url
201+ { _path :: Path
202+ , _queryStr :: [QueryArg]
203+ } deriving (Eq, Show)
204+
205+defUrl :: Url
206+defUrl = Url [] []
207+
208+type FunctionName = [String]
209+type Method = String
210+
211+data Req = Req
212+ { _reqUrl :: Url
213+ , _reqMethod :: Method
214+ , _reqHeaders :: [HeaderArg]
215+ , _reqBody :: Bool
216+ , _funcName :: FunctionName
217+ } deriving (Eq, Show)
218+
219+makeLenses ''QueryArg
220+makeLenses ''Segment
221+makeLenses ''Url
222+makeLenses ''Req
223+
224+isCapture :: Segment -> Bool
225+isCapture (Segment (Cap _) _) = True
226+isCapture _ = False
227+
228+captureArg :: Segment -> Arg
229+captureArg (Segment (Cap s) _) = s
230+captureArg _ = error "captureArg called on non capture"
231+
232+defReq :: Req
233+defReq = Req defUrl "GET" [] False []
234+
235+type family Elem (a :: *) (ls::[*]) :: Constraint where
236+ Elem a '[] = 'False ~ 'True
237+ Elem a (a ': list) = ()
238+ Elem a (b ': list) = Elem a list
239+
240+class HasForeign (layout :: *) where
241+ type Foreign layout :: *
242+ foreignFor :: Proxy layout -> Req -> Foreign layout
243+
244+instance (HasForeign a, HasForeign b)
245+ => HasForeign (a :<|> b) where
246+ type Foreign (a :<|> b) = Foreign a :<|> Foreign b
247+
248+ foreignFor Proxy req =
249+ foreignFor (Proxy :: Proxy a) req
250+ :<|> foreignFor (Proxy :: Proxy b) req
251+
252+instance (KnownSymbol sym, HasForeign sublayout)
253+ => HasForeign (Capture sym a :> sublayout) where
254+ type Foreign (Capture sym a :> sublayout) = Foreign sublayout
255+
256+ foreignFor Proxy req =
257+ foreignFor (Proxy :: Proxy sublayout) $
258+ req & reqUrl.path <>~ [Segment (Cap str) []]
259+ & funcName %~ (++ ["by", str])
260+
261+ where str = symbolVal (Proxy :: Proxy sym)
262+
263+instance Elem JSON list => HasForeign (Delete list a) where
264+ type Foreign (Delete list a) = Req
265+
266+ foreignFor Proxy req =
267+ req & funcName %~ ("delete" :)
268+ & reqMethod .~ "DELETE"
269+
270+instance Elem JSON list => HasForeign (Get list a) where
271+ type Foreign (Get list a) = Req
272+
273+ foreignFor Proxy req =
274+ req & funcName %~ ("get" :)
275+ & reqMethod .~ "GET"
276+
277+instance (KnownSymbol sym, HasForeign sublayout)
278+ => HasForeign (Header sym a :> sublayout) where
279+ type Foreign (Header sym a :> sublayout) = Foreign sublayout
280+
281+ foreignFor Proxy req =
282+ foreignFor subP (req & reqHeaders <>~ [HeaderArg hname])
283+
284+ where hname = symbolVal (Proxy :: Proxy sym)
285+ subP = Proxy :: Proxy sublayout
286+
287+instance Elem JSON list => HasForeign (Post list a) where
288+ type Foreign (Post list a) = Req
289+
290+ foreignFor Proxy req =
291+ req & funcName %~ ("post" :)
292+ & reqMethod .~ "POST"
293+
294+instance Elem JSON list => HasForeign (Put list a) where
295+ type Foreign (Put list a) = Req
296+
297+ foreignFor Proxy req =
298+ req & funcName %~ ("put" :)
299+ & reqMethod .~ "PUT"
300+
301+instance (KnownSymbol sym, HasForeign sublayout)
302+ => HasForeign (QueryParam sym a :> sublayout) where
303+ type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
304+
305+ foreignFor Proxy req =
306+ foreignFor (Proxy :: Proxy sublayout) $
307+ req & reqUrl.queryStr <>~ [QueryArg str Normal]
308+
309+ where str = symbolVal (Proxy :: Proxy sym)
310+
311+instance (KnownSymbol sym, HasForeign sublayout)
312+ => HasForeign (QueryParams sym a :> sublayout) where
313+ type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
314+
315+ foreignFor Proxy req =
316+ foreignFor (Proxy :: Proxy sublayout) $
317+ req & reqUrl.queryStr <>~ [QueryArg str List]
318+
319+ where str = symbolVal (Proxy :: Proxy sym)
320+
321+instance (KnownSymbol sym, HasForeign sublayout)
322+ => HasForeign (QueryFlag sym :> sublayout) where
323+ type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
324+
325+ foreignFor Proxy req =
326+ foreignFor (Proxy :: Proxy sublayout) $
327+ req & reqUrl.queryStr <>~ [QueryArg str Flag]
328+
329+ where str = symbolVal (Proxy :: Proxy sym)
330+
331+instance (KnownSymbol sym, HasForeign sublayout)
332+ => HasForeign (MatrixParam sym a :> sublayout) where
333+ type Foreign (MatrixParam sym a :> sublayout) = Foreign sublayout
334+
335+ foreignFor Proxy req =
336+ foreignFor (Proxy :: Proxy sublayout) $
337+ req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
338+
339+ where str = symbolVal (Proxy :: Proxy sym)
340+ strArg = str ++ "Value"
341+
342+instance (KnownSymbol sym, HasForeign sublayout)
343+ => HasForeign (MatrixParams sym a :> sublayout) where
344+ type Foreign (MatrixParams sym a :> sublayout) = Foreign sublayout
345+
346+ foreignFor Proxy req =
347+ foreignFor (Proxy :: Proxy sublayout) $
348+ req & reqUrl.path._last.matrix <>~ [QueryArg str List]
349+
350+ where str = symbolVal (Proxy :: Proxy sym)
351+
352+instance (KnownSymbol sym, HasForeign sublayout)
353+ => HasForeign (MatrixFlag sym :> sublayout) where
354+ type Foreign (MatrixFlag sym :> sublayout) = Foreign sublayout
355+
356+ foreignFor Proxy req =
357+ foreignFor (Proxy :: Proxy sublayout) $
358+ req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
359+
360+ where str = symbolVal (Proxy :: Proxy sym)
361+
362+instance HasForeign Raw where
363+ type Foreign Raw = Method -> Req
364+
365+ foreignFor Proxy req method =
366+ req & funcName %~ ((toLower <$> method) :)
367+ & reqMethod .~ method
368+
369+instance (Elem JSON list, HasForeign sublayout) => HasForeign (ReqBody list a :> sublayout) where
370+ type Foreign (ReqBody list a :> sublayout) = Foreign sublayout
371+
372+ foreignFor Proxy req =
373+ foreignFor (Proxy :: Proxy sublayout) $
374+ req & reqBody .~ True
375+
376+instance (KnownSymbol path, HasForeign sublayout)
377+ => HasForeign (path :> sublayout) where
378+ type Foreign (path :> sublayout) = Foreign sublayout
379+
380+ foreignFor Proxy req =
381+ foreignFor (Proxy :: Proxy sublayout) $
382+ req & reqUrl.path <>~ [Segment (Static str) []]
383+ & funcName %~ (++ [str])
384+
385+ where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
386+
387+instance HasForeign sublayout => HasForeign (RemoteHost :> sublayout) where
388+ type Foreign (RemoteHost :> sublayout) = Foreign sublayout
389+
390+ foreignFor Proxy req =
391+ foreignFor (Proxy :: Proxy sublayout) req
392+
393+instance HasForeign sublayout => HasForeign (IsSecure :> sublayout) where
394+ type Foreign (IsSecure :> sublayout) = Foreign sublayout
395+
396+ foreignFor Proxy req =
397+ foreignFor (Proxy :: Proxy sublayout) req
398+
399+instance HasForeign sublayout => HasForeign (Vault :> sublayout) where
400+ type Foreign (Vault :> sublayout) = Foreign sublayout
401+
402+ foreignFor Proxy req =
403+ foreignFor (Proxy :: Proxy sublayout) req
404+
405+instance HasForeign sublayout => HasForeign (HttpVersion :> sublayout) where
406+ type Foreign (HttpVersion :> sublayout) = Foreign sublayout
407+
408+ foreignFor Proxy req =
409+ foreignFor (Proxy :: Proxy sublayout) req
servant-js/servant-js.cabal+21−23
@@ -37,14 +37,12 @@ library
37 exposed-modules: Servant.JS
37 exposed-modules: Servant.JS
38 Servant.JS.Angular
38 Servant.JS.Angular
39 Servant.JS.Axios
39 Servant.JS.Axios
40+ Servant.JS.Internal
40 Servant.JS.JQuery
41 Servant.JS.JQuery
41 Servant.JS.Vanilla
42 Servant.JS.Vanilla
42− Servant.JS.Internal
43 build-depends: base >=4.5 && <5
43 build-depends: base >=4.5 && <5
44− , charset
45 , lens >= 4
44 , lens >= 4
46− , servant == 0.5.*
45+ , servant-foreign
47− , text
48 hs-source-dirs: src
46 hs-source-dirs: src
49 default-language: Haskell2010
47 default-language: Haskell2010
50 ghc-options: -Wall
48 ghc-options: -Wall
@@ -59,16 +57,16 @@ executable counter
59 else
57 else
60 buildable: False
58 buildable: False
61
59
62− build-depends:
60+ build-depends: aeson
63− aeson
64− , base
61+ , base
65− , filepath
62+ , filepath
66− , servant == 0.5.*
63+ , lens >= 4
64+ , servant
67− , servant-server == 0.5.*
65+ , servant-server
68− , servant-js == 0.5.*
66+ , servant-js
69− , stm
67+ , stm
70− , transformers
68+ , transformers
71− , warp
69+ , warp
72 default-language: Haskell2010
70 default-language: Haskell2010
73
71
74test-suite spec
72test-suite spec
@@ -76,12 +74,12 @@ test-suite spec
76 hs-source-dirs: test
74 hs-source-dirs: test
77 ghc-options: -Wall
75 ghc-options: -Wall
78 main-is: Spec.hs
76 main-is: Spec.hs
79− build-depends:
77+ build-depends: base == 4.*
80− base == 4.*
81− , lens
82− , servant-js
83− , servant
84− , hspec >= 2.1.8
78+ , hspec >= 2.1.8
85− , hspec-expectations
79+ , hspec-expectations
86− , language-ecmascript >= 0.16
80+ , language-ecmascript >= 0.16
81+ , lens
82+ , lens >= 4
83+ , servant
84+ , servant-js
87− default-language: Haskell2010
85+ default-language: Haskell2010
servant-js/src/Servant/JS.hs+7−9
@@ -76,7 +76,7 @@ module Servant.JS
76 , JavaScriptGenerator
76 , JavaScriptGenerator
77
77
78 , -- * Options common to all generators
78 , -- * Options common to all generators
79− CommonGeneratorOptions(..)
79+ CommonGeneratorOptions
80 , defCommonGeneratorOptions
80 , defCommonGeneratorOptions
81
81
82 , -- * Function renamers
82 , -- * Function renamers
@@ -109,13 +109,10 @@ module Servant.JS
109 , -- * Misc.
109 , -- * Misc.
110 listFromAPI
110 listFromAPI
111 , javascript
111 , javascript
112− , HasJS(..)
113 , GenerateList(..)
112 , GenerateList(..)
114− , AjaxReq
115 ) where
113 ) where
116
114
117import Data.Proxy
115import Data.Proxy
118−import Servant.API
119import Servant.JS.Angular
116import Servant.JS.Angular
120import Servant.JS.Axios
117import Servant.JS.Axios
121import Servant.JS.Internal
118import Servant.JS.Internal
@@ -125,13 +122,13 @@ import Servant.JS.Vanilla
125-- | Generate the data necessary to generate javascript code
122-- | Generate the data necessary to generate javascript code
126-- for all the endpoints of an API, as ':<|>'-separated values
123-- for all the endpoints of an API, as ':<|>'-separated values
127-- of type 'AjaxReq'.
124-- of type 'AjaxReq'.
128−javascript :: HasJS layout => Proxy layout -> JS layout
125+javascript :: HasForeign layout => Proxy layout -> Foreign layout
129−javascript p = javascriptFor p defReq
126+javascript p = foreignFor p defReq
130
127
131-- | Directly generate all the javascript functions for your API
128-- | Directly generate all the javascript functions for your API
132-- from a 'Proxy' for your API type. You can then write it to
129-- from a 'Proxy' for your API type. You can then write it to
133-- a file or integrate it in a page, for example.
130-- a file or integrate it in a page, for example.
134−jsForAPI :: (HasJS api, GenerateList (JS api))
131+jsForAPI :: (HasForeign api, GenerateList (Foreign api))
135 => Proxy api -- ^ proxy for your API type
132 => Proxy api -- ^ proxy for your API type
136 -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
133 -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
137 -> String -- ^ a string that you can embed in your pages or write to a file
134 -> String -- ^ a string that you can embed in your pages or write to a file
@@ -140,7 +137,7 @@ jsForAPI p gen = gen (listFromAPI p)
140-- | Directly generate all the javascript functions for your API
137-- | Directly generate all the javascript functions for your API
141-- from a 'Proxy' for your API type using the given generator
138-- from a 'Proxy' for your API type using the given generator
142-- and write the resulting code to a file at the given path.
139-- and write the resulting code to a file at the given path.
143−writeJSForAPI :: (HasJS api, GenerateList (JS api))
140+writeJSForAPI :: (HasForeign api, GenerateList (Foreign api))
144 => Proxy api -- ^ proxy for your API type
141 => Proxy api -- ^ proxy for your API type
145 -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
142 -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
146 -> FilePath -- ^ path to the file you want to write the resulting javascript code into
143 -> FilePath -- ^ path to the file you want to write the resulting javascript code into
@@ -161,5 +158,6 @@ instance (GenerateList start, GenerateList rest) => GenerateList (start :<|> res
161
158
162-- | Generate the necessary data for JS codegen as a list, each 'AjaxReq'
159-- | Generate the necessary data for JS codegen as a list, each 'AjaxReq'
163-- describing one endpoint from your API type.
160-- describing one endpoint from your API type.
164−listFromAPI :: (HasJS api, GenerateList (JS api)) => Proxy api -> [AjaxReq]
161+listFromAPI :: (HasForeign api, GenerateList (Foreign api)) => Proxy api -> [AjaxReq]
165listFromAPI p = generateList (javascript p)
162listFromAPI p = generateList (javascript p)
163+
servant-js/src/Servant/JS/Angular.hs+1
@@ -3,6 +3,7 @@ module Servant.JS.Angular where
3import Control.Lens
3import Control.Lens
4import Data.List
4import Data.List
5import Data.Monoid
5import Data.Monoid
6+import Servant.Foreign
6import Servant.JS.Internal
7import Servant.JS.Internal
7
8
8-- | Options specific to the angular code generator
9-- | Options specific to the angular code generator
servant-js/src/Servant/JS/Axios.hs+1
@@ -4,6 +4,7 @@ import Control.Lens
4import Data.Char (toLower)
4import Data.Char (toLower)
5import Data.List
5import Data.List
6import Data.Monoid
6import Data.Monoid
7+import Servant.Foreign
7import Servant.JS.Internal
8import Servant.JS.Internal
8
9
9-- | Axios 'configuration' type
10-- | Axios 'configuration' type
servant-js/src/Servant/JS/Internal.hs+35−380
@@ -1,206 +1,43 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE ScopedTypeVariables #-}
7−{-# LANGUAGE TemplateHaskell #-}
8−{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE TypeOperators #-}
10−{-# LANGUAGE UndecidableInstances #-}
11−module Servant.JS.Internal where
1+module Servant.JS.Internal
2+ ( JavaScriptGenerator
3+ , AjaxReq
4+ , jsSegments
5+ , segmentToStr
6+ , segmentTypeToStr
7+ , jsParams
8+ , jsGParams
9+ , jsMParams
10+ , paramToStr
11+ -- re-exports
12+ , (:<|>)(..)
13+ , (:>)
14+ , defReq
15+ , defCommonGeneratorOptions
16+ , reqHeaders
17+ , CommonGeneratorOptions(..)
18+ , HasForeign(..)
19+ , HeaderArg(..)
20+ , concatCase
21+ , snakeCase
22+ , camelCase
23+ , ReqBody
24+ , JSON
25+ , FormUrlEncoded
26+ , Post
27+ , Get
28+ , Raw
29+ , Header
30+ ) where
12
31
13−#if !MIN_VERSION_base(4,8,0)
14−import Control.Applicative
15−#endif
16import Control.Lens hiding (List)
32import Control.Lens hiding (List)
17−import Data.Char (toLower, toUpper)
33+import Servant.Foreign
18−import qualified Data.CharSet as Set
19−import qualified Data.CharSet.Unicode.Category as Set
20−import Data.List
21−import Data.Monoid
22−import Data.Proxy
23−import qualified Data.Text as T
24−import GHC.Exts (Constraint)
25−import GHC.TypeLits
26−import Servant.API
27
34
28−-- | this structure is used by JavaScriptGenerator implementations to let you
35+type AjaxReq = Req
29−-- customize the output
30−data CommonGeneratorOptions = CommonGeneratorOptions
31− {
32− functionNameBuilder :: FunctionName -> String -- ^ function generating function names
33− , requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
34− , successCallback :: String -- ^ name of the callback parameter when the request was successful
35− , errorCallback :: String -- ^ name of the callback parameter when the request reported an error
36− , moduleName :: String -- ^ namespace on which we define the js function (empty mean local var)
37− , urlPrefix :: String -- ^ a prefix we should add to the Url in the JS codegen
38− }
39−
40−-- | Default options.
41−--
42−-- @
43−-- > defCommonGeneratorOptions = CommonGeneratorOptions
44−-- > { functionNameBuilder = camelCase
45−-- > , requestBody = "body"
46−-- > , successCallback = "onSuccess"
47−-- > , errorCallback = "onError"
48−-- > , moduleName = ""
49−-- > , urlPrefix = ""
50−-- > }
51−-- @
52−defCommonGeneratorOptions :: CommonGeneratorOptions
53−defCommonGeneratorOptions = CommonGeneratorOptions
54− {
55− functionNameBuilder = camelCase
56− , requestBody = "body"
57− , successCallback = "onSuccess"
58− , errorCallback = "onError"
59− , moduleName = ""
60− , urlPrefix = ""
61− }
62−
63−-- | Function name builder that simply concat each part together
64−concatCase :: FunctionName -> String
65−concatCase = concat
66−
67−-- | Function name builder using the snake_case convention.
68−-- each part is separated by a single underscore character.
69−snakeCase :: FunctionName -> String
70−snakeCase = intercalate "_"
71−
72−-- | Function name builder using the CamelCase convention.
73−-- each part begins with an upper case character.
74−camelCase :: FunctionName -> String
75−camelCase [] = ""
76−camelCase (p:ps) = concat $ p : camelCase' ps
77− where camelCase' [] = []
78− camelCase' (r:rs) = capitalize r : camelCase' rs
79− capitalize [] = []
80− capitalize (x:xs) = toUpper x : xs
81−
82−type Arg = String
83
36
84-- A 'JavascriptGenerator' just takes the data found in the API type
37-- A 'JavascriptGenerator' just takes the data found in the API type
85-- for each endpoint and generates Javascript code in a String. Several
38-- for each endpoint and generates Javascript code in a String. Several
86-- generators are available in this package.
39-- generators are available in this package.
87−type JavaScriptGenerator = [AjaxReq] -> String
40+type JavaScriptGenerator = [Req] -> String
88−
89−data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
90− deriving (Eq, Show)
91−
92−data SegmentType = Static String -- ^ a static path segment. like "/foo"
93− | Cap Arg -- ^ a capture. like "/:userid"
94− deriving (Eq, Show)
95−
96−type Path = [Segment]
97−
98−data ArgType =
99− Normal
100− | Flag
101− | List
102− deriving (Eq, Show)
103−
104−data QueryArg = QueryArg
105− { _argName :: Arg
106− , _argType :: ArgType
107− } deriving (Eq, Show)
108−
109−data HeaderArg = HeaderArg
110− { headerArgName :: String
111− }
112− | ReplaceHeaderArg
113− { headerArgName :: String
114− , headerPattern :: String
115− } deriving (Eq)
116−
117−instance Show HeaderArg where
118− show (HeaderArg n) = toValidFunctionName ("header" <> n)
119− show (ReplaceHeaderArg n p)
120− | pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
121− | pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
122− | pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
123− <> "\""
124− | otherwise = p
125− where
126− pv = toValidFunctionName ("header" <> n)
127− pn = "{" <> n <> "}"
128− rp = replace pn "" p
129− -- Use replace method from Data.Text
130− replace old new = T.unpack .
131− T.replace (T.pack old) (T.pack new) .
132− T.pack
133−
134−-- | Attempts to reduce the function name provided to that allowed by JS.
135−-- https://mathiasbynens.be/notes/javascript-identifiers
136−-- Couldn't work out how to handle zero-width characters.
137−-- @TODO: specify better default function name, or throw error?
138−toValidFunctionName :: String -> String
139−toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
140− where
141− setFirstChar c = if firstChar c
142− then c
143− else '_'
144− firstChar c = prefixOK c || any (Set.member c) firstLetterOK
145− remainder c = prefixOK c || any (Set.member c) remainderOK
146− -- Valid prefixes
147− prefixOK c = c `elem` ['$','_']
148− -- Unicode character sets
149− firstLetterOK = [ Set.lowercaseLetter
150− , Set.uppercaseLetter
151− , Set.titlecaseLetter
152− , Set.modifierLetter
153− , Set.otherLetter
154− , Set.letterNumber ]
155− remainderOK = firstLetterOK <> [ Set.nonSpacingMark
156− , Set.spacingCombiningMark
157− , Set.decimalNumber
158− , Set.connectorPunctuation ]
159−toValidFunctionName [] = "_"
160−
161−type MatrixArg = QueryArg
162−
163−data Url = Url
164− { _path :: Path
165− , _queryStr :: [QueryArg]
166− } deriving (Eq, Show)
167−
168−defUrl :: Url
169−defUrl = Url [] []
170−
171−type FunctionName = [String]
172−type Method = String
173−
174−data AjaxReq = AjaxReq
175− { _reqUrl :: Url
176− , _reqMethod :: Method
177− , _reqHeaders :: [HeaderArg]
178− , _reqBody :: Bool
179− , _funcName :: FunctionName
180− } deriving (Eq, Show)
181−
182−makeLenses ''QueryArg
183−makeLenses ''Segment
184−makeLenses ''Url
185−makeLenses ''AjaxReq
186−
187−isCapture :: Segment -> Bool
188−isCapture (Segment (Cap _) _) = True
189−isCapture _ = False
190−
191−hasMatrixArgs :: Segment -> Bool
192−hasMatrixArgs (Segment _ (_:_)) = True
193−hasMatrixArgs _ = False
194−
195−hasArgs :: Segment -> Bool
196−hasArgs s = isCapture s || hasMatrixArgs s
197−
198−matrixArgs :: Segment -> [MatrixArg]
199−matrixArgs (Segment _ ms) = ms
200−
201−captureArg :: Segment -> Arg
202−captureArg (Segment (Cap s) _) = s
203−captureArg _ = error "captureArg called on non capture"
204
41
205jsSegments :: [Segment] -> String
42jsSegments :: [Segment] -> String
206jsSegments [] = ""
43jsSegments [] = ""
@@ -216,8 +53,8 @@ segmentTypeToStr (Static s) = s
216segmentTypeToStr (Cap s) = "' + encodeURIComponent(" ++ s ++ ") + '"
53segmentTypeToStr (Cap s) = "' + encodeURIComponent(" ++ s ++ ") + '"
217
54
218jsGParams :: String -> [QueryArg] -> String
55jsGParams :: String -> [QueryArg] -> String
219−jsGParams _ [] = ""
56+jsGParams _ [] = ""
220−jsGParams _ [x] = paramToStr x False
57+jsGParams _ [x] = paramToStr x False
221jsGParams s (x:xs) = paramToStr x True ++ s ++ jsGParams s xs
58jsGParams s (x:xs) = paramToStr x True ++ s ++ jsGParams s xs
222
59
223jsParams :: [QueryArg] -> String
60jsParams :: [QueryArg] -> String
@@ -234,191 +71,9 @@ paramToStr qarg notTheEnd =
234 ++ "=' + encodeURIComponent("
71 ++ "=' + encodeURIComponent("
235 ++ name
72 ++ name
236 ++ if notTheEnd then ") + '" else ")"
73 ++ if notTheEnd then ") + '" else ")"
237−
238 Flag -> name ++ "="
74 Flag -> name ++ "="
239−
240 List -> name
75 List -> name
241 ++ "[]=' + encodeURIComponent("
76 ++ "[]=' + encodeURIComponent("
242 ++ name
77 ++ name
243 ++ if notTheEnd then ") + '" else ")"
78 ++ if notTheEnd then ") + '" else ")"
244−
245 where name = qarg ^. argName
79 where name = qarg ^. argName
246−
247−defReq :: AjaxReq
248−defReq = AjaxReq defUrl "GET" [] False []
249−
250−type family Elem (a :: *) (ls::[*]) :: Constraint where
251− Elem a '[] = 'False ~ 'True
252− Elem a (a ': list) = ()
253− Elem a (b ': list) = Elem a list
254−
255−class HasJS (layout :: *) where
256− type JS layout :: *
257− javascriptFor :: Proxy layout -> AjaxReq -> JS layout
258−
259−instance (HasJS a, HasJS b)
260− => HasJS (a :<|> b) where
261− type JS (a :<|> b) = JS a :<|> JS b
262−
263− javascriptFor Proxy req =
264− javascriptFor (Proxy :: Proxy a) req
265− :<|> javascriptFor (Proxy :: Proxy b) req
266−
267−instance (KnownSymbol sym, HasJS sublayout)
268− => HasJS (Capture sym a :> sublayout) where
269− type JS (Capture sym a :> sublayout) = JS sublayout
270−
271− javascriptFor Proxy req =
272− javascriptFor (Proxy :: Proxy sublayout) $
273− req & reqUrl.path <>~ [Segment (Cap str) []]
274− & funcName %~ (++ ["by", str])
275−
276− where str = symbolVal (Proxy :: Proxy sym)
277−
278−instance Elem JSON list => HasJS (Delete list a) where
279− type JS (Delete list a) = AjaxReq
280−
281− javascriptFor Proxy req =
282− req & funcName %~ ("delete" :)
283− & reqMethod .~ "DELETE"
284−
285−instance Elem JSON list => HasJS (Get list a) where
286− type JS (Get list a) = AjaxReq
287−
288− javascriptFor Proxy req =
289− req & funcName %~ ("get" :)
290− & reqMethod .~ "GET"
291−
292−instance (KnownSymbol sym, HasJS sublayout)
293− => HasJS (Header sym a :> sublayout) where
294− type JS (Header sym a :> sublayout) = JS sublayout
295−
296− javascriptFor Proxy req =
297− javascriptFor subP (req & reqHeaders <>~ [HeaderArg hname])
298−
299− where hname = symbolVal (Proxy :: Proxy sym)
300− subP = Proxy :: Proxy sublayout
301−
302−instance Elem JSON list => HasJS (Post list a) where
303− type JS (Post list a) = AjaxReq
304−
305− javascriptFor Proxy req =
306− req & funcName %~ ("post" :)
307− & reqMethod .~ "POST"
308−
309−instance Elem JSON list => HasJS (Put list a) where
310− type JS (Put list a) = AjaxReq
311−
312− javascriptFor Proxy req =
313− req & funcName %~ ("put" :)
314− & reqMethod .~ "PUT"
315−
316−instance (KnownSymbol sym, HasJS sublayout)
317− => HasJS (QueryParam sym a :> sublayout) where
318− type JS (QueryParam sym a :> sublayout) = JS sublayout
319−
320− javascriptFor Proxy req =
321− javascriptFor (Proxy :: Proxy sublayout) $
322− req & reqUrl.queryStr <>~ [QueryArg str Normal]
323−
324− where str = symbolVal (Proxy :: Proxy sym)
325−
326−instance (KnownSymbol sym, HasJS sublayout)
327− => HasJS (QueryParams sym a :> sublayout) where
328− type JS (QueryParams sym a :> sublayout) = JS sublayout
329−
330− javascriptFor Proxy req =
331− javascriptFor (Proxy :: Proxy sublayout) $
332− req & reqUrl.queryStr <>~ [QueryArg str List]
333−
334− where str = symbolVal (Proxy :: Proxy sym)
335−
336−instance (KnownSymbol sym, HasJS sublayout)
337− => HasJS (QueryFlag sym :> sublayout) where
338− type JS (QueryFlag sym :> sublayout) = JS sublayout
339−
340− javascriptFor Proxy req =
341− javascriptFor (Proxy :: Proxy sublayout) $
342− req & reqUrl.queryStr <>~ [QueryArg str Flag]
343−
344− where str = symbolVal (Proxy :: Proxy sym)
345−
346−instance (KnownSymbol sym, HasJS sublayout)
347− => HasJS (MatrixParam sym a :> sublayout) where
348− type JS (MatrixParam sym a :> sublayout) = JS sublayout
349−
350− javascriptFor Proxy req =
351− javascriptFor (Proxy :: Proxy sublayout) $
352− req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
353−
354− where str = symbolVal (Proxy :: Proxy sym)
355− strArg = str ++ "Value"
356−
357−instance (KnownSymbol sym, HasJS sublayout)
358− => HasJS (MatrixParams sym a :> sublayout) where
359− type JS (MatrixParams sym a :> sublayout) = JS sublayout
360−
361− javascriptFor Proxy req =
362− javascriptFor (Proxy :: Proxy sublayout) $
363− req & reqUrl.path._last.matrix <>~ [QueryArg str List]
364−
365− where str = symbolVal (Proxy :: Proxy sym)
366−
367−instance (KnownSymbol sym, HasJS sublayout)
368− => HasJS (MatrixFlag sym :> sublayout) where
369− type JS (MatrixFlag sym :> sublayout) = JS sublayout
370−
371− javascriptFor Proxy req =
372− javascriptFor (Proxy :: Proxy sublayout) $
373− req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
374−
375− where str = symbolVal (Proxy :: Proxy sym)
376−
377−instance HasJS Raw where
378− type JS Raw = Method -> AjaxReq
379−
380− javascriptFor Proxy req method =
381− req & funcName %~ ((toLower <$> method) :)
382− & reqMethod .~ method
383−
384−instance (Elem JSON list, HasJS sublayout) => HasJS (ReqBody list a :> sublayout) where
385− type JS (ReqBody list a :> sublayout) = JS sublayout
386−
387− javascriptFor Proxy req =
388− javascriptFor (Proxy :: Proxy sublayout) $
389− req & reqBody .~ True
390−
391−instance (KnownSymbol path, HasJS sublayout)
392− => HasJS (path :> sublayout) where
393− type JS (path :> sublayout) = JS sublayout
394−
395− javascriptFor Proxy req =
396− javascriptFor (Proxy :: Proxy sublayout) $
397− req & reqUrl.path <>~ [Segment (Static str) []]
398− & funcName %~ (++ [str])
399−
400− where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
401−
402−instance HasJS sublayout => HasJS (RemoteHost :> sublayout) where
403− type JS (RemoteHost :> sublayout) = JS sublayout
404−
405− javascriptFor Proxy req =
406− javascriptFor (Proxy :: Proxy sublayout) req
407−
408−instance HasJS sublayout => HasJS (IsSecure :> sublayout) where
409− type JS (IsSecure :> sublayout) = JS sublayout
410−
411− javascriptFor Proxy req =
412− javascriptFor (Proxy :: Proxy sublayout) req
413−
414−instance HasJS sublayout => HasJS (Vault :> sublayout) where
415− type JS (Vault :> sublayout) = JS sublayout
416−
417− javascriptFor Proxy req =
418− javascriptFor (Proxy :: Proxy sublayout) req
419−
420−instance HasJS sublayout => HasJS (HttpVersion :> sublayout) where
421− type JS (HttpVersion :> sublayout) = JS sublayout
422−
423− javascriptFor Proxy req =
424− javascriptFor (Proxy :: Proxy sublayout) req
servant-js/src/Servant/JS/JQuery.hs+1
@@ -3,6 +3,7 @@ module Servant.JS.JQuery where
3import Control.Lens
3import Control.Lens
4import Data.List
4import Data.List
5import Data.Monoid
5import Data.Monoid
6+import Servant.Foreign
6import Servant.JS.Internal
7import Servant.JS.Internal
7
8
8-- | Generate javascript functions that use the /jQuery/ library
9-- | Generate javascript functions that use the /jQuery/ library
servant-js/src/Servant/JS/Vanilla.hs+1
@@ -3,6 +3,7 @@ module Servant.JS.Vanilla where
3import Control.Lens
3import Control.Lens
4import Data.List
4import Data.List
5import Data.Monoid
5import Data.Monoid
6+import Servant.Foreign
6import Servant.JS.Internal
7import Servant.JS.Internal
7
8
8-- | Generate vanilla javascript functions to make AJAX requests
9-- | Generate vanilla javascript functions to make AJAX requests
servant-js/test/Servant/JSSpec.hs+2−2
@@ -12,8 +12,8 @@ import Data.Proxy
12import Language.ECMAScript3.Parser (parseFromString)
12import Language.ECMAScript3.Parser (parseFromString)
13import Test.Hspec
13import Test.Hspec
14
14
15−import Servant.API
16import Servant.JS
15import Servant.JS
16+import Servant.JS.Internal
17import qualified Servant.JS.Angular as NG
17import qualified Servant.JS.Angular as NG
18import qualified Servant.JS.Axios as AX
18import qualified Servant.JS.Axios as AX
19import qualified Servant.JS.JQuery as JQ
19import qualified Servant.JS.JQuery as JQ
@@ -75,7 +75,7 @@ spec = describe "Servant.JQuery" $ do
75 generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
75 generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
76 generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
76 generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
77 generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
77 generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
78− generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions)
78+ generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { AX.withCredentials = True }) customOptions)
79
79
80 angularSpec Angular
80 angularSpec Angular
81 axiosSpec
81 axiosSpec
servant-js/test/Servant/JSSpec/CustomHeaders.hs+12−14
@@ -12,8 +12,6 @@ import Control.Lens
12import Data.Monoid
12import Data.Monoid
13import Data.Proxy
13import Data.Proxy
14import GHC.TypeLits
14import GHC.TypeLits
15−import Servant.API
16−import Servant.JS
17import Servant.JS.Internal
15import Servant.JS.Internal
18
16
19-- | This is a hypothetical combinator that fetches an Authorization header.
17-- | This is a hypothetical combinator that fetches an Authorization header.
@@ -21,11 +19,11 @@ import Servant.JS.Internal
21-- using -- Basic, Digest, whatever.
19-- using -- Basic, Digest, whatever.
22data Authorization (sym :: Symbol) a
20data Authorization (sym :: Symbol) a
23
21
24−instance (KnownSymbol sym, HasJS sublayout)
22+instance (KnownSymbol sym, HasForeign sublayout)
25− => HasJS (Authorization sym a :> sublayout) where
23+ => HasForeign (Authorization sym a :> sublayout) where
26− type JS (Authorization sym a :> sublayout) = JS sublayout
24+ type Foreign (Authorization sym a :> sublayout) = Foreign sublayout
27
25
28− javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
26+ foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
29 req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $
27 req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $
30 tokenType (symbolVal (Proxy :: Proxy sym)) ]
28 tokenType (symbolVal (Proxy :: Proxy sym)) ]
31 where
29 where
@@ -34,11 +32,11 @@ instance (KnownSymbol sym, HasJS sublayout)
34-- | This is a combinator that fetches an X-MyLovelyHorse header.
32-- | This is a combinator that fetches an X-MyLovelyHorse header.
35data MyLovelyHorse a
33data MyLovelyHorse a
36
34
37−instance (HasJS sublayout)
35+instance (HasForeign sublayout)
38− => HasJS (MyLovelyHorse a :> sublayout) where
36+ => HasForeign (MyLovelyHorse a :> sublayout) where
39− type JS (MyLovelyHorse a :> sublayout) = JS sublayout
37+ type Foreign (MyLovelyHorse a :> sublayout) = Foreign sublayout
40
38
41− javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
39+ foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
42 req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ]
40 req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ]
43 where
41 where
44 tpl = "I am good friends with {X-MyLovelyHorse}"
42 tpl = "I am good friends with {X-MyLovelyHorse}"
@@ -46,11 +44,11 @@ instance (HasJS sublayout)
46-- | This is a combinator that fetches an X-WhatsForDinner header.
44-- | This is a combinator that fetches an X-WhatsForDinner header.
47data WhatsForDinner a
45data WhatsForDinner a
48
46
49−instance (HasJS sublayout)
47+instance (HasForeign sublayout)
50− => HasJS (WhatsForDinner a :> sublayout) where
48+ => HasForeign (WhatsForDinner a :> sublayout) where
51− type JS (WhatsForDinner a :> sublayout) = JS sublayout
49+ type Foreign (WhatsForDinner a :> sublayout) = Foreign sublayout
52
50
53− javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
51+ foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
54 req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ]
52 req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ]
55 where
53 where
56 tpl = "I would like {X-WhatsForDinner} with a cherry on top."
54 tpl = "I would like {X-WhatsForDinner} with a cherry on top."
sources.txt+1
@@ -2,6 +2,7 @@ servant
2servant-cassava
2servant-cassava
3servant-client
3servant-client
4servant-docs
4servant-docs
5+servant-foreign
5servant-js
6servant-js
6servant-server
7servant-server
7servant-examples
8servant-examples
stack.yaml+1
@@ -8,6 +8,7 @@ packages:
8- servant-client/
8- servant-client/
9- servant-docs/
9- servant-docs/
10- servant-examples/
10- servant-examples/
11+- servant-foreign/
11- servant-js/
12- servant-js/
12- servant-lucid/
13- servant-lucid/
13- servant-mock/
14- servant-mock/