@@ -0,0 +1,82 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DefaultSignatures #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+module Servant.Client.Generic
13+import Generics.SOP (Generic, I(..), NP(..), NS(Z), Rep, SOP(..), to)
14+import Servant.API ((:<|>)(..))
15+import Servant.Client (ClientM)
17+-- | This class allows us to match client structure with client functions
18+-- produced with 'client' without explicit pattern-matching.
20+-- The client structure needs a 'Generics.SOP.Generic' instance.
25+-- = "foo" :> Capture "x" Int :> Get '[JSON] Int
26+-- :<|> "bar" :> QueryParam "a" Char :> QueryParam "b" String :> Post '[JSON] [Int]
27+-- :<|> Captre "nested" Int :> NestedAPI
30+-- = Get '[JSON] String
31+-- :<|> "baz" :> QueryParam "c" Char :> Post '[JSON] ()
33+-- data APIClient = APIClient
34+-- { getFoo :: Int -> Manager -> BaseUrl -> ClientM Int
35+-- , postBar :: Maybe Char -> Maybe String -> Manager -> BaseUrl -> ClientM [Int]
36+-- , mkNestedClient :: Int -> NestedClient
37+-- } deriving GHC.Generic
39+-- instance Generic.SOP.Generic APIClient
40+-- instance (Client API ~ client) => ClientLike client APIClient
42+-- data NestedClient = NestedClient
43+-- { getString :: Manager -> BaseUrl -> ClientM String
44+-- , postBaz :: Maybe Char -> Manager -> BaseUrl -> ClientM ()
45+-- } deriving GHC.Generic
47+-- instance Generic.SOP.Generic
48+-- instance (Client NestedAPI ~ client) => ClientLike client NestedAPI
50+-- mkAPIClient :: APIClient
51+-- mkAPIClient = mkClient (client (Proxy :: Proxy API))
52+class ClientLike client custom where
53+ mkClient :: client -> custom
54+ default mkClient :: (Generic custom, GClientLikeP client xs, SOP I '[xs] ~ Rep custom)
56+ mkClient = genericMkClient
58+instance ClientLike client custom
59+ => ClientLike (a -> client) (a -> custom) where
60+ mkClient c = mkClient . c
62+instance ClientLike (ClientM a) (ClientM a) where
65+-- | This class is used to match client functions to the
66+-- representation of client structure type as sum of products
67+-- and basically does all the internal job to build this structure.
68+class GClientLikeP client xs where
69+ gMkClientP :: client -> NP I xs
71+instance (GClientLikeP b (y ': xs), ClientLike a x)
72+ => GClientLikeP (a :<|> b) (x ': y ': xs) where
73+ gMkClientP (a :<|> b) = I (mkClient a) :* gMkClientP b
75+instance ClientLike a x => GClientLikeP a '[x] where
76+ gMkClientP a = I (mkClient a) :* Nil
78+-- | Generate client structure from client type.
79+genericMkClient :: (Generic custom, GClientLikeP client xs, SOP I '[xs] ~ Rep custom)
81+genericMkClient = to . SOP . Z . gMkClientP