[servant-foreign] Parameterise Req with a foreign type
This commit is contained in:
parent
bfe812f5d9
commit
207f05e759
4 changed files with 75 additions and 50 deletions
|
@ -46,6 +46,7 @@ library
|
||||||
, GeneralizedNewtypeDeriving
|
, GeneralizedNewtypeDeriving
|
||||||
, MultiParamTypeClasses
|
, MultiParamTypeClasses
|
||||||
, ScopedTypeVariables
|
, ScopedTypeVariables
|
||||||
|
, StandaloneDeriving
|
||||||
, TemplateHaskell
|
, TemplateHaskell
|
||||||
, TypeFamilies
|
, TypeFamilies
|
||||||
, TypeOperators
|
, TypeOperators
|
||||||
|
@ -64,6 +65,7 @@ test-suite spec
|
||||||
build-depends: base
|
build-depends: base
|
||||||
, hspec >= 2.1.8
|
, hspec >= 2.1.8
|
||||||
, servant-foreign
|
, servant-foreign
|
||||||
|
, text >= 1.2 && < 1.3
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
default-extensions: ConstraintKinds
|
default-extensions: ConstraintKinds
|
||||||
, DataKinds
|
, DataKinds
|
||||||
|
|
|
@ -27,8 +27,12 @@ newtype FunctionName = FunctionName { unFunctionName :: [Text] }
|
||||||
|
|
||||||
makePrisms ''FunctionName
|
makePrisms ''FunctionName
|
||||||
|
|
||||||
newtype ForeignType = ForeignType { unForeignType :: Text }
|
newtype ForeignType f = ForeignType { unForeignType :: f }
|
||||||
deriving (Show, Eq, IsString, Monoid)
|
|
||||||
|
deriving instance Show f => Show (ForeignType f)
|
||||||
|
deriving instance Eq f => Eq (ForeignType f)
|
||||||
|
deriving instance IsString f => IsString (ForeignType f)
|
||||||
|
deriving instance Monoid f => Monoid (ForeignType f)
|
||||||
|
|
||||||
makePrisms ''ForeignType
|
makePrisms ''ForeignType
|
||||||
|
|
||||||
|
@ -37,39 +41,45 @@ newtype PathSegment = PathSegment { unPathSegment :: Text }
|
||||||
|
|
||||||
makePrisms ''PathSegment
|
makePrisms ''PathSegment
|
||||||
|
|
||||||
data Arg = Arg
|
data Arg f = Arg
|
||||||
{ _argName :: PathSegment
|
{ _argName :: PathSegment
|
||||||
, _argType :: ForeignType }
|
, _argType :: ForeignType f }
|
||||||
deriving (Show, Eq)
|
|
||||||
|
deriving instance Eq f => Eq (Arg f)
|
||||||
|
deriving instance Show f => Show (Arg f)
|
||||||
|
|
||||||
makeLenses ''Arg
|
makeLenses ''Arg
|
||||||
|
|
||||||
argPath :: Getter Arg Text
|
argPath :: Getter (Arg f) Text
|
||||||
argPath = argName . _PathSegment
|
argPath = argName . _PathSegment
|
||||||
|
|
||||||
data SegmentType
|
data SegmentType f
|
||||||
= Static PathSegment
|
= Static PathSegment
|
||||||
-- ^ a static path segment. like "/foo"
|
-- ^ a static path segment. like "/foo"
|
||||||
| Cap Arg
|
| Cap (Arg f)
|
||||||
-- ^ a capture. like "/:userid"
|
-- ^ a capture. like "/:userid"
|
||||||
deriving (Show, Eq)
|
|
||||||
|
deriving instance Eq f => Eq (SegmentType f)
|
||||||
|
deriving instance Show f => Show (SegmentType f)
|
||||||
|
|
||||||
makePrisms ''SegmentType
|
makePrisms ''SegmentType
|
||||||
|
|
||||||
newtype Segment = Segment { unSegment :: SegmentType }
|
newtype Segment f = Segment { unSegment :: SegmentType f }
|
||||||
deriving (Eq, Show)
|
|
||||||
|
deriving instance Eq f => Eq (Segment f)
|
||||||
|
deriving instance Show f => Show (Segment f)
|
||||||
|
|
||||||
makePrisms ''Segment
|
makePrisms ''Segment
|
||||||
|
|
||||||
isCapture :: Segment -> Bool
|
isCapture :: Segment f -> Bool
|
||||||
isCapture (Segment (Cap _)) = True
|
isCapture (Segment (Cap _)) = True
|
||||||
isCapture _ = False
|
isCapture _ = False
|
||||||
|
|
||||||
captureArg :: Segment -> Arg
|
captureArg :: Segment f -> Arg f
|
||||||
captureArg (Segment (Cap s)) = s
|
captureArg (Segment (Cap s)) = s
|
||||||
captureArg _ = error "captureArg called on non capture"
|
captureArg _ = error "captureArg called on non capture"
|
||||||
|
|
||||||
type Path = [Segment]
|
type Path f = [Segment f]
|
||||||
|
|
||||||
data ArgType
|
data ArgType
|
||||||
= Normal
|
= Normal
|
||||||
|
@ -79,46 +89,58 @@ data ArgType
|
||||||
|
|
||||||
makePrisms ''ArgType
|
makePrisms ''ArgType
|
||||||
|
|
||||||
data QueryArg = QueryArg
|
data QueryArg f = QueryArg
|
||||||
{ _queryArgName :: Arg
|
{ _queryArgName :: Arg f
|
||||||
, _queryArgType :: ArgType
|
, _queryArgType :: ArgType
|
||||||
} deriving (Eq, Show)
|
}
|
||||||
|
|
||||||
|
deriving instance Eq f => Eq (QueryArg f)
|
||||||
|
deriving instance Show f => Show (QueryArg f)
|
||||||
|
|
||||||
makeLenses ''QueryArg
|
makeLenses ''QueryArg
|
||||||
|
|
||||||
data HeaderArg = HeaderArg
|
data HeaderArg f = HeaderArg
|
||||||
{ _headerArg :: Arg }
|
{ _headerArg :: Arg f }
|
||||||
| ReplaceHeaderArg
|
| ReplaceHeaderArg
|
||||||
{ _headerArg :: Arg
|
{ _headerArg :: Arg f
|
||||||
, _headerPattern :: Text
|
, _headerPattern :: Text
|
||||||
} deriving (Eq, Show)
|
}
|
||||||
|
|
||||||
|
deriving instance Eq f => Eq (HeaderArg f)
|
||||||
|
deriving instance Show f => Show (HeaderArg f)
|
||||||
|
|
||||||
makeLenses ''HeaderArg
|
makeLenses ''HeaderArg
|
||||||
|
|
||||||
makePrisms ''HeaderArg
|
makePrisms ''HeaderArg
|
||||||
|
|
||||||
data Url = Url
|
data Url f = Url
|
||||||
{ _path :: Path
|
{ _path :: Path f
|
||||||
, _queryStr :: [QueryArg]
|
, _queryStr :: [QueryArg f]
|
||||||
} deriving (Eq, Show)
|
}
|
||||||
|
|
||||||
defUrl :: Url
|
deriving instance Eq f => Eq (Url f)
|
||||||
|
deriving instance Show f => Show (Url f)
|
||||||
|
|
||||||
|
defUrl :: Url f
|
||||||
defUrl = Url [] []
|
defUrl = Url [] []
|
||||||
|
|
||||||
makeLenses ''Url
|
makeLenses ''Url
|
||||||
|
|
||||||
data Req = Req
|
data Req f = Req
|
||||||
{ _reqUrl :: Url
|
{ _reqUrl :: Url f
|
||||||
, _reqMethod :: HTTP.Method
|
, _reqMethod :: HTTP.Method
|
||||||
, _reqHeaders :: [HeaderArg]
|
, _reqHeaders :: [HeaderArg f]
|
||||||
, _reqBody :: Maybe ForeignType
|
, _reqBody :: Maybe (ForeignType f)
|
||||||
, _reqReturnType :: ForeignType
|
, _reqReturnType :: ForeignType f
|
||||||
, _reqFuncName :: FunctionName
|
, _reqFuncName :: FunctionName
|
||||||
} deriving (Eq, Show)
|
}
|
||||||
|
|
||||||
|
deriving instance Eq f => Eq (Req f)
|
||||||
|
deriving instance Show f => Show (Req f)
|
||||||
|
|
||||||
makeLenses ''Req
|
makeLenses ''Req
|
||||||
|
|
||||||
defReq :: Req
|
defReq :: Req Text
|
||||||
defReq = Req defUrl "GET" [] Nothing (ForeignType "") (FunctionName [])
|
defReq = Req defUrl "GET" [] Nothing (ForeignType "") (FunctionName [])
|
||||||
|
|
||||||
-- | To be used exclusively as a "negative" return type/constraint
|
-- | To be used exclusively as a "negative" return type/constraint
|
||||||
|
@ -161,7 +183,7 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where
|
||||||
-- >
|
-- >
|
||||||
--
|
--
|
||||||
class HasForeignType lang a where
|
class HasForeignType lang a where
|
||||||
typeFor :: Proxy lang -> Proxy a -> ForeignType
|
typeFor :: Proxy lang -> Proxy a -> ForeignType Text
|
||||||
|
|
||||||
data NoTypes
|
data NoTypes
|
||||||
|
|
||||||
|
@ -172,7 +194,7 @@ type HasNoForeignType = HasForeignType NoTypes
|
||||||
|
|
||||||
class HasForeign lang (layout :: *) where
|
class HasForeign lang (layout :: *) where
|
||||||
type Foreign layout :: *
|
type Foreign layout :: *
|
||||||
foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout
|
foreignFor :: Proxy lang -> Proxy layout -> Req Text -> Foreign layout
|
||||||
|
|
||||||
instance (HasForeign lang a, HasForeign lang b)
|
instance (HasForeign lang a, HasForeign lang b)
|
||||||
=> HasForeign lang (a :<|> b) where
|
=> HasForeign lang (a :<|> b) where
|
||||||
|
@ -199,7 +221,7 @@ instance (KnownSymbol sym, HasForeignType lang ftype, HasForeign lang sublayout)
|
||||||
|
|
||||||
instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
|
instance (Elem JSON list, HasForeignType lang a, ReflectMethod method)
|
||||||
=> HasForeign lang (Verb method status list a) where
|
=> HasForeign lang (Verb method status list a) where
|
||||||
type Foreign (Verb method status list a) = Req
|
type Foreign (Verb method status list a) = Req Text
|
||||||
|
|
||||||
foreignFor lang Proxy req =
|
foreignFor lang Proxy req =
|
||||||
req & reqFuncName . _FunctionName %~ (methodLC :)
|
req & reqFuncName . _FunctionName %~ (methodLC :)
|
||||||
|
@ -264,7 +286,7 @@ instance
|
||||||
, _argType = typeFor lang (Proxy :: Proxy Bool) }
|
, _argType = typeFor lang (Proxy :: Proxy Bool) }
|
||||||
|
|
||||||
instance HasForeign lang Raw where
|
instance HasForeign lang Raw where
|
||||||
type Foreign Raw = HTTP.Method -> Req
|
type Foreign Raw = HTTP.Method -> Req Text
|
||||||
|
|
||||||
foreignFor _ Proxy req method =
|
foreignFor _ Proxy req method =
|
||||||
req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
|
req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
|
||||||
|
@ -329,9 +351,9 @@ instance HasForeign lang sublayout
|
||||||
-- the data needed to generate a function for each endpoint
|
-- the data needed to generate a function for each endpoint
|
||||||
-- and hands it all back in a list.
|
-- and hands it all back in a list.
|
||||||
class GenerateList reqs where
|
class GenerateList reqs where
|
||||||
generateList :: reqs -> [Req]
|
generateList :: reqs -> [Req Text]
|
||||||
|
|
||||||
instance GenerateList Req where
|
instance GenerateList (Req Text) where
|
||||||
generateList r = [r]
|
generateList r = [r]
|
||||||
|
|
||||||
instance (GenerateList start, GenerateList rest)
|
instance (GenerateList start, GenerateList rest)
|
||||||
|
@ -344,5 +366,5 @@ listFromAPI
|
||||||
:: (HasForeign lang api, GenerateList (Foreign api))
|
:: (HasForeign lang api, GenerateList (Foreign api))
|
||||||
=> Proxy lang
|
=> Proxy lang
|
||||||
-> Proxy api
|
-> Proxy api
|
||||||
-> [Req]
|
-> [Req Text]
|
||||||
listFromAPI lang p = generateList (foreignFor lang p defReq)
|
listFromAPI lang p = generateList (foreignFor lang p defReq)
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Servant.ForeignSpec where
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import Servant.Foreign
|
import Servant.Foreign
|
||||||
|
import Data.Text (Text(..))
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ type TestApi
|
||||||
:<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] ()
|
:<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] ()
|
||||||
:<|> "test" :> Capture "id" Int :> Delete '[JSON] ()
|
:<|> "test" :> Capture "id" Int :> Delete '[JSON] ()
|
||||||
|
|
||||||
testApi :: [Req]
|
testApi :: [Req Text]
|
||||||
testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy TestApi)
|
testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy TestApi)
|
||||||
|
|
||||||
listFromAPISpec :: Spec
|
listFromAPISpec :: Spec
|
||||||
|
|
|
@ -57,12 +57,12 @@ import qualified Data.Text as T
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Servant.Foreign
|
import Servant.Foreign
|
||||||
|
|
||||||
type AjaxReq = Req
|
type AjaxReq = Req Text
|
||||||
|
|
||||||
-- A 'JavascriptGenerator' just takes the data found in the API type
|
-- A 'JavascriptGenerator' just takes the data found in the API type
|
||||||
-- for each endpoint and generates Javascript code in a Text. Several
|
-- for each endpoint and generates Javascript code in a Text. Several
|
||||||
-- generators are available in this package.
|
-- generators are available in this package.
|
||||||
type JavaScriptGenerator = [Req] -> Text
|
type JavaScriptGenerator = [Req Text] -> Text
|
||||||
|
|
||||||
-- | This structure is used by specific implementations to let you
|
-- | This structure is used by specific implementations to let you
|
||||||
-- customize the output
|
-- customize the output
|
||||||
|
@ -139,7 +139,7 @@ toValidFunctionName t =
|
||||||
, Set.connectorPunctuation
|
, Set.connectorPunctuation
|
||||||
]
|
]
|
||||||
|
|
||||||
toJSHeader :: HeaderArg -> Text
|
toJSHeader :: HeaderArg f -> Text
|
||||||
toJSHeader (HeaderArg n)
|
toJSHeader (HeaderArg n)
|
||||||
= toValidFunctionName ("header" <> n ^. argName . _PathSegment)
|
= toValidFunctionName ("header" <> n ^. argName . _PathSegment)
|
||||||
toJSHeader (ReplaceHeaderArg n p)
|
toJSHeader (ReplaceHeaderArg n p)
|
||||||
|
@ -153,29 +153,29 @@ toJSHeader (ReplaceHeaderArg n p)
|
||||||
pn = "{" <> n ^. argName . _PathSegment <> "}"
|
pn = "{" <> n ^. argName . _PathSegment <> "}"
|
||||||
rp = T.replace pn "" p
|
rp = T.replace pn "" p
|
||||||
|
|
||||||
jsSegments :: [Segment] -> Text
|
jsSegments :: [Segment f] -> Text
|
||||||
jsSegments [] = ""
|
jsSegments [] = ""
|
||||||
jsSegments [x] = "/" <> segmentToStr x False
|
jsSegments [x] = "/" <> segmentToStr x False
|
||||||
jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
|
jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
|
||||||
|
|
||||||
segmentToStr :: Segment -> Bool -> Text
|
segmentToStr :: Segment f -> Bool -> Text
|
||||||
segmentToStr (Segment st) notTheEnd =
|
segmentToStr (Segment st) notTheEnd =
|
||||||
segmentTypeToStr st <> if notTheEnd then "" else "'"
|
segmentTypeToStr st <> if notTheEnd then "" else "'"
|
||||||
|
|
||||||
segmentTypeToStr :: SegmentType -> Text
|
segmentTypeToStr :: SegmentType f -> Text
|
||||||
segmentTypeToStr (Static s) = s ^. _PathSegment
|
segmentTypeToStr (Static s) = s ^. _PathSegment
|
||||||
segmentTypeToStr (Cap s) =
|
segmentTypeToStr (Cap s) =
|
||||||
"' + encodeURIComponent(" <> s ^. argName . _PathSegment <> ") + '"
|
"' + encodeURIComponent(" <> s ^. argName . _PathSegment <> ") + '"
|
||||||
|
|
||||||
jsGParams :: Text -> [QueryArg] -> Text
|
jsGParams :: Text -> [QueryArg f] -> Text
|
||||||
jsGParams _ [] = ""
|
jsGParams _ [] = ""
|
||||||
jsGParams _ [x] = paramToStr x False
|
jsGParams _ [x] = paramToStr x False
|
||||||
jsGParams s (x:xs) = paramToStr x True <> s <> jsGParams s xs
|
jsGParams s (x:xs) = paramToStr x True <> s <> jsGParams s xs
|
||||||
|
|
||||||
jsParams :: [QueryArg] -> Text
|
jsParams :: [QueryArg f] -> Text
|
||||||
jsParams = jsGParams "&"
|
jsParams = jsGParams "&"
|
||||||
|
|
||||||
paramToStr :: QueryArg -> Bool -> Text
|
paramToStr :: QueryArg f -> Bool -> Text
|
||||||
paramToStr qarg notTheEnd =
|
paramToStr qarg notTheEnd =
|
||||||
case qarg ^. queryArgType of
|
case qarg ^. queryArgType of
|
||||||
Normal -> name
|
Normal -> name
|
||||||
|
|
Loading…
Reference in a new issue