Merge pull request #232 from dredozubov/servant-foreign
Common code generation types and helpers extracted to servant-foreign
This commit is contained in:
commit
a1dc0561a3
18 changed files with 529 additions and 385 deletions
4
servant-foreign/CHANGELOG.md
Normal file
4
servant-foreign/CHANGELOG.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
0.5
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Extract javascript-oblivious types and helpers to *servant-foreign*
|
30
servant-foreign/LICENSE
Normal file
30
servant-foreign/LICENSE
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2014, Zalora South East Asia Pte Ltd
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Zalora South East Asia Pte Ltd nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
servant-foreign/README.md
Normal file
5
servant-foreign/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# servant-foreign
|
||||||
|
|
||||||
|
![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
|
||||||
|
|
||||||
|
Types and helpers for generating clients for *servant* servers in arbitrary programming languages.
|
2
servant-foreign/Setup.hs
Normal file
2
servant-foreign/Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
34
servant-foreign/servant-foreign.cabal
Normal file
34
servant-foreign/servant-foreign.cabal
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
name: servant-foreign
|
||||||
|
version: 0.5
|
||||||
|
synopsis: Helpers for generating clients for servant APIs in any programming language
|
||||||
|
description:
|
||||||
|
Helper types and functions for generating client functions for servant APIs in any programming language
|
||||||
|
.
|
||||||
|
This package provides types and functions that collect all the data needed to generate client functions in the programming language of your choice. This effectively means you only have to write the code that "pretty-prints" this data as some code in your target language.
|
||||||
|
.
|
||||||
|
See the servant-js package for an example
|
||||||
|
.
|
||||||
|
<https://github.com/haskell-servant/servant/blob/master/servant-foreign/CHANGELOG.md CHANGELOG>
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Denis Redozubov
|
||||||
|
maintainer: denis.redozubov@gmail.com
|
||||||
|
copyright: 2015 Denis Redozubov, Alp Mestanogullari
|
||||||
|
category: Web
|
||||||
|
build-type: Simple
|
||||||
|
cabal-version: >=1.10
|
||||||
|
extra-source-files:
|
||||||
|
CHANGELOG.md
|
||||||
|
README.md
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: http://github.com/haskell-servant/servant.git
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules: Servant.Foreign
|
||||||
|
build-depends: base == 4.*
|
||||||
|
, lens == 4.*
|
||||||
|
, servant == 0.5.*
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
ghc-options: -Wall
|
323
servant-foreign/src/Servant/Foreign.hs
Normal file
323
servant-foreign/src/Servant/Foreign.hs
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE ConstraintKinds #-}
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE NullaryTypeClasses #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
|
-- | Generalizes all the data needed to make code generation work with
|
||||||
|
-- arbitrary programming languages.
|
||||||
|
module Servant.Foreign
|
||||||
|
( HasForeign(..)
|
||||||
|
, Segment(..)
|
||||||
|
, SegmentType(..)
|
||||||
|
, MatrixArg
|
||||||
|
, FunctionName
|
||||||
|
, QueryArg(..)
|
||||||
|
, HeaderArg(..)
|
||||||
|
, ArgType(..)
|
||||||
|
, Req
|
||||||
|
, captureArg
|
||||||
|
, defReq
|
||||||
|
, concatCase
|
||||||
|
, snakeCase
|
||||||
|
, camelCase
|
||||||
|
-- lenses
|
||||||
|
, argType
|
||||||
|
, argName
|
||||||
|
, isCapture
|
||||||
|
, funcName
|
||||||
|
, path
|
||||||
|
, reqUrl
|
||||||
|
, reqBody
|
||||||
|
, reqHeaders
|
||||||
|
, reqMethod
|
||||||
|
, segment
|
||||||
|
, queryStr
|
||||||
|
-- re-exports
|
||||||
|
, module Servant.API
|
||||||
|
) where
|
||||||
|
|
||||||
|
#if !MIN_VERSION_base(4,8,0)
|
||||||
|
import Control.Applicative
|
||||||
|
#endif
|
||||||
|
import Control.Lens hiding (List)
|
||||||
|
import Data.Char (toLower, toUpper)
|
||||||
|
import Data.List
|
||||||
|
import Data.Proxy
|
||||||
|
import GHC.Exts (Constraint)
|
||||||
|
import GHC.TypeLits
|
||||||
|
import Servant.API
|
||||||
|
|
||||||
|
-- | Function name builder that simply concat each part together
|
||||||
|
concatCase :: FunctionName -> String
|
||||||
|
concatCase = concat
|
||||||
|
|
||||||
|
-- | Function name builder using the snake_case convention.
|
||||||
|
-- each part is separated by a single underscore character.
|
||||||
|
snakeCase :: FunctionName -> String
|
||||||
|
snakeCase = intercalate "_"
|
||||||
|
|
||||||
|
-- | Function name builder using the CamelCase convention.
|
||||||
|
-- each part begins with an upper case character.
|
||||||
|
camelCase :: FunctionName -> String
|
||||||
|
camelCase [] = ""
|
||||||
|
camelCase (p:ps) = concat $ p : camelCase' ps
|
||||||
|
where camelCase' [] = []
|
||||||
|
camelCase' (r:rs) = capitalize r : camelCase' rs
|
||||||
|
capitalize [] = []
|
||||||
|
capitalize (x:xs) = toUpper x : xs
|
||||||
|
|
||||||
|
type Arg = String
|
||||||
|
|
||||||
|
data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data SegmentType = Static String -- ^ a static path segment. like "/foo"
|
||||||
|
| Cap Arg -- ^ a capture. like "/:userid"
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
type Path = [Segment]
|
||||||
|
|
||||||
|
data ArgType =
|
||||||
|
Normal
|
||||||
|
| Flag
|
||||||
|
| List
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data QueryArg = QueryArg
|
||||||
|
{ _argName :: Arg
|
||||||
|
, _argType :: ArgType
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
data HeaderArg = HeaderArg
|
||||||
|
{ headerArgName :: String
|
||||||
|
}
|
||||||
|
| ReplaceHeaderArg
|
||||||
|
{ headerArgName :: String
|
||||||
|
, headerPattern :: String
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
|
||||||
|
type MatrixArg = QueryArg
|
||||||
|
|
||||||
|
data Url = Url
|
||||||
|
{ _path :: Path
|
||||||
|
, _queryStr :: [QueryArg]
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
defUrl :: Url
|
||||||
|
defUrl = Url [] []
|
||||||
|
|
||||||
|
type FunctionName = [String]
|
||||||
|
type Method = String
|
||||||
|
|
||||||
|
data Req = Req
|
||||||
|
{ _reqUrl :: Url
|
||||||
|
, _reqMethod :: Method
|
||||||
|
, _reqHeaders :: [HeaderArg]
|
||||||
|
, _reqBody :: Bool
|
||||||
|
, _funcName :: FunctionName
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
makeLenses ''QueryArg
|
||||||
|
makeLenses ''Segment
|
||||||
|
makeLenses ''Url
|
||||||
|
makeLenses ''Req
|
||||||
|
|
||||||
|
isCapture :: Segment -> Bool
|
||||||
|
isCapture (Segment (Cap _) _) = True
|
||||||
|
isCapture _ = False
|
||||||
|
|
||||||
|
captureArg :: Segment -> Arg
|
||||||
|
captureArg (Segment (Cap s) _) = s
|
||||||
|
captureArg _ = error "captureArg called on non capture"
|
||||||
|
|
||||||
|
defReq :: Req
|
||||||
|
defReq = Req defUrl "GET" [] False []
|
||||||
|
|
||||||
|
-- | To be used exclusively as a "negative" return type/constraint
|
||||||
|
-- by @'Elem`@ type family.
|
||||||
|
class NotFound
|
||||||
|
|
||||||
|
type family Elem (a :: *) (ls::[*]) :: Constraint where
|
||||||
|
Elem a '[] = NotFound
|
||||||
|
Elem a (a ': list) = ()
|
||||||
|
Elem a (b ': list) = Elem a list
|
||||||
|
|
||||||
|
class HasForeign (layout :: *) where
|
||||||
|
type Foreign layout :: *
|
||||||
|
foreignFor :: Proxy layout -> Req -> Foreign layout
|
||||||
|
|
||||||
|
instance (HasForeign a, HasForeign b)
|
||||||
|
=> HasForeign (a :<|> b) where
|
||||||
|
type Foreign (a :<|> b) = Foreign a :<|> Foreign b
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy a) req
|
||||||
|
:<|> foreignFor (Proxy :: Proxy b) req
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (Capture sym a :> sublayout) where
|
||||||
|
type Foreign (Capture sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.path <>~ [Segment (Cap str) []]
|
||||||
|
& funcName %~ (++ ["by", str])
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance Elem JSON list => HasForeign (Delete list a) where
|
||||||
|
type Foreign (Delete list a) = Req
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
req & funcName %~ ("delete" :)
|
||||||
|
& reqMethod .~ "DELETE"
|
||||||
|
|
||||||
|
instance Elem JSON list => HasForeign (Get list a) where
|
||||||
|
type Foreign (Get list a) = Req
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
req & funcName %~ ("get" :)
|
||||||
|
& reqMethod .~ "GET"
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (Header sym a :> sublayout) where
|
||||||
|
type Foreign (Header sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor subP (req & reqHeaders <>~ [HeaderArg hname])
|
||||||
|
|
||||||
|
where hname = symbolVal (Proxy :: Proxy sym)
|
||||||
|
subP = Proxy :: Proxy sublayout
|
||||||
|
|
||||||
|
instance Elem JSON list => HasForeign (Post list a) where
|
||||||
|
type Foreign (Post list a) = Req
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
req & funcName %~ ("post" :)
|
||||||
|
& reqMethod .~ "POST"
|
||||||
|
|
||||||
|
instance Elem JSON list => HasForeign (Put list a) where
|
||||||
|
type Foreign (Put list a) = Req
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
req & funcName %~ ("put" :)
|
||||||
|
& reqMethod .~ "PUT"
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (QueryParam sym a :> sublayout) where
|
||||||
|
type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.queryStr <>~ [QueryArg str Normal]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (QueryParams sym a :> sublayout) where
|
||||||
|
type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.queryStr <>~ [QueryArg str List]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (QueryFlag sym :> sublayout) where
|
||||||
|
type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.queryStr <>~ [QueryArg str Flag]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (MatrixParam sym a :> sublayout) where
|
||||||
|
type Foreign (MatrixParam sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
strArg = str ++ "Value"
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (MatrixParams sym a :> sublayout) where
|
||||||
|
type Foreign (MatrixParams sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.path._last.matrix <>~ [QueryArg str List]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
|
=> HasForeign (MatrixFlag sym :> sublayout) where
|
||||||
|
type Foreign (MatrixFlag sym :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
|
||||||
|
|
||||||
|
where str = symbolVal (Proxy :: Proxy sym)
|
||||||
|
|
||||||
|
instance HasForeign Raw where
|
||||||
|
type Foreign Raw = Method -> Req
|
||||||
|
|
||||||
|
foreignFor Proxy req method =
|
||||||
|
req & funcName %~ ((toLower <$> method) :)
|
||||||
|
& reqMethod .~ method
|
||||||
|
|
||||||
|
instance (Elem JSON list, HasForeign sublayout) => HasForeign (ReqBody list a :> sublayout) where
|
||||||
|
type Foreign (ReqBody list a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqBody .~ True
|
||||||
|
|
||||||
|
instance (KnownSymbol path, HasForeign sublayout)
|
||||||
|
=> HasForeign (path :> sublayout) where
|
||||||
|
type Foreign (path :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) $
|
||||||
|
req & reqUrl.path <>~ [Segment (Static str) []]
|
||||||
|
& funcName %~ (++ [str])
|
||||||
|
|
||||||
|
where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
|
||||||
|
|
||||||
|
instance HasForeign sublayout => HasForeign (RemoteHost :> sublayout) where
|
||||||
|
type Foreign (RemoteHost :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) req
|
||||||
|
|
||||||
|
instance HasForeign sublayout => HasForeign (IsSecure :> sublayout) where
|
||||||
|
type Foreign (IsSecure :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) req
|
||||||
|
|
||||||
|
instance HasForeign sublayout => HasForeign (Vault :> sublayout) where
|
||||||
|
type Foreign (Vault :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) req
|
||||||
|
|
||||||
|
instance HasForeign sublayout => HasForeign (HttpVersion :> sublayout) where
|
||||||
|
type Foreign (HttpVersion :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
|
foreignFor Proxy req =
|
||||||
|
foreignFor (Proxy :: Proxy sublayout) req
|
|
@ -37,14 +37,15 @@ library
|
||||||
exposed-modules: Servant.JS
|
exposed-modules: Servant.JS
|
||||||
Servant.JS.Angular
|
Servant.JS.Angular
|
||||||
Servant.JS.Axios
|
Servant.JS.Axios
|
||||||
|
Servant.JS.Internal
|
||||||
Servant.JS.JQuery
|
Servant.JS.JQuery
|
||||||
Servant.JS.Vanilla
|
Servant.JS.Vanilla
|
||||||
Servant.JS.Internal
|
|
||||||
build-depends: base >= 4.5 && <5
|
build-depends: base >= 4.5 && <5
|
||||||
, charset
|
, charset >= 0.3
|
||||||
, lens >= 4
|
, lens >= 4
|
||||||
, servant == 0.5.*
|
, servant-foreign == 0.5.*
|
||||||
, text
|
, text >= 1.2 && < 1.3
|
||||||
|
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
@ -59,13 +60,13 @@ executable counter
|
||||||
else
|
else
|
||||||
buildable: False
|
buildable: False
|
||||||
|
|
||||||
build-depends:
|
build-depends: base >= 4.7 && < 5
|
||||||
aeson
|
, aeson >= 0.7 && < 0.11
|
||||||
, base
|
, filepath >= 1
|
||||||
, filepath
|
, lens >= 4
|
||||||
, servant == 0.5.*
|
, servant == 0.5.*
|
||||||
, servant-server == 0.5.*
|
, servant-server == 0.5.*
|
||||||
, servant-js == 0.5.*
|
, servant-js
|
||||||
, stm
|
, stm
|
||||||
, transformers
|
, transformers
|
||||||
, warp
|
, warp
|
||||||
|
@ -76,12 +77,12 @@ test-suite spec
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
main-is: Spec.hs
|
main-is: Spec.hs
|
||||||
build-depends:
|
build-depends: base
|
||||||
base == 4.*
|
|
||||||
, lens
|
|
||||||
, servant-js
|
|
||||||
, servant
|
|
||||||
, hspec >= 2.1.8
|
, hspec >= 2.1.8
|
||||||
, hspec-expectations
|
, hspec-expectations
|
||||||
, language-ecmascript >= 0.16
|
, language-ecmascript >= 0.16
|
||||||
|
, lens
|
||||||
|
, servant
|
||||||
|
, servant-js
|
||||||
|
, text
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
|
@ -76,7 +76,7 @@ module Servant.JS
|
||||||
, JavaScriptGenerator
|
, JavaScriptGenerator
|
||||||
|
|
||||||
, -- * Options common to all generators
|
, -- * Options common to all generators
|
||||||
CommonGeneratorOptions(..)
|
CommonGeneratorOptions
|
||||||
, defCommonGeneratorOptions
|
, defCommonGeneratorOptions
|
||||||
|
|
||||||
, -- * Function renamers
|
, -- * Function renamers
|
||||||
|
@ -109,13 +109,10 @@ module Servant.JS
|
||||||
, -- * Misc.
|
, -- * Misc.
|
||||||
listFromAPI
|
listFromAPI
|
||||||
, javascript
|
, javascript
|
||||||
, HasJS(..)
|
|
||||||
, GenerateList(..)
|
, GenerateList(..)
|
||||||
, AjaxReq
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import Servant.API
|
|
||||||
import Servant.JS.Angular
|
import Servant.JS.Angular
|
||||||
import Servant.JS.Axios
|
import Servant.JS.Axios
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
@ -125,13 +122,13 @@ import Servant.JS.Vanilla
|
||||||
-- | Generate the data necessary to generate javascript code
|
-- | Generate the data necessary to generate javascript code
|
||||||
-- for all the endpoints of an API, as ':<|>'-separated values
|
-- for all the endpoints of an API, as ':<|>'-separated values
|
||||||
-- of type 'AjaxReq'.
|
-- of type 'AjaxReq'.
|
||||||
javascript :: HasJS layout => Proxy layout -> JS layout
|
javascript :: HasForeign layout => Proxy layout -> Foreign layout
|
||||||
javascript p = javascriptFor p defReq
|
javascript p = foreignFor p defReq
|
||||||
|
|
||||||
-- | Directly generate all the javascript functions for your API
|
-- | Directly generate all the javascript functions for your API
|
||||||
-- from a 'Proxy' for your API type. You can then write it to
|
-- from a 'Proxy' for your API type. You can then write it to
|
||||||
-- a file or integrate it in a page, for example.
|
-- a file or integrate it in a page, for example.
|
||||||
jsForAPI :: (HasJS api, GenerateList (JS api))
|
jsForAPI :: (HasForeign api, GenerateList (Foreign api))
|
||||||
=> Proxy api -- ^ proxy for your API type
|
=> Proxy api -- ^ proxy for your API type
|
||||||
-> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
|
-> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
|
||||||
-> String -- ^ a string that you can embed in your pages or write to a file
|
-> 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)
|
||||||
-- | Directly generate all the javascript functions for your API
|
-- | Directly generate all the javascript functions for your API
|
||||||
-- from a 'Proxy' for your API type using the given generator
|
-- from a 'Proxy' for your API type using the given generator
|
||||||
-- and write the resulting code to a file at the given path.
|
-- and write the resulting code to a file at the given path.
|
||||||
writeJSForAPI :: (HasJS api, GenerateList (JS api))
|
writeJSForAPI :: (HasForeign api, GenerateList (Foreign api))
|
||||||
=> Proxy api -- ^ proxy for your API type
|
=> Proxy api -- ^ proxy for your API type
|
||||||
-> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
|
-> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others)
|
||||||
-> FilePath -- ^ path to the file you want to write the resulting javascript code into
|
-> 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
|
||||||
|
|
||||||
-- | Generate the necessary data for JS codegen as a list, each 'AjaxReq'
|
-- | Generate the necessary data for JS codegen as a list, each 'AjaxReq'
|
||||||
-- describing one endpoint from your API type.
|
-- describing one endpoint from your API type.
|
||||||
listFromAPI :: (HasJS api, GenerateList (JS api)) => Proxy api -> [AjaxReq]
|
listFromAPI :: (HasForeign api, GenerateList (Foreign api)) => Proxy api -> [AjaxReq]
|
||||||
listFromAPI p = generateList (javascript p)
|
listFromAPI p = generateList (javascript p)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ module Servant.JS.Angular where
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
|
import Servant.Foreign
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
|
||||||
-- | Options specific to the angular code generator
|
-- | Options specific to the angular code generator
|
||||||
|
@ -108,7 +109,7 @@ generateAngularJSWith ngOptions opts req = "\n" <>
|
||||||
where headersStr = intercalate ", " $ map headerStr hs
|
where headersStr = intercalate ", " $ map headerStr hs
|
||||||
headerStr header = "\"" ++
|
headerStr header = "\"" ++
|
||||||
headerArgName header ++
|
headerArgName header ++
|
||||||
"\": " ++ show header
|
"\": " ++ toJSHeader header
|
||||||
|
|
||||||
namespace =
|
namespace =
|
||||||
if hasService
|
if hasService
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Control.Lens
|
||||||
import Data.Char (toLower)
|
import Data.Char (toLower)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
|
import Servant.Foreign
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
|
||||||
-- | Axios 'configuration' type
|
-- | Axios 'configuration' type
|
||||||
|
@ -103,7 +104,7 @@ generateAxiosJSWith aopts opts req = "\n" <>
|
||||||
where headersStr = intercalate ", " $ map headerStr hs
|
where headersStr = intercalate ", " $ map headerStr hs
|
||||||
headerStr header = "\"" ++
|
headerStr header = "\"" ++
|
||||||
headerArgName header ++
|
headerArgName header ++
|
||||||
"\": " ++ show header
|
"\": " ++ toJSHeader header
|
||||||
|
|
||||||
namespace =
|
namespace =
|
||||||
if hasNoModule
|
if hasNoModule
|
||||||
|
|
|
@ -1,31 +1,52 @@
|
||||||
{-# LANGUAGE CPP #-}
|
module Servant.JS.Internal
|
||||||
{-# LANGUAGE ConstraintKinds #-}
|
( JavaScriptGenerator
|
||||||
{-# LANGUAGE DataKinds #-}
|
, CommonGeneratorOptions(..)
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
, defCommonGeneratorOptions
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
, AjaxReq
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
, jsSegments
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
, segmentToStr
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
, segmentTypeToStr
|
||||||
{-# LANGUAGE TypeOperators #-}
|
, jsParams
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
, jsGParams
|
||||||
module Servant.JS.Internal where
|
, jsMParams
|
||||||
|
, paramToStr
|
||||||
|
, toValidFunctionName
|
||||||
|
, toJSHeader
|
||||||
|
-- re-exports
|
||||||
|
, (:<|>)(..)
|
||||||
|
, (:>)
|
||||||
|
, defReq
|
||||||
|
, reqHeaders
|
||||||
|
, HasForeign(..)
|
||||||
|
, HeaderArg(..)
|
||||||
|
, concatCase
|
||||||
|
, snakeCase
|
||||||
|
, camelCase
|
||||||
|
, ReqBody
|
||||||
|
, JSON
|
||||||
|
, FormUrlEncoded
|
||||||
|
, Post
|
||||||
|
, Get
|
||||||
|
, Raw
|
||||||
|
, Header
|
||||||
|
) where
|
||||||
|
|
||||||
#if !MIN_VERSION_base(4,8,0)
|
|
||||||
import Control.Applicative
|
|
||||||
#endif
|
|
||||||
import Control.Lens hiding (List)
|
import Control.Lens hiding (List)
|
||||||
import Data.Char (toLower, toUpper)
|
|
||||||
import qualified Data.CharSet as Set
|
import qualified Data.CharSet as Set
|
||||||
import qualified Data.CharSet.Unicode.Category as Set
|
import qualified Data.CharSet.Unicode.Category as Set
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Data.Proxy
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import GHC.Exts (Constraint)
|
import Servant.Foreign
|
||||||
import GHC.TypeLits
|
|
||||||
import Servant.API
|
|
||||||
|
|
||||||
-- | this structure is used by JavaScriptGenerator implementations to let you
|
type AjaxReq = Req
|
||||||
|
|
||||||
|
-- A 'JavascriptGenerator' just takes the data found in the API type
|
||||||
|
-- for each endpoint and generates Javascript code in a String. Several
|
||||||
|
-- generators are available in this package.
|
||||||
|
type JavaScriptGenerator = [Req] -> String
|
||||||
|
|
||||||
|
-- | This structure is used by specific implementations to let you
|
||||||
-- customize the output
|
-- customize the output
|
||||||
data CommonGeneratorOptions = CommonGeneratorOptions
|
data CommonGeneratorOptions = CommonGeneratorOptions
|
||||||
{
|
{
|
||||||
|
@ -33,8 +54,8 @@ data CommonGeneratorOptions = CommonGeneratorOptions
|
||||||
, requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
|
, requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
|
||||||
, successCallback :: String -- ^ name of the callback parameter when the request was successful
|
, successCallback :: String -- ^ name of the callback parameter when the request was successful
|
||||||
, errorCallback :: String -- ^ name of the callback parameter when the request reported an error
|
, errorCallback :: String -- ^ name of the callback parameter when the request reported an error
|
||||||
, moduleName :: String -- ^ namespace on which we define the js function (empty mean local var)
|
, moduleName :: String -- ^ namespace on which we define the foreign function (empty mean local var)
|
||||||
, urlPrefix :: String -- ^ a prefix we should add to the Url in the JS codegen
|
, urlPrefix :: String -- ^ a prefix we should add to the Url in the codegen
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Default options.
|
-- | Default options.
|
||||||
|
@ -60,87 +81,16 @@ defCommonGeneratorOptions = CommonGeneratorOptions
|
||||||
, urlPrefix = ""
|
, urlPrefix = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Function name builder that simply concat each part together
|
-- | Attempts to reduce the function name provided to that allowed by @'Foreign'@.
|
||||||
concatCase :: FunctionName -> String
|
--
|
||||||
concatCase = concat
|
|
||||||
|
|
||||||
-- | Function name builder using the snake_case convention.
|
|
||||||
-- each part is separated by a single underscore character.
|
|
||||||
snakeCase :: FunctionName -> String
|
|
||||||
snakeCase = intercalate "_"
|
|
||||||
|
|
||||||
-- | Function name builder using the CamelCase convention.
|
|
||||||
-- each part begins with an upper case character.
|
|
||||||
camelCase :: FunctionName -> String
|
|
||||||
camelCase [] = ""
|
|
||||||
camelCase (p:ps) = concat $ p : camelCase' ps
|
|
||||||
where camelCase' [] = []
|
|
||||||
camelCase' (r:rs) = capitalize r : camelCase' rs
|
|
||||||
capitalize [] = []
|
|
||||||
capitalize (x:xs) = toUpper x : xs
|
|
||||||
|
|
||||||
type Arg = String
|
|
||||||
|
|
||||||
-- A 'JavascriptGenerator' just takes the data found in the API type
|
|
||||||
-- for each endpoint and generates Javascript code in a String. Several
|
|
||||||
-- generators are available in this package.
|
|
||||||
type JavaScriptGenerator = [AjaxReq] -> String
|
|
||||||
|
|
||||||
data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
data SegmentType = Static String -- ^ a static path segment. like "/foo"
|
|
||||||
| Cap Arg -- ^ a capture. like "/:userid"
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
type Path = [Segment]
|
|
||||||
|
|
||||||
data ArgType =
|
|
||||||
Normal
|
|
||||||
| Flag
|
|
||||||
| List
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
data QueryArg = QueryArg
|
|
||||||
{ _argName :: Arg
|
|
||||||
, _argType :: ArgType
|
|
||||||
} deriving (Eq, Show)
|
|
||||||
|
|
||||||
data HeaderArg = HeaderArg
|
|
||||||
{ headerArgName :: String
|
|
||||||
}
|
|
||||||
| ReplaceHeaderArg
|
|
||||||
{ headerArgName :: String
|
|
||||||
, headerPattern :: String
|
|
||||||
} deriving (Eq)
|
|
||||||
|
|
||||||
instance Show HeaderArg where
|
|
||||||
show (HeaderArg n) = toValidFunctionName ("header" <> n)
|
|
||||||
show (ReplaceHeaderArg n p)
|
|
||||||
| pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
|
|
||||||
| pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
|
|
||||||
| pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
|
|
||||||
<> "\""
|
|
||||||
| otherwise = p
|
|
||||||
where
|
|
||||||
pv = toValidFunctionName ("header" <> n)
|
|
||||||
pn = "{" <> n <> "}"
|
|
||||||
rp = replace pn "" p
|
|
||||||
-- Use replace method from Data.Text
|
|
||||||
replace old new = T.unpack .
|
|
||||||
T.replace (T.pack old) (T.pack new) .
|
|
||||||
T.pack
|
|
||||||
|
|
||||||
-- | Attempts to reduce the function name provided to that allowed by JS.
|
|
||||||
-- https://mathiasbynens.be/notes/javascript-identifiers
|
-- https://mathiasbynens.be/notes/javascript-identifiers
|
||||||
-- Couldn't work out how to handle zero-width characters.
|
-- Couldn't work out how to handle zero-width characters.
|
||||||
|
--
|
||||||
-- @TODO: specify better default function name, or throw error?
|
-- @TODO: specify better default function name, or throw error?
|
||||||
toValidFunctionName :: String -> String
|
toValidFunctionName :: String -> String
|
||||||
toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
|
toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
|
||||||
where
|
where
|
||||||
setFirstChar c = if firstChar c
|
setFirstChar c = if firstChar c then c else '_'
|
||||||
then c
|
|
||||||
else '_'
|
|
||||||
firstChar c = prefixOK c || any (Set.member c) firstLetterOK
|
firstChar c = prefixOK c || any (Set.member c) firstLetterOK
|
||||||
remainder c = prefixOK c || any (Set.member c) remainderOK
|
remainder c = prefixOK c || any (Set.member c) remainderOK
|
||||||
-- Valid prefixes
|
-- Valid prefixes
|
||||||
|
@ -152,55 +102,29 @@ toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
|
||||||
, Set.modifierLetter
|
, Set.modifierLetter
|
||||||
, Set.otherLetter
|
, Set.otherLetter
|
||||||
, Set.letterNumber ]
|
, Set.letterNumber ]
|
||||||
remainderOK = firstLetterOK <> [ Set.nonSpacingMark
|
remainderOK = firstLetterOK
|
||||||
|
<> [ Set.nonSpacingMark
|
||||||
, Set.spacingCombiningMark
|
, Set.spacingCombiningMark
|
||||||
, Set.decimalNumber
|
, Set.decimalNumber
|
||||||
, Set.connectorPunctuation ]
|
, Set.connectorPunctuation ]
|
||||||
toValidFunctionName [] = "_"
|
toValidFunctionName [] = "_"
|
||||||
|
|
||||||
type MatrixArg = QueryArg
|
toJSHeader :: HeaderArg -> String
|
||||||
|
toJSHeader (HeaderArg n) = toValidFunctionName ("header" <> n)
|
||||||
data Url = Url
|
toJSHeader (ReplaceHeaderArg n p)
|
||||||
{ _path :: Path
|
| pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
|
||||||
, _queryStr :: [QueryArg]
|
| pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
|
||||||
} deriving (Eq, Show)
|
| pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
|
||||||
|
<> "\""
|
||||||
defUrl :: Url
|
| otherwise = p
|
||||||
defUrl = Url [] []
|
where
|
||||||
|
pv = toValidFunctionName ("header" <> n)
|
||||||
type FunctionName = [String]
|
pn = "{" <> n <> "}"
|
||||||
type Method = String
|
rp = replace pn "" p
|
||||||
|
-- Use replace method from Data.Text
|
||||||
data AjaxReq = AjaxReq
|
replace old new = T.unpack
|
||||||
{ _reqUrl :: Url
|
. T.replace (T.pack old) (T.pack new)
|
||||||
, _reqMethod :: Method
|
. T.pack
|
||||||
, _reqHeaders :: [HeaderArg]
|
|
||||||
, _reqBody :: Bool
|
|
||||||
, _funcName :: FunctionName
|
|
||||||
} deriving (Eq, Show)
|
|
||||||
|
|
||||||
makeLenses ''QueryArg
|
|
||||||
makeLenses ''Segment
|
|
||||||
makeLenses ''Url
|
|
||||||
makeLenses ''AjaxReq
|
|
||||||
|
|
||||||
isCapture :: Segment -> Bool
|
|
||||||
isCapture (Segment (Cap _) _) = True
|
|
||||||
isCapture _ = False
|
|
||||||
|
|
||||||
hasMatrixArgs :: Segment -> Bool
|
|
||||||
hasMatrixArgs (Segment _ (_:_)) = True
|
|
||||||
hasMatrixArgs _ = False
|
|
||||||
|
|
||||||
hasArgs :: Segment -> Bool
|
|
||||||
hasArgs s = isCapture s || hasMatrixArgs s
|
|
||||||
|
|
||||||
matrixArgs :: Segment -> [MatrixArg]
|
|
||||||
matrixArgs (Segment _ ms) = ms
|
|
||||||
|
|
||||||
captureArg :: Segment -> Arg
|
|
||||||
captureArg (Segment (Cap s) _) = s
|
|
||||||
captureArg _ = error "captureArg called on non capture"
|
|
||||||
|
|
||||||
jsSegments :: [Segment] -> String
|
jsSegments :: [Segment] -> String
|
||||||
jsSegments [] = ""
|
jsSegments [] = ""
|
||||||
|
@ -234,191 +158,9 @@ paramToStr qarg notTheEnd =
|
||||||
++ "=' + encodeURIComponent("
|
++ "=' + encodeURIComponent("
|
||||||
++ name
|
++ name
|
||||||
++ if notTheEnd then ") + '" else ")"
|
++ if notTheEnd then ") + '" else ")"
|
||||||
|
|
||||||
Flag -> name ++ "="
|
Flag -> name ++ "="
|
||||||
|
|
||||||
List -> name
|
List -> name
|
||||||
++ "[]=' + encodeURIComponent("
|
++ "[]=' + encodeURIComponent("
|
||||||
++ name
|
++ name
|
||||||
++ if notTheEnd then ") + '" else ")"
|
++ if notTheEnd then ") + '" else ")"
|
||||||
|
|
||||||
where name = qarg ^. argName
|
where name = qarg ^. argName
|
||||||
|
|
||||||
defReq :: AjaxReq
|
|
||||||
defReq = AjaxReq defUrl "GET" [] False []
|
|
||||||
|
|
||||||
type family Elem (a :: *) (ls::[*]) :: Constraint where
|
|
||||||
Elem a '[] = 'False ~ 'True
|
|
||||||
Elem a (a ': list) = ()
|
|
||||||
Elem a (b ': list) = Elem a list
|
|
||||||
|
|
||||||
class HasJS (layout :: *) where
|
|
||||||
type JS layout :: *
|
|
||||||
javascriptFor :: Proxy layout -> AjaxReq -> JS layout
|
|
||||||
|
|
||||||
instance (HasJS a, HasJS b)
|
|
||||||
=> HasJS (a :<|> b) where
|
|
||||||
type JS (a :<|> b) = JS a :<|> JS b
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy a) req
|
|
||||||
:<|> javascriptFor (Proxy :: Proxy b) req
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (Capture sym a :> sublayout) where
|
|
||||||
type JS (Capture sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.path <>~ [Segment (Cap str) []]
|
|
||||||
& funcName %~ (++ ["by", str])
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance Elem JSON list => HasJS (Delete list a) where
|
|
||||||
type JS (Delete list a) = AjaxReq
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
req & funcName %~ ("delete" :)
|
|
||||||
& reqMethod .~ "DELETE"
|
|
||||||
|
|
||||||
instance Elem JSON list => HasJS (Get list a) where
|
|
||||||
type JS (Get list a) = AjaxReq
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
req & funcName %~ ("get" :)
|
|
||||||
& reqMethod .~ "GET"
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (Header sym a :> sublayout) where
|
|
||||||
type JS (Header sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor subP (req & reqHeaders <>~ [HeaderArg hname])
|
|
||||||
|
|
||||||
where hname = symbolVal (Proxy :: Proxy sym)
|
|
||||||
subP = Proxy :: Proxy sublayout
|
|
||||||
|
|
||||||
instance Elem JSON list => HasJS (Post list a) where
|
|
||||||
type JS (Post list a) = AjaxReq
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
req & funcName %~ ("post" :)
|
|
||||||
& reqMethod .~ "POST"
|
|
||||||
|
|
||||||
instance Elem JSON list => HasJS (Put list a) where
|
|
||||||
type JS (Put list a) = AjaxReq
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
req & funcName %~ ("put" :)
|
|
||||||
& reqMethod .~ "PUT"
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (QueryParam sym a :> sublayout) where
|
|
||||||
type JS (QueryParam sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.queryStr <>~ [QueryArg str Normal]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (QueryParams sym a :> sublayout) where
|
|
||||||
type JS (QueryParams sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.queryStr <>~ [QueryArg str List]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (QueryFlag sym :> sublayout) where
|
|
||||||
type JS (QueryFlag sym :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.queryStr <>~ [QueryArg str Flag]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (MatrixParam sym a :> sublayout) where
|
|
||||||
type JS (MatrixParam sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
strArg = str ++ "Value"
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (MatrixParams sym a :> sublayout) where
|
|
||||||
type JS (MatrixParams sym a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.path._last.matrix <>~ [QueryArg str List]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
|
||||||
=> HasJS (MatrixFlag sym :> sublayout) where
|
|
||||||
type JS (MatrixFlag sym :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
|
|
||||||
|
|
||||||
where str = symbolVal (Proxy :: Proxy sym)
|
|
||||||
|
|
||||||
instance HasJS Raw where
|
|
||||||
type JS Raw = Method -> AjaxReq
|
|
||||||
|
|
||||||
javascriptFor Proxy req method =
|
|
||||||
req & funcName %~ ((toLower <$> method) :)
|
|
||||||
& reqMethod .~ method
|
|
||||||
|
|
||||||
instance (Elem JSON list, HasJS sublayout) => HasJS (ReqBody list a :> sublayout) where
|
|
||||||
type JS (ReqBody list a :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqBody .~ True
|
|
||||||
|
|
||||||
instance (KnownSymbol path, HasJS sublayout)
|
|
||||||
=> HasJS (path :> sublayout) where
|
|
||||||
type JS (path :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) $
|
|
||||||
req & reqUrl.path <>~ [Segment (Static str) []]
|
|
||||||
& funcName %~ (++ [str])
|
|
||||||
|
|
||||||
where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
|
|
||||||
|
|
||||||
instance HasJS sublayout => HasJS (RemoteHost :> sublayout) where
|
|
||||||
type JS (RemoteHost :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) req
|
|
||||||
|
|
||||||
instance HasJS sublayout => HasJS (IsSecure :> sublayout) where
|
|
||||||
type JS (IsSecure :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) req
|
|
||||||
|
|
||||||
instance HasJS sublayout => HasJS (Vault :> sublayout) where
|
|
||||||
type JS (Vault :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) req
|
|
||||||
|
|
||||||
instance HasJS sublayout => HasJS (HttpVersion :> sublayout) where
|
|
||||||
type JS (HttpVersion :> sublayout) = JS sublayout
|
|
||||||
|
|
||||||
javascriptFor Proxy req =
|
|
||||||
javascriptFor (Proxy :: Proxy sublayout) req
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ module Servant.JS.JQuery where
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
|
import Servant.Foreign
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
|
||||||
-- | Generate javascript functions that use the /jQuery/ library
|
-- | Generate javascript functions that use the /jQuery/ library
|
||||||
|
@ -71,7 +72,7 @@ generateJQueryJSWith opts req = "\n" <>
|
||||||
where headersStr = intercalate ", " $ map headerStr hs
|
where headersStr = intercalate ", " $ map headerStr hs
|
||||||
headerStr header = "\"" ++
|
headerStr header = "\"" ++
|
||||||
headerArgName header ++
|
headerArgName header ++
|
||||||
"\": " ++ show header
|
"\": " ++ toJSHeader header
|
||||||
|
|
||||||
namespace = if null (moduleName opts)
|
namespace = if null (moduleName opts)
|
||||||
then "var "
|
then "var "
|
||||||
|
|
|
@ -3,6 +3,7 @@ module Servant.JS.Vanilla where
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
|
import Servant.Foreign
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
|
||||||
-- | Generate vanilla javascript functions to make AJAX requests
|
-- | Generate vanilla javascript functions to make AJAX requests
|
||||||
|
@ -78,7 +79,7 @@ generateVanillaJSWith opts req = "\n" <>
|
||||||
where headersStr = intercalate "\n" $ map headerStr hs
|
where headersStr = intercalate "\n" $ map headerStr hs
|
||||||
headerStr header = " xhr.setRequestHeader(\"" ++
|
headerStr header = " xhr.setRequestHeader(\"" ++
|
||||||
headerArgName header ++
|
headerArgName header ++
|
||||||
"\", " ++ show header ++ ");"
|
"\", " ++ toJSHeader header ++ ");"
|
||||||
|
|
||||||
namespace = if null (moduleName opts)
|
namespace = if null (moduleName opts)
|
||||||
then "var "
|
then "var "
|
||||||
|
|
|
@ -12,8 +12,8 @@ import Data.Proxy
|
||||||
import Language.ECMAScript3.Parser (parseFromString)
|
import Language.ECMAScript3.Parser (parseFromString)
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
import Servant.API
|
|
||||||
import Servant.JS
|
import Servant.JS
|
||||||
|
import Servant.JS.Internal
|
||||||
import qualified Servant.JS.Angular as NG
|
import qualified Servant.JS.Angular as NG
|
||||||
import qualified Servant.JS.Axios as AX
|
import qualified Servant.JS.Axios as AX
|
||||||
import qualified Servant.JS.JQuery as JQ
|
import qualified Servant.JS.JQuery as JQ
|
||||||
|
@ -75,7 +75,7 @@ spec = describe "Servant.JQuery" $ do
|
||||||
generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
|
generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions)
|
||||||
generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
|
generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions)
|
||||||
generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
|
generateJSSpec Axios (AX.generateAxiosJS AX.defAxiosOptions)
|
||||||
generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { withCredentials = True }) customOptions)
|
generateJSSpec AxiosCustom (AX.generateAxiosJSWith (AX.defAxiosOptions { AX.withCredentials = True }) customOptions)
|
||||||
|
|
||||||
angularSpec Angular
|
angularSpec Angular
|
||||||
axiosSpec
|
axiosSpec
|
||||||
|
|
|
@ -12,8 +12,6 @@ import Control.Lens
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import GHC.TypeLits
|
import GHC.TypeLits
|
||||||
import Servant.API
|
|
||||||
import Servant.JS
|
|
||||||
import Servant.JS.Internal
|
import Servant.JS.Internal
|
||||||
|
|
||||||
-- | This is a hypothetical combinator that fetches an Authorization header.
|
-- | This is a hypothetical combinator that fetches an Authorization header.
|
||||||
|
@ -21,11 +19,11 @@ import Servant.JS.Internal
|
||||||
-- using -- Basic, Digest, whatever.
|
-- using -- Basic, Digest, whatever.
|
||||||
data Authorization (sym :: Symbol) a
|
data Authorization (sym :: Symbol) a
|
||||||
|
|
||||||
instance (KnownSymbol sym, HasJS sublayout)
|
instance (KnownSymbol sym, HasForeign sublayout)
|
||||||
=> HasJS (Authorization sym a :> sublayout) where
|
=> HasForeign (Authorization sym a :> sublayout) where
|
||||||
type JS (Authorization sym a :> sublayout) = JS sublayout
|
type Foreign (Authorization sym a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
|
foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
|
||||||
req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $
|
req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $
|
||||||
tokenType (symbolVal (Proxy :: Proxy sym)) ]
|
tokenType (symbolVal (Proxy :: Proxy sym)) ]
|
||||||
where
|
where
|
||||||
|
@ -34,11 +32,11 @@ instance (KnownSymbol sym, HasJS sublayout)
|
||||||
-- | This is a combinator that fetches an X-MyLovelyHorse header.
|
-- | This is a combinator that fetches an X-MyLovelyHorse header.
|
||||||
data MyLovelyHorse a
|
data MyLovelyHorse a
|
||||||
|
|
||||||
instance (HasJS sublayout)
|
instance (HasForeign sublayout)
|
||||||
=> HasJS (MyLovelyHorse a :> sublayout) where
|
=> HasForeign (MyLovelyHorse a :> sublayout) where
|
||||||
type JS (MyLovelyHorse a :> sublayout) = JS sublayout
|
type Foreign (MyLovelyHorse a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
|
foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
|
||||||
req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ]
|
req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ]
|
||||||
where
|
where
|
||||||
tpl = "I am good friends with {X-MyLovelyHorse}"
|
tpl = "I am good friends with {X-MyLovelyHorse}"
|
||||||
|
@ -46,11 +44,11 @@ instance (HasJS sublayout)
|
||||||
-- | This is a combinator that fetches an X-WhatsForDinner header.
|
-- | This is a combinator that fetches an X-WhatsForDinner header.
|
||||||
data WhatsForDinner a
|
data WhatsForDinner a
|
||||||
|
|
||||||
instance (HasJS sublayout)
|
instance (HasForeign sublayout)
|
||||||
=> HasJS (WhatsForDinner a :> sublayout) where
|
=> HasForeign (WhatsForDinner a :> sublayout) where
|
||||||
type JS (WhatsForDinner a :> sublayout) = JS sublayout
|
type Foreign (WhatsForDinner a :> sublayout) = Foreign sublayout
|
||||||
|
|
||||||
javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $
|
foreignFor Proxy req = foreignFor (Proxy :: Proxy sublayout) $
|
||||||
req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ]
|
req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ]
|
||||||
where
|
where
|
||||||
tpl = "I would like {X-WhatsForDinner} with a cherry on top."
|
tpl = "I would like {X-WhatsForDinner} with a cherry on top."
|
||||||
|
|
|
@ -2,6 +2,7 @@ servant
|
||||||
servant-cassava
|
servant-cassava
|
||||||
servant-client
|
servant-client
|
||||||
servant-docs
|
servant-docs
|
||||||
|
servant-foreign
|
||||||
servant-js
|
servant-js
|
||||||
servant-server
|
servant-server
|
||||||
servant-examples
|
servant-examples
|
||||||
|
|
|
@ -6,6 +6,7 @@ packages:
|
||||||
- servant-client/
|
- servant-client/
|
||||||
- servant-lucid/
|
- servant-lucid/
|
||||||
- servant-mock/
|
- servant-mock/
|
||||||
|
- servant-foreign/
|
||||||
- servant-js/
|
- servant-js/
|
||||||
- servant/
|
- servant/
|
||||||
- servant-server/
|
- servant-server/
|
||||||
|
|
|
@ -8,6 +8,7 @@ packages:
|
||||||
- servant-client/
|
- servant-client/
|
||||||
- servant-docs/
|
- servant-docs/
|
||||||
- servant-examples/
|
- servant-examples/
|
||||||
|
- servant-foreign/
|
||||||
- servant-js/
|
- servant-js/
|
||||||
- servant-lucid/
|
- servant-lucid/
|
||||||
- servant-mock/
|
- servant-mock/
|
||||||
|
|
Loading…
Reference in a new issue