servant-foreign 0.15.3 → 0.15.4
raw patch · 5 files changed
+180/−72 lines, 5 filesdep ~basedep ~base-compatdep ~hspec
Dependency ranges changed: base, base-compat, hspec, lens, servant, text
Files
- CHANGELOG.md +12/−0
- servant-foreign.cabal +6/−7
- src/Servant/Foreign.hs +22/−19
- src/Servant/Foreign/Inflections.hs +18/−12
- src/Servant/Foreign/Internal.hs +122/−34
CHANGELOG.md view
@@ -1,6 +1,18 @@ [The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-foreign/CHANGELOG.md) [Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md) +0.15.4+------++### Significant changes++- Documentation improvements.++### Other changes++- Support GHC-9.0.1.+- Bump `lens` and `hspec` dependencies.+ 0.15.3 ------
servant-foreign.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: servant-foreign-version: 0.15.3+version: 0.15.4 synopsis: Helpers for generating clients for servant APIs in any programming language category: Servant, Web@@ -21,7 +21,7 @@ maintainer: haskell-servant-maintainers@googlegroups.com copyright: 2015-2019 Servant Contributors build-type: Simple-tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.2+tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1 extra-source-files: CHANGELOG.md@@ -41,7 +41,7 @@ -- -- note: mtl lower bound is so low because of GHC-7.8 build-depends:- base >= 4.9 && < 4.15+ base >= 4.9 && < 4.16 , text >= 1.2.3.0 && < 1.3 -- Servant dependencies@@ -52,7 +52,7 @@ -- Here can be exceptions if we really need features from the newer versions. build-depends: base-compat >= 0.10.5 && < 0.12- , lens >= 4.17 && < 4.20+ , lens >= 4.17 && < 5.1 , http-types >= 0.12.2 && < 0.13 hs-source-dirs: src@@ -74,8 +74,7 @@ -- Additional dependencies build-depends:- hspec >= 2.6.0 && <2.8-+ hspec >= 2.6.0 && <2.9 build-tool-depends:- hspec-discover:hspec-discover >=2.6.0 && <2.8+ hspec-discover:hspec-discover >=2.6.0 && <2.9 default-language: Haskell2010
src/Servant/Foreign.hs view
@@ -1,20 +1,32 @@ -- | Generalizes all the data needed to make code generation work with -- arbitrary programming languages.+--+-- See documentation of 'HasForeignType' for a simple example. 'listFromAPI' returns a list of all your endpoints and their foreign types, given a mapping from Haskell types to foreign types (conventionally called `ftypes` below). module Servant.Foreign- ( ArgType(..)- , HeaderArg(..)- , QueryArg(..)+ (+ -- * Main API+ listFromAPI , Req(..)- , ReqBodyContentType(..)- , Segment(..)- , SegmentType(..)+ , defReq+ , HasForeignType(..)+ , GenerateList(..)+ , HasForeign(..)+ , NoTypes+ -- * Subtypes of 'Req' , Url(..)- -- aliases , Path+ , Segment(..)+ , SegmentType(..)+ , isCapture+ , captureArg+ , QueryArg(..)+ , ArgType(..)+ , HeaderArg(..) , Arg(..) , FunctionName(..)+ , ReqBodyContentType(..) , PathSegment(..)- -- lenses+ -- * Lenses , argName , argType , argPath@@ -30,7 +42,7 @@ , queryArgName , queryArgType , headerArg- -- prisms+ -- * Prisms , _PathSegment , _HeaderArg , _ReplaceHeaderArg@@ -39,16 +51,7 @@ , _Normal , _Flag , _List- -- rest of it- , HasForeign(..)- , HasForeignType(..)- , GenerateList(..)- , NoTypes- , captureArg- , isCapture- , defReq- , listFromAPI- -- re-exports+ -- * Re-exports , module Servant.API , module Servant.Foreign.Inflections ) where
src/Servant/Foreign/Inflections.hs view
@@ -20,20 +20,31 @@ (head, tail) import Servant.Foreign.Internal +-- | Simply concat each part of the FunctionName together.+--+-- @[ "get", "documents", "by", "id" ] → "getdocumentsbyid"@+concatCase :: FunctionName -> Text+concatCase = view concatCaseL+ concatCaseL :: Getter FunctionName Text concatCaseL = _FunctionName . to mconcat --- | Function name builder that simply concat each part together-concatCase :: FunctionName -> Text-concatCase = view concatCaseL+-- | Use the snake_case convention.+-- Each part is separated by a single underscore character.+--+-- @[ "get", "documents", "by", "id" ] → "get_documents_by_id"@+snakeCase :: FunctionName -> Text+snakeCase = view snakeCaseL snakeCaseL :: Getter FunctionName Text snakeCaseL = _FunctionName . to (intercalate "_") --- | Function name builder using the snake_case convention.--- each part is separated by a single underscore character.-snakeCase :: FunctionName -> Text-snakeCase = view snakeCaseL+-- | Use the camelCase convention.+-- The first part is lower case, every other part starts with an upper case character.+--+-- @[ "get", "documents", "by", "id" ] → "getDocumentsById"@+camelCase :: FunctionName -> Text+camelCase = view camelCaseL camelCaseL :: Getter FunctionName Text camelCaseL = _FunctionName . to convert@@ -42,8 +53,3 @@ convert (p:ps) = mconcat $ p : map capitalize ps capitalize "" = "" capitalize name = C.toUpper (head name) `cons` tail name---- | Function name builder using the CamelCase convention.--- each part begins with an upper case character.-camelCase :: FunctionName -> Text-camelCase = view camelCaseL
src/Servant/Foreign/Internal.hs view
@@ -13,8 +13,6 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} --- | Generalizes all the data needed to make code generation work with--- arbitrary programming languages. module Servant.Foreign.Internal where import Prelude ()@@ -40,55 +38,75 @@ (RequiredArgument) import Servant.API.TypeLevel +-- | Canonical name of the endpoint, can be used to generate a function name.+--+-- You can use the functions in "Servant.Foreign.Inflections", like 'Servant.Foreign.Inflections.camelCase' to transform to `Text`. newtype FunctionName = FunctionName { unFunctionName :: [Text] } deriving (Data, Show, Eq, Semigroup, Monoid, Typeable) makePrisms ''FunctionName +-- | See documentation of 'Arg' newtype PathSegment = PathSegment { unPathSegment :: Text } deriving (Data, Show, Eq, IsString, Semigroup, Monoid, Typeable) makePrisms ''PathSegment -data Arg f = Arg+-- | Maps a name to the foreign type that belongs to the annotated value.+--+-- Used for header args, query args, and capture args.+data Arg ftype = Arg { _argName :: PathSegment- , _argType :: f }+ -- ^ The name to be captured.+ --+ -- Only for capture args it really denotes a path segment.+ , _argType :: ftype+ -- ^ Foreign type the associated value will have+ } deriving (Data, Eq, Show, Typeable) makeLenses ''Arg -argPath :: Getter (Arg f) Text+argPath :: Getter (Arg ftype) Text argPath = argName . _PathSegment -data SegmentType f+data SegmentType ftype = Static PathSegment- -- ^ a static path segment. like "/foo"- | Cap (Arg f)- -- ^ a capture. like "/:userid"+ -- ^ Static path segment.+ --+ -- @"foo\/bar\/baz"@+ --+ -- contains the static segments @"foo"@, @"bar"@ and @"baz"@.+ | Cap (Arg ftype)+ -- ^ A capture.+ --+ -- @"user\/{userid}\/name"@+ --+ -- would capture the arg @userid@ with type @ftype@. deriving (Data, Eq, Show, Typeable) makePrisms ''SegmentType -newtype Segment f = Segment { unSegment :: SegmentType f }+-- | A part of the Url’s path.+newtype Segment ftype = Segment { unSegment :: SegmentType ftype } deriving (Data, Eq, Show, Typeable) makePrisms ''Segment -isCapture :: Segment f -> Bool+-- | Whether a segment is a 'Cap'.+isCapture :: Segment ftype -> Bool isCapture (Segment (Cap _)) = True isCapture _ = False -captureArg :: Segment f -> Arg f+-- | Crashing Arg extraction from segment, TODO: remove+captureArg :: Segment ftype -> Arg ftype captureArg (Segment (Cap s)) = s captureArg _ = error "captureArg called on non capture" -type Path f = [Segment f]--newtype Frag f = Frag { unFragment :: Arg f }- deriving (Data, Eq, Show, Typeable)--makePrisms ''Frag+-- TODO: remove, unnecessary indirection+type Path ftype = [Segment ftype] +-- | Type of a 'QueryArg'. data ArgType = Normal | Flag@@ -97,18 +115,41 @@ makePrisms ''ArgType -data QueryArg f = QueryArg- { _queryArgName :: Arg f+-- | Url Query argument.+--+-- Urls can contain query arguments, which is a list of key-value pairs.+-- In a typical url, query arguments look like this:+--+-- @?foo=bar&alist[]=el1&alist[]=el2&aflag@+--+-- Each pair can be+--+-- * @?foo=bar@: a plain key-val pair, either optional or required ('QueryParam')+-- * @?aflag@: a flag (no value, implicitly Bool with default `false` if it’s missing) ('QueryFlag')+-- * @?alist[]=el1&alist[]=el2@: list of values ('QueryParams')+--+-- @_queryArgType@ will be set accordingly.+--+-- For the plain key-val pairs ('QueryParam'), @_queryArgName@’s @ftype@ will be wrapped in a @Maybe@ if the argument is optional.+data QueryArg ftype = QueryArg+ { _queryArgName :: Arg ftype+ -- ^ Name and foreign type of the argument. Will be wrapped in `Maybe` if the query is optional and in a `[]` if the query is a list , _queryArgType :: ArgType+ -- ^ one of normal/plain, list or flag } deriving (Data, Eq, Show, Typeable) makeLenses ''QueryArg -data HeaderArg f = HeaderArg- { _headerArg :: Arg f }+data HeaderArg ftype =+ -- | The name of the header and the foreign type of its value.+ HeaderArg+ { _headerArg :: Arg ftype }+ -- | Unused, will never be set.+ --+ -- TODO: remove | ReplaceHeaderArg- { _headerArg :: Arg f+ { _headerArg :: Arg ftype , _headerPattern :: Text } deriving (Data, Eq, Show, Typeable)@@ -117,29 +158,71 @@ makePrisms ''HeaderArg -data Url f = Url- { _path :: Path f- , _queryStr :: [QueryArg f]- , _frag :: Maybe f+-- | Full endpoint url, with all captures and parameters+data Url ftype = Url+ { _path :: Path ftype+ -- ^ Url path, list of either static segments or captures+ --+ -- @"foo\/{id}\/bar"@+ , _queryStr :: [QueryArg ftype]+ -- ^ List of query args+ --+ -- @"?foo=bar&a=b"@+ , _frag :: Maybe ftype+ -- ^ Url fragment.+ --+ -- Not sent to the HTTP server, so only useful for frontend matters (e.g. inter-page linking).+ --+ -- @#fragmentText@ } deriving (Data, Eq, Show, Typeable) -defUrl :: Url f+defUrl :: Url ftype defUrl = Url [] [] Nothing makeLenses ''Url +-- | See documentation of '_reqBodyContentType' data ReqBodyContentType = ReqBodyJSON | ReqBodyMultipart deriving (Data, Eq, Show, Read) -data Req f = Req- { _reqUrl :: Url f+-- | Full description of an endpoint in your API, generated by 'listFromAPI'. It should give you all the information needed to generate foreign language bindings.+--+-- Every field containing @ftype@ will use the foreign type mapping specified via 'HasForeignType' (see its docstring on how to set that up).+--+-- See https://docs.servant.dev/en/stable/tutorial/ApiType.html for accessible documentation of the possible content of an endpoint.+data Req ftype = Req+ { _reqUrl :: Url ftype+ -- ^ Full list of URL segments, including captures , _reqMethod :: HTTP.Method- , _reqHeaders :: [HeaderArg f]- , _reqBody :: Maybe f- , _reqReturnType :: Maybe f+ -- ^ @\"GET\"@\/@\"POST\"@\/@\"PUT\"@\/…+ , _reqHeaders :: [HeaderArg ftype]+ -- ^ Headers required by this endpoint, with their type+ , _reqBody :: Maybe ftype+ -- ^ Foreign type of the expected request body ('ReqBody'), if any+ , _reqReturnType :: Maybe ftype+ -- ^ The foreign type of the response, if any , _reqFuncName :: FunctionName+ -- ^ The URL segments rendered in a way that they can be easily concatenated into a canonical function name , _reqBodyContentType :: ReqBodyContentType+ -- ^ The content type the request body is transferred as.+ --+ -- This is a severe limitation of @servant-foreign@ currently,+ -- as we only allow the content type to be `JSON`+ -- no user-defined content types. ('ReqBodyMultipart' is not+ -- actually implemented.)+ --+ -- Thus, any routes looking like this will work:+ --+ -- @"foo" :> Get '[JSON] Foo@+ --+ -- while routes like+ --+ -- @"foo" :> Get '[MyFancyContentType] Foo@+ --+ -- will fail with an error like+ --+ -- @• JSON expected in list '[MyFancyContentType]@ } deriving (Data, Eq, Show, Typeable) @@ -183,11 +266,16 @@ class HasForeignType lang ftype a where typeFor :: Proxy lang -> Proxy ftype -> Proxy a -> ftype +-- | The language definition without any foreign types. It can be used for dynamic languages which do not /do/ type annotations. data NoTypes -instance HasForeignType NoTypes NoContent ftype where+-- | Use if the foreign language does not have any types.+instance HasForeignType NoTypes NoContent a where typeFor _ _ _ = NoContent +-- | Implementation of the Servant framework types.+--+-- Relevant instances: Everything containing 'HasForeignType'. class HasForeign lang ftype (api :: *) where type Foreign ftype api :: * foreignFor :: Proxy lang -> Proxy ftype -> Proxy api -> Req ftype -> Foreign ftype api