@@ -1,140 +1,217 @@
1+-- | Description: ways to build a reified api from a servant description.
3+-- arguably this could be more general and be abstracted away from even relying on servant
4+-- but that's future work.
2{-# LANGUAGE AllowAmbiguousTypes #-}
6{-# LANGUAGE AllowAmbiguousTypes #-}
3{-# LANGUAGE DataKinds #-}
7{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DerivingStrategies #-}
8{-# LANGUAGE DerivingStrategies #-}
9+{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
10{-# LANGUAGE FlexibleInstances #-}
11+{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE LambdaCase #-}
13{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE PolyKinds #-}
14{-# LANGUAGE PolyKinds #-}
9{-# LANGUAGE ScopedTypeVariables #-}
15{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE StandaloneDeriving #-}
11−{-# LANGUAGE TupleSections #-}
12{-# LANGUAGE TypeApplications #-}
16{-# LANGUAGE TypeApplications #-}
13{-# LANGUAGE TypeFamilies #-}
17{-# LANGUAGE TypeFamilies #-}
14{-# LANGUAGE TypeOperators #-}
18{-# LANGUAGE TypeOperators #-}
15{-# LANGUAGE UndecidableInstances #-}
19{-# LANGUAGE UndecidableInstances #-}
16−
20+{-# LANGUAGE CPP #-}
17module Roboservant.Types.ReifiedApi where
21module Roboservant.Types.ReifiedApi where
19−
23+import Data.Dynamic (Dynamic)
20−import Control.Monad.Except (runExceptT)
24+import Control.Exception(Exception)
21−import Data.Dynamic (Dynamic, toDyn)
25+import Data.List.NonEmpty (NonEmpty)
22−import Data.Function ((&))
23−import Data.Typeable (TypeRep, Typeable, typeRep)
26+import Data.Typeable (Typeable)
24−import GHC.TypeLits (Symbol)
27+import GHC.Generics ((:*:)(..))
28+import Roboservant.Types.Internal
29+import Roboservant.Types.Breakdown
30+import Roboservant.Types.BuildFrom
31+import Data.Kind(Type)
25import Servant
32import Servant
26import Servant.API.Modifiers(FoldRequired,FoldLenient)
33import Servant.API.Modifiers(FoldRequired,FoldLenient)
27−import Roboservant.Types.FlattenServer
34+import GHC.TypeLits (Symbol)
28−
35+import qualified Data.Text as T
36+import qualified Data.Vinyl as V
37+import qualified Data.Vinyl.Curry as V
38+import qualified Type.Reflection as R
30newtype ApiOffset = ApiOffset Int
40newtype ApiOffset = ApiOffset Int
31− deriving (Eq, Show)
41+ deriving (Eq, Show, Ord)
32 deriving newtype (Enum, Num)
42 deriving newtype (Enum, Num)
34−type ReifiedEndpoint = ([TypeRep], TypeRep, Dynamic)
44+type TypedF = (:*:) R.TypeRep
36−type ReifiedApi = [(ApiOffset, [TypeRep], TypeRep, Dynamic)]
46+newtype Argument a = Argument
47+ { getArgument :: Stash -> Maybe (StashValue a)
50+data ReifiedEndpoint = forall as. (V.RecordToList as, V.RMap as) => ReifiedEndpoint
51+ { reArguments :: V.Rec (TypedF Argument) as
52+ , reEndpointFunc :: V.Curried as (IO (Either InteractionError (NonEmpty (Dynamic,Int))))
39−class ToReifiedApi (endpoints :: [*]) where
55+instance Show ReifiedEndpoint where
40− toReifiedApi :: Bundled endpoints -> Proxy endpoints -> ReifiedApi
56+ show _ = "lol"
58+class ( V.RecordToList (EndpointArgs endpoint)
59+ , V.RMap (EndpointArgs endpoint)
42−class ToReifiedEndpoint (endpoint :: *) where
60+ ) => ToReifiedEndpoint (endpoint :: Type) where
43− toReifiedEndpoint :: Dynamic -> Proxy endpoint -> ReifiedEndpoint
61+ type EndpointArgs endpoint :: [Type]
62+ type EndpointRes endpoint :: Type
64+ reifiedEndpointArguments :: V.Rec (TypedF Argument) (EndpointArgs endpoint)
67+tagType :: Typeable a => f a -> TypedF f a
68+tagType = (R.typeRep :*:)
70+data InteractionError = InteractionError
71+ { errorMessage :: T.Text
75+instance Exception InteractionError
45−instance ToReifiedApi '[] where
46− toReifiedApi NoEndpoints _ = []
49− (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
50− ToReifiedApi (endpoint : endpoints)
52− toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
53− withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
55− (\(n, x, y, z) -> (n + 1, x, y, z))
56− (toReifiedApi endpoints (Proxy @endpoints))
58− withOffset (x, y, z) = (0, x, y, z)
60−class NormalizeFunction m where
62− normalize :: m -> Normal m
64−instance NormalizeFunction x => NormalizeFunction (r -> x) where
65− type Normal (r -> x) = r -> Normal x
66− normalize = fmap normalize
68−instance Typeable x => NormalizeFunction (Handler x) where
69− type Normal (Handler x) = IO (Either ServerError (TypeRep, Dynamic))
70− normalize handler = (runExceptT . runHandler') handler >>= \case
71− Left serverError -> pure (Left serverError)
72− Right x -> pure (Right (typeRep (Proxy @x), toDyn x))
75− Typeable responseType =>
80+ (Typeable responseType, Breakdown responseType) =>
76 ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
81 ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
78− toReifiedEndpoint endpoint _ =
83+ type EndpointArgs (Verb method statusCode contentTypes responseType) = '[]
79− ([], typeRep (Proxy @responseType), endpoint)
84+ type EndpointRes (Verb method statusCode contentTypes responseType) = responseType
85+ reifiedEndpointArguments = V.RNil
87+instance ToReifiedEndpoint (NoContentVerb method)
89+ type EndpointArgs (NoContentVerb method) = '[]
90+ type EndpointRes (NoContentVerb method) = NoContent
91+ reifiedEndpointArguments = V.RNil
82 (ToReifiedEndpoint endpoint) =>
94 (ToReifiedEndpoint endpoint) =>
83 ToReifiedEndpoint ((x :: Symbol) :> endpoint)
95 ToReifiedEndpoint ((x :: Symbol) :> endpoint)
85− toReifiedEndpoint endpoint _ =
97+ type EndpointArgs ((x :: Symbol) :> endpoint) = EndpointArgs endpoint
86− toReifiedEndpoint endpoint (Proxy @endpoint)
98+ type EndpointRes ((x :: Symbol) :> endpoint) = EndpointRes endpoint
99+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
102+ (ToReifiedEndpoint endpoint) =>
103+ ToReifiedEndpoint (RemoteHost :> endpoint)
105+ type EndpointArgs (RemoteHost :> endpoint) = EndpointArgs endpoint
106+ type EndpointRes (RemoteHost :> endpoint) = EndpointRes endpoint
107+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
89 (ToReifiedEndpoint endpoint) =>
112 (ToReifiedEndpoint endpoint) =>
90 ToReifiedEndpoint (Description s :> endpoint)
113 ToReifiedEndpoint (Description s :> endpoint)
92− toReifiedEndpoint endpoint _ =
115+ type EndpointArgs (Description s :> endpoint) = EndpointArgs endpoint
93− toReifiedEndpoint endpoint (Proxy @endpoint)
116+ type EndpointRes (Description s :> endpoint) = EndpointRes endpoint
117+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
96 (ToReifiedEndpoint endpoint) =>
120 (ToReifiedEndpoint endpoint) =>
97 ToReifiedEndpoint (Summary s :> endpoint)
121 ToReifiedEndpoint (Summary s :> endpoint)
99− toReifiedEndpoint endpoint _ =
123+ type EndpointArgs (Summary s :> endpoint) = EndpointArgs endpoint
100− toReifiedEndpoint endpoint (Proxy @endpoint)
124+ type EndpointRes (Summary s :> endpoint) = EndpointRes endpoint
125+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
128+ (Typeable requestType
129+ ,BuildFrom requestType
103− (Typeable requestType, ToReifiedEndpoint endpoint) =>
130+ ,ToReifiedEndpoint endpoint) =>
104 ToReifiedEndpoint (QueryFlag name :> endpoint)
131 ToReifiedEndpoint (QueryFlag name :> endpoint)
106− toReifiedEndpoint endpoint _ =
133+ type EndpointArgs (QueryFlag name :> endpoint) = Bool ': EndpointArgs endpoint
107− toReifiedEndpoint endpoint (Proxy @endpoint)
134+ type EndpointRes (QueryFlag name :> endpoint) = EndpointRes endpoint
108− & \(args, result, typeRepMap) -> (typeRep (Proxy @Bool) : args, result, typeRepMap)
135+ reifiedEndpointArguments = tagType (Argument (buildFrom @Bool)) V.:& reifiedEndpointArguments @endpoint
137+type IfLenient s mods t = If (FoldLenient mods) (Either s t) t
138+type IfRequired mods t = If (FoldRequired mods) t (Maybe t)
139+type IfRequiredLenient s mods t = IfRequired mods (IfLenient s mods t)
142+ ( BuildFrom (IfRequiredLenient T.Text mods paramType)
111− (Typeable (If (FoldRequired mods) paramType (Maybe paramType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
143+ , ToReifiedEndpoint endpoint
112 ToReifiedEndpoint (QueryParam' mods name paramType :> endpoint)
145 ToReifiedEndpoint (QueryParam' mods name paramType :> endpoint)
114− toReifiedEndpoint endpoint _ =
147+ type EndpointArgs (QueryParam' mods name paramType :> endpoint) = IfRequiredLenient T.Text mods paramType ': EndpointArgs endpoint
115− toReifiedEndpoint endpoint (Proxy @endpoint)
148+ type EndpointRes (QueryParam' mods name paramType :> endpoint) = EndpointRes endpoint
116− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) paramType (Maybe paramType))) : args, result, typeRepMap)
149+ reifiedEndpointArguments =
150+ tagType (Argument (buildFrom @(IfRequiredLenient T.Text mods paramType)))
151+ V.:& reifiedEndpointArguments @endpoint
119− (Typeable (If (FoldRequired mods) headerType (Maybe headerType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
155+ ( BuildFrom paramType
156+ , ToReifiedEndpoint endpoint
160+ ToReifiedEndpoint (QueryParams name paramType :> endpoint)
162+ type EndpointArgs (QueryParams name paramType :> endpoint) = [paramType] ': EndpointArgs endpoint
163+ type EndpointRes (QueryParams name paramType :> endpoint) = EndpointRes endpoint
164+ reifiedEndpointArguments =
165+ tagType (Argument (buildFrom @[paramType]))
166+ V.:& reifiedEndpointArguments @endpoint
172+ ( BuildFrom (IfRequiredLenient T.Text mods headerType)
173+ , ToReifiedEndpoint endpoint
120 ToReifiedEndpoint (Header' mods headerName headerType :> endpoint)
175 ToReifiedEndpoint (Header' mods headerName headerType :> endpoint)
122− toReifiedEndpoint endpoint _ =
177+ type EndpointArgs (Header' mods headerName headerType :> endpoint) = IfRequiredLenient T.Text mods headerType ': EndpointArgs endpoint
123− toReifiedEndpoint endpoint (Proxy @endpoint)
178+ type EndpointRes (Header' mods headerName headerType :> endpoint) = EndpointRes endpoint
124− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) headerType (Maybe headerType))) : args, result, typeRepMap)
179+ reifiedEndpointArguments =
180+ tagType (Argument (buildFrom @(IfRequiredLenient T.Text mods headerType)))
181+ V.:& reifiedEndpointArguments @endpoint
183+#if MIN_VERSION_servant(0,17,0)
185+ ( BuildFrom (IfLenient String mods captureType)
127− (Typeable captureType, ToReifiedEndpoint endpoint) =>
186+ , ToReifiedEndpoint endpoint) =>
128 ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
187 ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
130− toReifiedEndpoint endpoint _ =
189+ type EndpointArgs (Capture' mods name captureType :> endpoint) = IfLenient String mods captureType ': EndpointArgs endpoint
131− toReifiedEndpoint endpoint (Proxy @endpoint)
190+ type EndpointRes (Capture' mods name captureType :> endpoint) = EndpointRes endpoint
132− & \(args, result, typeRepMap) -> (typeRep (Proxy @captureType) : args, result, typeRepMap)
191+ reifiedEndpointArguments =
192+ tagType (Argument (buildFrom @(IfLenient String mods captureType)))
193+ V.:& reifiedEndpointArguments @endpoint
196+ ( BuildFrom captureType
197+ , ToReifiedEndpoint endpoint) =>
198+ ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
200+ type EndpointArgs (Capture' mods name captureType :> endpoint) = captureType ': EndpointArgs endpoint
201+ type EndpointRes (Capture' mods name captureType :> endpoint) = EndpointRes endpoint
202+ reifiedEndpointArguments =
203+ tagType (Argument (buildFrom @(captureType)))
204+ V.:& reifiedEndpointArguments @endpoint
135− (Typeable (If (FoldLenient mods) (Either String requestType) requestType), ToReifiedEndpoint endpoint, SBoolI (FoldLenient mods)) =>
209+ ( BuildFrom (IfLenient String mods requestType)
210+ , ToReifiedEndpoint endpoint) =>
136 ToReifiedEndpoint (ReqBody' mods contentTypes requestType :> endpoint)
211 ToReifiedEndpoint (ReqBody' mods contentTypes requestType :> endpoint)
138− toReifiedEndpoint endpoint _ =
213+ type EndpointArgs (ReqBody' mods contentTypes requestType :> endpoint) = IfLenient String mods requestType ': EndpointArgs endpoint
139− toReifiedEndpoint endpoint (Proxy @endpoint)
214+ type EndpointRes (ReqBody' mods contentTypes requestType :> endpoint) = EndpointRes endpoint
140− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldLenient mods) (Either String requestType) requestType)) : args, result, typeRepMap)
215+ reifiedEndpointArguments =
216+ tagType (Argument (buildFrom @(IfLenient String mods requestType)))
217+ V.:& reifiedEndpointArguments @endpoint