on master help sign in
modularise a little
commit30762a2695c5c1d0acd65db04fd5d24bd91d5989 authorMark Wotton <mwotton@gmail.com> dateMon 21 Sep 2020, 16:11 (6 years ago) parent98c08a2
3 files changed+191−165
roboservant.cabal+2−1 src/Roboservant/StateMachine.hs+3−164 src/Roboservant/Types.hs+186
roboservant.cabal+2−1
@@ -4,7 +4,7 @@ cabal-version: 1.12
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: 0bfe1c9487789846b43d871520e7e546842a8ff9acb003e426d5282dac1e6aa1
7+-- hash: 54c16a0abf993f08c909111fc8c7a86a3e633745bc93734d36b3bf9ccb93062a
88
99name: roboservant
1010version: 0.1.0.2
@@ -31,6 +31,7 @@ library
3131 exposed-modules:
3232 Roboservant
3333 Roboservant.StateMachine
34+ Roboservant.Types
3435 other-modules:
3536 Paths_roboservant
3637 hs-source-dirs:
src/Roboservant/StateMachine.hs+3−164
@@ -21,182 +21,21 @@ module Roboservant.StateMachine
2121, prop_concurrent
2222) where
2323
24−import Control.Monad.Except (runExceptT)
2524import Control.Monad.IO.Class (MonadIO, liftIO)
26−import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic, toDyn)
27−import Data.Function ((&))
25+import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic)
2826import Data.IORef (IORef, newIORef)
2927import qualified Data.List.NonEmpty as NEL
30−import Data.List.NonEmpty (NonEmpty)
31−import Data.Map.Strict (Map)
3228import qualified Data.Map.Strict as Map
3329import Data.Maybe (mapMaybe)
34−import Data.Typeable (TypeRep, Typeable, typeRep)
30+import Data.Typeable (TypeRep)
3531import GHC.IORef (readIORef)
36−import GHC.TypeLits (Symbol)
3732import Hedgehog
3833import qualified Hedgehog.Gen as Gen
3934import qualified Hedgehog.Range as Range
4035import Servant
41−import Servant.API.Modifiers
4236import Type.Reflection (SomeTypeRep)
4337
44−data State v
45− = State
46− { stateRefs :: Map TypeRep (NonEmpty (Var (Opaque (IORef Dynamic)) v))
47− }
48−
49−class FlattenServer api where
50− flattenServer :: Server api -> Bundled (Endpoints api)
51−
52−instance
53− ( Endpoints (endpoint :<|> api) ~ (endpoint ': Endpoints api),
54− Server (endpoint :<|> api) ~ (Server endpoint :<|> Server api),
55− FlattenServer api
56− ) =>
57− FlattenServer (endpoint :<|> api)
58− where
59− flattenServer (endpoint :<|> server) = endpoint `AnEndpoint` flattenServer @api server
60−
61−instance
62− ( HasServer (x :> api) '[],
63− Endpoints (x :> api) ~ '[x :> api]
64− ) =>
65− FlattenServer (x :> api)
66− where
67− flattenServer server = server `AnEndpoint` NoEndpoints
68−
69−instance
70− ( HasServer (Verb method statusCode contentTypes responseType) '[],
71− Endpoints (Verb method statusCode contentTypes responseType) ~ '[Verb method statusCode contentTypes responseType]
72− ) =>
73− FlattenServer (Verb method statusCode contentTypes responseType)
74− where
75− flattenServer server = server `AnEndpoint` NoEndpoints
76−
77−type ReifiedEndpoint = ([TypeRep], TypeRep, Dynamic)
78−
79−type ReifiedApi = [(ApiOffset, [TypeRep], TypeRep, Dynamic)]
80−
81−data Bundled endpoints where
82− AnEndpoint :: Server endpoint -> Bundled endpoints -> Bundled (endpoint ': endpoints)
83− NoEndpoints :: Bundled '[]
84−
85−class ToReifiedApi (endpoints :: [*]) where
86− toReifiedApi :: Bundled endpoints -> Proxy endpoints -> ReifiedApi
87−
88−class ToReifiedEndpoint (endpoint :: *) where
89− toReifiedEndpoint :: Dynamic -> Proxy endpoint -> ReifiedEndpoint
90−
91−instance ToReifiedApi '[] where
92− toReifiedApi NoEndpoints _ = []
93−
94−instance
95− (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
96− ToReifiedApi (endpoint : endpoints)
97− where
98− toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
99− withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
100− : map
101− (\(n, x, y, z) -> (n + 1, x, y, z))
102− (toReifiedApi endpoints (Proxy @endpoints))
103− where
104− withOffset (x, y, z) = (0, x, y, z)
105−
106−class NormalizeFunction m where
107− type Normal m
108− normalize :: m -> Normal m
109−
110−instance NormalizeFunction x => NormalizeFunction (r -> x) where
111− type Normal (r -> x) = r -> Normal x
112− normalize = fmap normalize
113−
114−instance Typeable x => NormalizeFunction (Handler x) where
115− type Normal (Handler x) = IO (Either ServerError (TypeRep, Dynamic))
116− normalize handler = (runExceptT . runHandler') handler >>= \case
117− Left serverError -> pure (Left serverError)
118− Right x -> pure (Right (typeRep (Proxy @x), toDyn x))
119−
120−instance
121− Typeable responseType =>
122− ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
123− where
124− toReifiedEndpoint endpoint _ =
125− ([], typeRep (Proxy @responseType), endpoint)
126−
127−instance
128− (ToReifiedEndpoint endpoint) =>
129− ToReifiedEndpoint ((x :: Symbol) :> endpoint)
130− where
131− toReifiedEndpoint endpoint _ =
132− toReifiedEndpoint endpoint (Proxy @endpoint)
133−
134−instance
135− (ToReifiedEndpoint endpoint) =>
136− ToReifiedEndpoint (Description s :> endpoint)
137− where
138− toReifiedEndpoint endpoint _ =
139− toReifiedEndpoint endpoint (Proxy @endpoint)
140−
141−instance
142− (ToReifiedEndpoint endpoint) =>
143− ToReifiedEndpoint (Summary s :> endpoint)
144− where
145− toReifiedEndpoint endpoint _ =
146− toReifiedEndpoint endpoint (Proxy @endpoint)
147−
148−instance
149− (Typeable requestType, ToReifiedEndpoint endpoint) =>
150− ToReifiedEndpoint (QueryFlag name :> endpoint)
151− where
152− toReifiedEndpoint endpoint _ =
153− toReifiedEndpoint endpoint (Proxy @endpoint)
154− & \(args, result, typeRepMap) -> (typeRep (Proxy @Bool) : args, result, typeRepMap)
155−
156−instance
157− (Typeable (If (FoldRequired mods) paramType (Maybe paramType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
158− ToReifiedEndpoint (QueryParam' mods name paramType :> endpoint)
159− where
160− toReifiedEndpoint endpoint _ =
161− toReifiedEndpoint endpoint (Proxy @endpoint)
162− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) paramType (Maybe paramType))) : args, result, typeRepMap)
163−
164−instance
165− (Typeable (If (FoldRequired mods) headerType (Maybe headerType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
166− ToReifiedEndpoint (Header' mods headerName headerType :> endpoint)
167− where
168− toReifiedEndpoint endpoint _ =
169− toReifiedEndpoint endpoint (Proxy @endpoint)
170− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) headerType (Maybe headerType))) : args, result, typeRepMap)
171−
172−instance
173− (Typeable captureType, ToReifiedEndpoint endpoint) =>
174− ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
175− where
176− toReifiedEndpoint endpoint _ =
177− toReifiedEndpoint endpoint (Proxy @endpoint)
178− & \(args, result, typeRepMap) -> (typeRep (Proxy @captureType) : args, result, typeRepMap)
179−
180−instance
181− (Typeable (If (FoldLenient mods) (Either String requestType) requestType), ToReifiedEndpoint endpoint, SBoolI (FoldLenient mods)) =>
182− ToReifiedEndpoint (ReqBody' mods contentTypes requestType :> endpoint)
183− where
184− toReifiedEndpoint endpoint _ =
185− toReifiedEndpoint endpoint (Proxy @endpoint)
186− & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldLenient mods) (Either String requestType) requestType)) : args, result, typeRepMap)
187−
188−newtype ApiOffset = ApiOffset Int
189− deriving (Eq, Show)
190− deriving newtype (Enum, Num)
191−
192−-- | we need to specify an offset because it's entirely possible to have two
193−-- functions with the same arguments that do different things.
194−data Op (v :: * -> *) = Op ApiOffset [(TypeRep, Var (Opaque (IORef Dynamic)) v)]
195−
196−deriving instance Show (Op Symbolic)
197−
198−instance HTraversable Op where
199− htraverse r (Op offset args) = Op offset <$> traverse (\(t, v) -> (t,) <$> htraverse r v) args
38+import Roboservant.Types
20039
20140callEndpoint :: (MonadGen n, MonadIO m) => ReifiedApi -> Command n m State
20241callEndpoint staticRoutes =
src/Roboservant/Types.hsnew file+186
@@ -0,0 +1,186 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DerivingStrategies #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE LambdaCase #-}
8+{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE StandaloneDeriving #-}
11+{-# LANGUAGE TupleSections #-}
12+{-# LANGUAGE TypeApplications #-}
13+{-# LANGUAGE TypeFamilies #-}
14+{-# LANGUAGE TypeOperators #-}
15+{-# LANGUAGE UndecidableInstances #-}
16+
17+module Roboservant.Types where
18+
19+import Control.Monad.Except (runExceptT)
20+import Data.Dynamic (Dynamic, toDyn)
21+import Data.Function ((&))
22+import Data.IORef (IORef)
23+import Data.List.NonEmpty (NonEmpty)
24+import Data.Map.Strict (Map)
25+import Data.Typeable (TypeRep, Typeable, typeRep)
26+import GHC.TypeLits (Symbol)
27+import Hedgehog
28+import Servant
29+import Servant.API.Modifiers
30+
31+data State v
32+ = State
33+ { stateRefs :: Map TypeRep (NonEmpty (Var (Opaque (IORef Dynamic)) v))
34+ }
35+
36+class FlattenServer api where
37+ flattenServer :: Server api -> Bundled (Endpoints api)
38+
39+instance
40+ ( Endpoints (endpoint :<|> api) ~ (endpoint ': Endpoints api),
41+ Server (endpoint :<|> api) ~ (Server endpoint :<|> Server api),
42+ FlattenServer api
43+ ) =>
44+ FlattenServer (endpoint :<|> api)
45+ where
46+ flattenServer (endpoint :<|> server) = endpoint `AnEndpoint` flattenServer @api server
47+
48+instance
49+ ( HasServer (x :> api) '[],
50+ Endpoints (x :> api) ~ '[x :> api]
51+ ) =>
52+ FlattenServer (x :> api)
53+ where
54+ flattenServer server = server `AnEndpoint` NoEndpoints
55+
56+instance
57+ ( HasServer (Verb method statusCode contentTypes responseType) '[],
58+ Endpoints (Verb method statusCode contentTypes responseType) ~ '[Verb method statusCode contentTypes responseType]
59+ ) =>
60+ FlattenServer (Verb method statusCode contentTypes responseType)
61+ where
62+ flattenServer server = server `AnEndpoint` NoEndpoints
63+
64+type ReifiedEndpoint = ([TypeRep], TypeRep, Dynamic)
65+
66+type ReifiedApi = [(ApiOffset, [TypeRep], TypeRep, Dynamic)]
67+
68+data Bundled endpoints where
69+ AnEndpoint :: Server endpoint -> Bundled endpoints -> Bundled (endpoint ': endpoints)
70+ NoEndpoints :: Bundled '[]
71+
72+class ToReifiedApi (endpoints :: [*]) where
73+ toReifiedApi :: Bundled endpoints -> Proxy endpoints -> ReifiedApi
74+
75+class ToReifiedEndpoint (endpoint :: *) where
76+ toReifiedEndpoint :: Dynamic -> Proxy endpoint -> ReifiedEndpoint
77+
78+instance ToReifiedApi '[] where
79+ toReifiedApi NoEndpoints _ = []
80+
81+instance
82+ (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
83+ ToReifiedApi (endpoint : endpoints)
84+ where
85+ toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
86+ withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
87+ : map
88+ (\(n, x, y, z) -> (n + 1, x, y, z))
89+ (toReifiedApi endpoints (Proxy @endpoints))
90+ where
91+ withOffset (x, y, z) = (0, x, y, z)
92+
93+class NormalizeFunction m where
94+ type Normal m
95+ normalize :: m -> Normal m
96+
97+instance NormalizeFunction x => NormalizeFunction (r -> x) where
98+ type Normal (r -> x) = r -> Normal x
99+ normalize = fmap normalize
100+
101+instance Typeable x => NormalizeFunction (Handler x) where
102+ type Normal (Handler x) = IO (Either ServerError (TypeRep, Dynamic))
103+ normalize handler = (runExceptT . runHandler') handler >>= \case
104+ Left serverError -> pure (Left serverError)
105+ Right x -> pure (Right (typeRep (Proxy @x), toDyn x))
106+
107+instance
108+ Typeable responseType =>
109+ ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
110+ where
111+ toReifiedEndpoint endpoint _ =
112+ ([], typeRep (Proxy @responseType), endpoint)
113+
114+instance
115+ (ToReifiedEndpoint endpoint) =>
116+ ToReifiedEndpoint ((x :: Symbol) :> endpoint)
117+ where
118+ toReifiedEndpoint endpoint _ =
119+ toReifiedEndpoint endpoint (Proxy @endpoint)
120+
121+instance
122+ (ToReifiedEndpoint endpoint) =>
123+ ToReifiedEndpoint (Description s :> endpoint)
124+ where
125+ toReifiedEndpoint endpoint _ =
126+ toReifiedEndpoint endpoint (Proxy @endpoint)
127+
128+instance
129+ (ToReifiedEndpoint endpoint) =>
130+ ToReifiedEndpoint (Summary s :> endpoint)
131+ where
132+ toReifiedEndpoint endpoint _ =
133+ toReifiedEndpoint endpoint (Proxy @endpoint)
134+
135+instance
136+ (Typeable requestType, ToReifiedEndpoint endpoint) =>
137+ ToReifiedEndpoint (QueryFlag name :> endpoint)
138+ where
139+ toReifiedEndpoint endpoint _ =
140+ toReifiedEndpoint endpoint (Proxy @endpoint)
141+ & \(args, result, typeRepMap) -> (typeRep (Proxy @Bool) : args, result, typeRepMap)
142+
143+instance
144+ (Typeable (If (FoldRequired mods) paramType (Maybe paramType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
145+ ToReifiedEndpoint (QueryParam' mods name paramType :> endpoint)
146+ where
147+ toReifiedEndpoint endpoint _ =
148+ toReifiedEndpoint endpoint (Proxy @endpoint)
149+ & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) paramType (Maybe paramType))) : args, result, typeRepMap)
150+
151+instance
152+ (Typeable (If (FoldRequired mods) headerType (Maybe headerType)), ToReifiedEndpoint endpoint, SBoolI (FoldRequired mods)) =>
153+ ToReifiedEndpoint (Header' mods headerName headerType :> endpoint)
154+ where
155+ toReifiedEndpoint endpoint _ =
156+ toReifiedEndpoint endpoint (Proxy @endpoint)
157+ & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldRequired mods) headerType (Maybe headerType))) : args, result, typeRepMap)
158+
159+instance
160+ (Typeable captureType, ToReifiedEndpoint endpoint) =>
161+ ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
162+ where
163+ toReifiedEndpoint endpoint _ =
164+ toReifiedEndpoint endpoint (Proxy @endpoint)
165+ & \(args, result, typeRepMap) -> (typeRep (Proxy @captureType) : args, result, typeRepMap)
166+
167+instance
168+ (Typeable (If (FoldLenient mods) (Either String requestType) requestType), ToReifiedEndpoint endpoint, SBoolI (FoldLenient mods)) =>
169+ ToReifiedEndpoint (ReqBody' mods contentTypes requestType :> endpoint)
170+ where
171+ toReifiedEndpoint endpoint _ =
172+ toReifiedEndpoint endpoint (Proxy @endpoint)
173+ & \(args, result, typeRepMap) -> (typeRep (Proxy @(If (FoldLenient mods) (Either String requestType) requestType)) : args, result, typeRepMap)
174+
175+newtype ApiOffset = ApiOffset Int
176+ deriving (Eq, Show)
177+ deriving newtype (Enum, Num)
178+
179+-- | we need to specify an offset because it's entirely possible to have two
180+-- functions with the same arguments that do different things.
181+data Op (v :: * -> *) = Op ApiOffset [(TypeRep, Var (Opaque (IORef Dynamic)) v)]
182+
183+deriving instance Show (Op Symbolic)
184+
185+instance HTraversable Op where
186+ htraverse r (Op offset args) = Op offset <$> traverse (\(t, v) -> (t,) <$> htraverse r v) args