packages feed

servant-queryparam-core 1.0.0 → 2.0.0

raw patch · 6 files changed

+501/−304 lines, 6 files

Files

+ README.md view
@@ -0,0 +1,192 @@+# servant-queryparam++Provides several libraries that let you use records to specify query parameters in [servant](https://hackage.haskell.org/package/servant) APIs.++These libraries are:++- [servant-queryparam-core](https://github.com/deemp/servant-queryparam/blob/main/servant-queryparam-core)+- [servant-queryparam-server](https://github.com/deemp/servant-queryparam/blob/main/servant-queryparam-server)+- [servant-queryparam-client](https://github.com/deemp/servant-queryparam/blob/main/servant-queryparam-client)+- [servant-queryparam-openapi3](https://github.com/deemp/servant-queryparam/blob/main/servant-queryparam-openapi3)++The following example was taken from [here](https://github.com/deemp/servant-queryparam/tree/main/servant-queryparam-example).++## Example++This example demonstrates servant APIs with query parameters.+There's an OpenAPI specification for each of them.++First, I enable compiler extensions.++```haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+```++Next, I import the necessary modules.++```haskell+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (ToJSON)+import Data.ByteString.Lazy.Char8 qualified as BSL8+import Data.Data (Proxy (..), Typeable)+import Data.Maybe (maybeToList)+import Data.OpenApi (OpenApi, ToParamSchema, ToSchema)+import Data.OpenApi.Internal.Utils (encodePretty)+import GHC.Generics (Generic)+import GHC.TypeLits (Symbol)+import Network.Wai.Handler.Warp qualified as Warp+import Servant (Application, Context (..), GenericMode ((:-)), Get, Handler (..), JSON, NamedRoutes, ServerError, (:>))+import Servant.OpenApi (HasOpenApi (..))+import Servant.OpenApi.Record ()+import Servant.Record (RecordParam)+import Servant.Server.Generic (genericServe, genericServeTWithContext)+import Servant.Server.Record ()+import Servant.TypeLevel (DropPrefix, Eval, Exp)+```++I define a label for dropping the prefix of a `Symbol`.++```haskell+data DropPrefixExp :: Symbol -> Exp Symbol+```++Next, I provide an interpreter for that wrapper.++```haskell+type instance Eval (DropPrefixExp sym) = DropPrefix sym+```++`DropPrefix` drops the leading `'_'` characters, then the non-`'_'` characters, then the `'_'` characters.++```haskell+-- >>> :kind! DropPrefix "_params_user"+-- DropPrefix "_params_user" :: Symbol+-- = "user"+```++Then, I define a label for keeping the prefix of a `Symbol`.++```haskell+data KeepPrefixExp :: Symbol -> Exp Symbol+```++The interpreter of this label does nothing to a `Symbol`.++```haskell+type instance Eval (KeepPrefixExp sym) = sym+```++Now, I write a record.+I'll use this record to produce query parameters.++```haskell+data Params = Params+  { _params_user :: Maybe String+  , _params_users :: [String]+  , _params_oneUser :: String+  , _params_userFlag :: Bool+  }+  deriving (Show, Generic, Typeable, ToJSON, ToSchema)+```++This is our first API.+In this API, the query parameter names are the record field names with dropped prefixes.+E.g., the query parameter `user` corresponds to the field name `_params_user`.++```haskell+newtype UserAPI1 routes = UserAPI1+  { get :: routes :- "get" :> RecordParam DropPrefixExp Params :> Get '[JSON] [String]+  }+  deriving (Generic)+```++This is our second API.+In this API, query parameter names are the record field names.+E.g., the query parameter `_params_user` corresponds to the field name `_params_user`.++```haskell+newtype UserAPI2 routes = UserAPI2+  { get :: routes :- "get" :> RecordParam KeepPrefixExp Params :> Get '[JSON] [String]+  }+  deriving (Generic)+```++Now, I define an OpenAPI specification for the first API.++```haskell+specDrop :: OpenApi+specDrop = toOpenApi (Proxy :: Proxy (NamedRoutes UserAPI1))+```++Then, I define an OpenAPI specification for the second API.++```haskell+specKeep :: OpenApi+specKeep = toOpenApi (Proxy :: Proxy (NamedRoutes UserAPI2))+```++Following that, I define an `Application`.+At the `/get` endpoint, the application returns a list of query parameter values.++```haskell+server :: Application+server =+  genericServeTWithContext+    (\x -> Servant.Handler (ExceptT $ Right <$> liftIO x))+    UserAPI1+      { get = \Params{..} ->+          pure $+            maybeToList _params_user+              <> _params_users+              <> [_params_oneUser, show _params_userFlag]+      }+    EmptyContext+```++Finally, I combine all parts.++First, the application will print the OpenAPI specifications in JSON format. Next, it will start a server.++```haskell+main :: IO ()+main = do+  putStrLn "\n---\nQuery parameters without prefixes\n---\n"+  BSL8.putStrLn $ encodePretty specDrop+  putStrLn "\n---\nQuery parameters with prefixes\n---\n"+  BSL8.putStrLn $ encodePretty specKeep++  putStrLn "\n---\nStarting the server...\n---\n"+  putStrLn "\nTry running\n"+  putStrLn "curl -v \"localhost:8080/get?user=1&users=2&users=3&oneUser=4&userFlag=true\""+  Warp.run 8080 server+```++## Run++Run the server.++```hs+cabal run example+```++## Test++When the server is up, request the `/get` endpoint via `curl`.++```console+curl -v "localhost:8080/get?user=1&users=2&users=3&oneUser=4&userFlag=true"+```++You should receive the values of query parameters in a list.++```console+...+["1","2","3","4","True"]+```
servant-queryparam-core.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name:          servant-queryparam-core synopsis:      Use records for query parameters in servant APIs.-version:       1.0.0+version:       2.0.0 maintainer:    Danila Danko <https://github.com/deemp> author:        Kristof Bastiaensen copyright:     Kristof Bastiaensen 2020@@ -11,8 +11,13 @@ description:   Having positional parameters in @servant@ can be error-prone, especially when there are a lot of them and they have similar types.   This package solves that problem by letting one use records to specify query parameters in @servant@ APIs.-  Use [servant-queryparam-server](https://hackage.haskell.org/package/servant-queryparam-server) for servers and [servant-queryparam-client](https://hackage.haskell.org/package/servant-queryparam-client) for clients.+  Use [servant-queryparam-server](https://hackage.haskell.org/package/servant-queryparam-server) for servers, [servant-queryparam-client](https://hackage.haskell.org/package/servant-queryparam-client) for clients,+  [servant-queryparam-openapi3](https://hackage.haskell.org/package/servant-queryparam-openapi3) for [openapi3](https://hackage.haskell.org/package/openapi3).+  See the [README](https://github.com/deemp/servant-queryparam/tree/main/servant-queryparam-core#readme) for more information. +extra-source-files:+  README.md+ source-repository head   type:     git   location: https://github.com/deemp/servant-queryparam@@ -21,8 +26,8 @@   default-language: GHC2021   ghc-options:      -Wall   exposed-modules:-    Servant.Record-    Servant.TypeLevel+    Servant.QueryParam.Record+    Servant.QueryParam.TypeLevel    hs-source-dirs:   src   build-depends:
+ src/Servant/QueryParam/Record.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++-- | This module provides functions and instances for working with query parameter records.+module Servant.QueryParam.Record (RecordParam, UnRecordParam) where++import Data.Kind+import Data.Proxy+import GHC.Generics+import GHC.TypeLits+import Servant.API+import Servant.QueryParam.TypeLevel++-- | 'RecordParam' uses fields in a record to represent query parameters.+--+-- For each record field:+--+-- - The modified record field name becomes a query parameter name.+-- - The record field type becomes the query parameter type.+--+-- For example, this API:+--+-- @+-- type API = "users" :> (QueryParam "category" Category :>+--                        QueryParam' '[Required, Strict] "sort_by" SortBy :>+--                        QueryFlag "with_schema" :>+--                        QueryParams "filters" Filter :>+--                        Get '[JSON] User)+-- @+--+-- can be written using records:+--+-- @+-- data DropPrefixExp :: sym -> 'Exp' sym+--+-- type instance 'Eval' (DropPrefixExp sym) = 'DropPrefix' sym+--+-- data UserParams = UserParams+--   { _userParams_category :: Maybe Category+--   , _userParams_sort_by :: SortBy+--   , _userParams_with_schema :: Bool+--   , _userParams_filters :: [Filter]+--   }+--+-- type API = "users" :> 'RecordParam' DropPrefixExp UserParams :> Get '[JSON] User+-- @+--+-- Here, @DropPrefixExp@ wraps a @sym@ into @Exp@.+--+-- The instance of 'Eval' for @DropPrefixExp sym@ drops the prefix of that @sym@ via 'DropPrefix'.+--+-- 'DropPrefix' is applied to the fields of @UserParams@.+--+-- The @"_userParams_category"@ record field corresponds to the query parameter @"category"@.+data RecordParam (mod :: Symbol -> Exp Symbol) (a :: Type)++-- | Append an element to a servant API+type family ServantAppend x y where+  ServantAppend (a :> b) c = a :> ServantAppend b c+  ServantAppend a c = a :> c++-- | Type family for rewriting a 'RecordParam' API to a regular @servant@ API.+-- This family is useful for defining instances of classes that extract information from the API type,+-- such as classes from @servant-swagger@ or @servant-foreign@.+--+-- Typical use:+--+-- @+-- instance SomeClass (UnRecordParam (RecordParam mod a :> api))) =>+--          SomeClass (RecordParam mod a :> api) where+--    someMethod _ = someMethod (Proxy :: Proxy (UnRecordParam (RecordParam mod a :> api))+-- @+type family UnRecordParam (mod :: Symbol -> Exp Symbol) (x :: Type) :: Type where+  UnRecordParam mod (a :> b) = ServantAppend (UnRecordParam mod a) b+  UnRecordParam mod (RecordParam mod a) = UnRecordParam mod (Rep a ())+  UnRecordParam mod (D1 m c d) = UnRecordParam mod (c d)+  UnRecordParam mod ((a :*: b) d) =+    ServantAppend+      (UnRecordParam mod (a d))+      (UnRecordParam mod (b d))+  UnRecordParam mod (C1 m a d) = UnRecordParam mod (a d)+  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 Bool) d) =+    QueryFlag (Eval (mod sym))+  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 [a]) d) =+    QueryParams (Eval (mod sym)) a+  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 (Maybe a)) d) =+    QueryParam' [Optional, Strict] (Eval (mod sym)) a+  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 a) d) =+    QueryParam' [Required, Strict] (Eval (mod sym)) a++instance (Generic a, GHasLink mod (Rep a) sub) => HasLink (RecordParam mod a :> sub) where+  type MkLink (RecordParam mod a :> sub) b = a -> MkLink sub b+  toLink toA _ l record = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l (from record :: Rep a ())++data GParam (mod :: Symbol -> Exp Symbol) a++instance GHasLink mod a sub => HasLink (GParam mod (a ()) :> sub) where+  type MkLink (GParam mod (a ()) :> sub) b = a () -> MkLink sub b+  toLink toA _ = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub)+  {-# INLINE toLink #-}++class HasLink sub => GHasLink (mod :: Symbol -> Exp Symbol) (a :: Type -> Type) sub where+  gToLink :: Proxy mod -> (Link -> b) -> Proxy sub -> Link -> a () -> MkLink sub b++instance GHasLink mod c sub => GHasLink mod (D1 m c) sub where+  gToLink _ toA _ l (M1 x) = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l x+  {-# INLINE gToLink #-}++instance+  ( HasLink sub+  , GHasLink mod a (GParam mod (b ()) :> sub)+  ) =>+  GHasLink mod (a :*: b) sub+  where+  gToLink _ toA _ l (a :*: b) =+    gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy (GParam mod (b ()) :> sub)) l a b+  {-# INLINE gToLink #-}++instance+  ( GHasLink mod a sub+  , HasLink sub+  ) =>+  GHasLink mod (C1 m a) sub+  where+  gToLink _ toA _ l (M1 x) = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l x+  {-# INLINE gToLink #-}++instance+  {-# OVERLAPPING #-}+  ( KnownSymbol sym+  , KnownSymbol (Eval (mod sym))+  , HasLink (sub :: Type)+  ) =>+  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 Bool)) sub+  where+  gToLink _ toA _ l (M1 (K1 x)) =+    toLink toA (Proxy :: Proxy (QueryFlag (Eval (mod sym)) :> sub)) l x+  {-# INLINE gToLink #-}++instance+  {-# OVERLAPPING #-}+  ( KnownSymbol sym+  , KnownSymbol (Eval (mod sym))+  , ToHttpApiData a+  , HasLink (a :> sub)+  , HasLink sub+  ) =>+  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 [a])) sub+  where+  gToLink _ toA _ l (M1 (K1 x)) =+    toLink toA (Proxy :: Proxy (QueryParams (Eval (mod sym)) a :> sub)) l x+  {-# INLINE gToLink #-}++instance+  {-# OVERLAPPING #-}+  ( KnownSymbol sym+  , KnownSymbol (Eval (mod sym))+  , ToHttpApiData a+  , HasLink (a :> sub)+  , HasLink sub+  ) =>+  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 (Maybe a))) sub+  where+  gToLink _ toA _ l (M1 (K1 x)) =+    toLink toA (Proxy :: Proxy (QueryParam' '[Optional, Strict] (Eval (mod sym)) a :> sub)) l x+  {-# INLINE gToLink #-}++instance+  {-# OVERLAPPABLE #-}+  ( KnownSymbol sym+  , KnownSymbol (Eval (mod sym))+  , ToHttpApiData a+  , HasLink (a :> sub)+  , HasLink sub+  ) =>+  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 a)) sub+  where+  gToLink _ toA _ l (M1 (K1 x)) =+    toLink toA (Proxy :: Proxy (QueryParam' '[Required, Strict] (Eval (mod sym)) a :> sub)) l x+  {-# INLINE gToLink #-}
+ src/Servant/QueryParam/TypeLevel.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-}+-- |+-- This module provides functions to modify 'Symbol's and type-level lists.+--+-- The workflow to define a modification function can be as follows.+--+-- 1. Use 'ToList' to convert a 'Symbol' to a type-level list of 'Char's.+--+-- 1. Use functions from here and from the package [first-class-families](https://hackage.haskell.org/package/first-class-families) for type-level lists (@Fcf.Data.List@).+--+-- 1. Use 'FromList' to convert a type-level list of 'Char's back to a 'Symbol'.+--+-- Example (see 'DropPrefix'):+--+-- >>> :kind! DropPrefix "_userParams_category"+-- DropPrefix "_userParams_category" :: Symbol+-- = "category"+--+-- Modules that use the 'Eval' type family (e.g., "Servant.Record") must be imported together with modules that export instances of 'Eval'+-- (see the @GHC@ documentation on Type families).+module Servant.QueryParam.TypeLevel+  ( -- * Re-exports from @first-class-families@+    Eval,+    Exp,++    -- * List functions+    FromList,+    FromList1,+    ToList,+    ToList1,++    -- * Comparison functions+    TyEq,+    NotTyEq,++    -- * Examples+    DropUnderscores,+    DropNonUnderscores,+    DropPrefix,+  )+where++import Fcf+import Fcf.Data.List+import GHC.TypeLits++-- | Convert a 'Symbol' to a list of 'Char's.+type family ToList (sym :: Symbol) :: [Char] where+  ToList sym = ToList1 (UnconsSymbol sym)++-- | Convert a possibly unconsed 'Symbol' to a list of 'Char's.+type family ToList1 (sym :: Maybe (Char, Symbol)) :: [Char] where+  ToList1 'Nothing = '[]+  ToList1 ('Just '(c, sym)) = c : ToList1 (UnconsSymbol sym)++-- | Convert a list of 'Char's to a 'Symbol'.+--+-- >>> :kind! FromList ['a', '+', 'c']+-- FromList ['a', '+', 'c'] :: Symbol+-- = "a+c"+type family FromList (cs :: [Char]) :: Symbol where+  FromList cs = FromList1 (Eval (Reverse cs)) ""++-- | Convert a list of 'Char's to a 'Symbol'.+--+-- In this list, 'Chars' go in a reverse order.+--+-- >>> :kind! FromList1 ['a', 'b', 'c'] ""+-- FromList1 ['a', 'b', 'c'] "" :: Symbol+-- = "cba"+type family FromList1 (syms :: [Char]) (sym :: Symbol) :: Symbol where+  FromList1 '[] s = s+  FromList1 (x : xs) s = FromList1 xs (ConsSymbol x s)++-- $ lists++-- | Type inequality+data NotTyEq :: a -> b -> Exp Bool++type instance Eval (NotTyEq a b) = NotTyEqImpl a b++-- | Check types aren't equal+type family NotTyEqImpl (a :: k) (b :: k) :: Bool where+  NotTyEqImpl a a = 'False+  NotTyEqImpl a b = 'True++-- $ examples++-- | Drop leading underscores.+--+-- >>> :kind! Eval (DropUnderscores ['_', '_', 'a'])+-- Eval (DropUnderscores ['_', '_', 'a']) :: [Char]+-- = '['a']+type DropUnderscores = DropWhile (TyEq '_')++-- | Drop leading non-underscores.+--+-- >>> :kind! Eval (DropNonUnderscores ['a', 'a', '_'])+-- Eval (DropNonUnderscores ['a', 'a', '_']) :: [Char]+-- = '['_']+type DropNonUnderscores = DropWhile (NotTyEq '_')++-- | Drop the prefix of a 'Symbol'.+--+-- >>> :kind! DropPrefix "_userParams_category"+-- DropPrefix "_userParams_category" :: Symbol+-- = "category"+type family DropPrefix (sym :: Symbol) :: Symbol where+  DropPrefix sym = FromList (Eval (DropUnderscores (Eval (DropNonUnderscores (Eval (DropUnderscores (ToList sym)))))))
− src/Servant/Record.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}---- | This module provides functions and instances for working with query parameter records.-module Servant.Record (RecordParam, UnRecordParam) where--import Data.Kind-import Data.Proxy-import GHC.Generics-import GHC.TypeLits-import Servant.API-import Servant.TypeLevel---- | 'RecordParam' uses fields in a record to represent query parameters.------ For each record field:------ - The modified record field name becomes a query parameter name.--- - The record field type becomes the query parameter type.------ For example, this API:------ @--- type API = "users" :> (QueryParam "category" Category :>---                        QueryParam' '[Required, Strict] "sort_by" SortBy :>---                        QueryFlag "with_schema" :>---                        QueryParams "filters" Filter :>---                        Get '[JSON] User)--- @------ can be written using records:------ @--- data DropPrefixExp :: sym -> 'Exp' sym------ type instance 'Eval' (DropPrefixExp sym) = 'DropPrefix' sym------ data UserParams = UserParams---   { _userParams_category :: Maybe Category---   , _userParams_sort_by :: SortBy---   , _userParams_with_schema :: Bool---   , _userParams_filters :: [Filter]---   }------ type API = "users" :> 'RecordParam' DropPrefixExp UserParams :> Get '[JSON] User--- @------ Here, @DropPrefixExp@ wraps a @sym@ into @Exp@.------ The instance of 'Eval' for @DropPrefixExp sym@ drops the prefix of that @sym@ via 'DropPrefix'.------ 'DropPrefix' is applied to the fields of @UserParams@.------ The @"_userParams_category"@ record field corresponds to the query parameter @"category"@.-data RecordParam (mod :: Symbol -> Exp Symbol) (a :: Type)---- | Append an element to a servant API-type family ServantAppend x y where-  ServantAppend (a :> b) c = a :> ServantAppend b c-  ServantAppend a c = a :> c---- | Type family for rewriting a 'RecordParam' API to a regular @servant@ API.--- This family is useful for defining instances of classes that extract information from the API type,--- such as classes from @servant-swagger@ or @servant-foreign@.------ Typical use:------ @--- instance SomeClass (UnRecordParam (RecordParam mod a :> api))) =>---          SomeClass (RecordParam mod a :> api) where---    someMethod _ = someMethod (Proxy :: Proxy (UnRecordParam (RecordParam mod a :> api))--- @-type family UnRecordParam (mod :: Symbol -> Exp Symbol) (x :: Type) :: Type where-  UnRecordParam mod (a :> b) = ServantAppend (UnRecordParam mod a) b-  UnRecordParam mod (RecordParam mod a) = UnRecordParam mod (Rep a ())-  UnRecordParam mod (D1 m c d) = UnRecordParam mod (c d)-  UnRecordParam mod ((a :*: b) d) =-    ServantAppend-      (UnRecordParam mod (a d))-      (UnRecordParam mod (b d))-  UnRecordParam mod (C1 m a d) = UnRecordParam mod (a d)-  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 Bool) d) =-    QueryFlag (Eval (mod sym))-  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 [a]) d) =-    QueryParams (Eval (mod sym)) a-  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 (Maybe a)) d) =-    QueryParam' [Optional, Strict] (Eval (mod sym)) a-  UnRecordParam mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 a) d) =-    QueryParam' [Required, Strict] (Eval (mod sym)) a--instance (Generic a, GHasLink mod (Rep a) sub) => HasLink (RecordParam mod a :> sub) where-  type MkLink (RecordParam mod a :> sub) b = a -> MkLink sub b-  toLink toA _ l record = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l (from record :: Rep a ())--data GParam (mod :: Symbol -> Exp Symbol) a--instance GHasLink mod a sub => HasLink (GParam mod (a ()) :> sub) where-  type MkLink (GParam mod (a ()) :> sub) b = a () -> MkLink sub b-  toLink toA _ = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub)-  {-# INLINE toLink #-}--class HasLink sub => GHasLink (mod :: Symbol -> Exp Symbol) (a :: Type -> Type) sub where-  gToLink :: Proxy mod -> (Link -> b) -> Proxy sub -> Link -> a () -> MkLink sub b--instance GHasLink mod c sub => GHasLink mod (D1 m c) sub where-  gToLink _ toA _ l (M1 x) = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l x-  {-# INLINE gToLink #-}--instance-  ( HasLink sub-  , GHasLink mod a (GParam mod (b ()) :> sub)-  ) =>-  GHasLink mod (a :*: b) sub-  where-  gToLink _ toA _ l (a :*: b) =-    gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy (GParam mod (b ()) :> sub)) l a b-  {-# INLINE gToLink #-}--instance-  ( GHasLink mod a sub-  , HasLink sub-  ) =>-  GHasLink mod (C1 m a) sub-  where-  gToLink _ toA _ l (M1 x) = gToLink (Proxy :: Proxy mod) toA (Proxy :: Proxy sub) l x-  {-# INLINE gToLink #-}--instance-  {-# OVERLAPPING #-}-  ( KnownSymbol sym-  , KnownSymbol (Eval (mod sym))-  , HasLink (sub :: Type)-  ) =>-  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 Bool)) sub-  where-  gToLink _ toA _ l (M1 (K1 x)) =-    toLink toA (Proxy :: Proxy (QueryFlag (Eval (mod sym)) :> sub)) l x-  {-# INLINE gToLink #-}--instance-  {-# OVERLAPPING #-}-  ( KnownSymbol sym-  , KnownSymbol (Eval (mod sym))-  , ToHttpApiData a-  , HasLink (a :> sub)-  , HasLink sub-  ) =>-  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 [a])) sub-  where-  gToLink _ toA _ l (M1 (K1 x)) =-    toLink toA (Proxy :: Proxy (QueryParams (Eval (mod sym)) a :> sub)) l x-  {-# INLINE gToLink #-}--instance-  {-# OVERLAPPING #-}-  ( KnownSymbol sym-  , KnownSymbol (Eval (mod sym))-  , ToHttpApiData a-  , HasLink (a :> sub)-  , HasLink sub-  ) =>-  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 (Maybe a))) sub-  where-  gToLink _ toA _ l (M1 (K1 x)) =-    toLink toA (Proxy :: Proxy (QueryParam' '[Optional, Strict] (Eval (mod sym)) a :> sub)) l x-  {-# INLINE gToLink #-}--instance-  {-# OVERLAPPABLE #-}-  ( KnownSymbol sym-  , KnownSymbol (Eval (mod sym))-  , ToHttpApiData a-  , HasLink (a :> sub)-  , HasLink sub-  ) =>-  GHasLink mod (S1 ('MetaSel ('Just sym) d1 d2 d3) (Rec0 a)) sub-  where-  gToLink _ toA _ l (M1 (K1 x)) =-    toLink toA (Proxy :: Proxy (QueryParam' '[Required, Strict] (Eval (mod sym)) a :> sub)) l x-  {-# INLINE gToLink #-}
− src/Servant/TypeLevel.hs
@@ -1,113 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE PolyKinds #-}--- |--- This module provides functions to modify 'Symbol's and type-level lists.------ The workflow to define a modification function can be as follows.------ 1. Use 'ToList' to convert a 'Symbol' to a type-level list of 'Char's.------ 1. Use functions from here and from the package [first-class-families](https://hackage.haskell.org/package/first-class-families) for type-level lists (@Fcf.Data.List@).------ 1. Use 'FromList' to convert a type-level list of 'Char's back to a 'Symbol'.------ Example (see 'DropPrefix'):------ >>> :kind! DropPrefix "_userParams_category"--- DropPrefix "_userParams_category" :: Symbol--- = "category"------ Modules that use the 'Eval' type family (e.g., "Servant.Record") must be imported together with modules that export instances of 'Eval'--- (see the @GHC@ documentation on Type families).-module Servant.TypeLevel-  ( -- * Re-exports from @first-class-families@-    Eval,-    Exp,--    -- * List functions-    FromList,-    FromList1,-    ToList,-    ToList1,--    -- * Comparison functions-    TyEq,-    NotTyEq,--    -- * Examples-    DropUnderscores,-    DropNonUnderscores,-    DropPrefix,-  )-where--import Fcf-import Fcf.Data.List-import GHC.TypeLits---- | Convert a 'Symbol' to a list of 'Char's.-type family ToList (sym :: Symbol) :: [Char] where-  ToList sym = ToList1 (UnconsSymbol sym)---- | Convert a possibly unconsed 'Symbol' to a list of 'Char's.-type family ToList1 (sym :: Maybe (Char, Symbol)) :: [Char] where-  ToList1 'Nothing = '[]-  ToList1 ('Just '(c, sym)) = c : ToList1 (UnconsSymbol sym)---- | Convert a list of 'Char's to a 'Symbol'.------ >>> :kind! FromList ['a', '+', 'c']--- FromList ['a', '+', 'c'] :: Symbol--- = "a+c"-type family FromList (cs :: [Char]) :: Symbol where-  FromList cs = FromList1 (Eval (Reverse cs)) ""---- | Convert a list of 'Char's to a 'Symbol'.------ In this list, 'Chars' go in a reverse order.------ >>> :kind! FromList1 ['a', 'b', 'c'] ""--- FromList1 ['a', 'b', 'c'] "" :: Symbol--- = "cba"-type family FromList1 (syms :: [Char]) (sym :: Symbol) :: Symbol where-  FromList1 '[] s = s-  FromList1 (x : xs) s = FromList1 xs (ConsSymbol x s)---- $ lists---- | Type inequality-data NotTyEq :: a -> b -> Exp Bool--type instance Eval (NotTyEq a b) = NotTyEqImpl a b---- | Check types aren't equal-type family NotTyEqImpl (a :: k) (b :: k) :: Bool where-  NotTyEqImpl a a = 'False-  NotTyEqImpl a b = 'True---- $ examples---- | Drop leading underscores.------ >>> :kind! Eval (DropUnderscores ['_', '_', 'a'])--- Eval (DropUnderscores ['_', '_', 'a']) :: [Char]--- = '['a']-type DropUnderscores = DropWhile (TyEq '_')---- | Drop leading non-underscores.------ >>> :kind! Eval (DropNonUnderscores ['a', 'a', '_'])--- Eval (DropNonUnderscores ['a', 'a', '_']) :: [Char]--- = '['_']-type DropNonUnderscores = DropWhile (NotTyEq '_')---- | Drop the prefix of a 'Symbol'.------ >>> :kind! DropPrefix "_userParams_category"--- DropPrefix "_userParams_category" :: Symbol--- = "category"-type family DropPrefix (sym :: Symbol) :: Symbol where-  DropPrefix sym = FromList (Eval (DropUnderscores (Eval (DropNonUnderscores (Eval (DropUnderscores (ToList sym)))))))