@@ -1,255 +0,0 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
7−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8−{-# LANGUAGE LambdaCase #-}
9−{-# LANGUAGE PolyKinds #-}
10−{-# LANGUAGE RecordWildCards #-}
11−{-# LANGUAGE ScopedTypeVariables #-}
12−{-# LANGUAGE StandaloneDeriving #-}
13−{-# LANGUAGE TupleSections #-}
14−{-# LANGUAGE TypeApplications #-}
15−{-# LANGUAGE TypeFamilies #-}
16−{-# LANGUAGE TypeOperators #-}
17−{-# LANGUAGE UndecidableInstances #-}
19−module Roboservant.StateMachine
24−import Control.Monad.Except (runExceptT)
25−import Control.Monad.IO.Class (MonadIO, liftIO)
26−import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic, toDyn)
27−import Data.Function ((&))
28−import Data.IORef (IORef, newIORef)
29−import qualified Data.List.NonEmpty as NEL
30−import Data.List.NonEmpty (NonEmpty)
31−import Data.Map.Strict (Map)
32−import qualified Data.Map.Strict as Map
33−import Data.Maybe (mapMaybe)
34−import Data.Typeable (TypeRep, Typeable, typeRep)
35−import GHC.IORef (readIORef)
36−import GHC.TypeLits (Symbol)
38−import qualified Hedgehog.Gen as Gen
39−import qualified Hedgehog.Range as Range
41−import Type.Reflection (SomeTypeRep)
45− { stateRefs :: Map TypeRep (NonEmpty (Var (Opaque (IORef Dynamic)) v))
48−class FlattenServer api where
49− flattenServer :: Server api -> Bundled (Endpoints api)
52− ( Endpoints (endpoint :<|> api) ~ (endpoint ': Endpoints api),
53− Server (endpoint :<|> api) ~ (Server endpoint :<|> Server api),
56− FlattenServer (endpoint :<|> api)
58− flattenServer (endpoint :<|> server) = endpoint `AnEndpoint` flattenServer @api server
61− ( HasServer (x :> api) '[],
62− Endpoints (x :> api) ~ '[x :> api]
64− FlattenServer (x :> api)
66− flattenServer server = server `AnEndpoint` NoEndpoints
69− ( HasServer (Verb method statusCode contentTypes responseType) '[],
70− Endpoints (Verb method statusCode contentTypes responseType) ~ '[Verb method statusCode contentTypes responseType]
72− FlattenServer (Verb method statusCode contentTypes responseType)
74− flattenServer server = server `AnEndpoint` NoEndpoints
76−type ReifiedEndpoint = ([TypeRep], TypeRep, Dynamic)
78−type ReifiedApi = [(ApiOffset, [TypeRep], TypeRep, Dynamic)]
80−data Bundled endpoints where
81− AnEndpoint :: Server endpoint -> Bundled endpoints -> Bundled (endpoint ': endpoints)
82− NoEndpoints :: Bundled '[]
84−class ToReifiedApi (endpoints :: [*]) where
85− toReifiedApi :: Bundled endpoints -> Proxy endpoints -> ReifiedApi
87−class ToReifiedEndpoint (endpoint :: *) where
88− toReifiedEndpoint :: Dynamic -> Proxy endpoint -> ReifiedEndpoint
90−instance ToReifiedApi '[] where
91− toReifiedApi NoEndpoints _ = []
94− (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
95− ToReifiedApi (endpoint : endpoints)
97− toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
98− withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
100− (\(n, x, y, z) -> (n + 1, x, y, z))
101− (toReifiedApi endpoints (Proxy @endpoints))
103− withOffset (x, y, z) = (0, x, y, z)
105−class NormalizeFunction m where
107− normalize :: m -> Normal m
109−instance NormalizeFunction x => NormalizeFunction (r -> x) where
110− type Normal (r -> x) = r -> Normal x
111− normalize = fmap normalize
113−instance Typeable x => NormalizeFunction (Handler x) where
114− type Normal (Handler x) = IO (Either ServerError (TypeRep, Dynamic))
115− normalize handler = (runExceptT . runHandler') handler >>= \case
116− Left serverError -> pure (Left serverError)
117− Right x -> pure (Right (typeRep (Proxy @x), toDyn x))
120− Typeable responseType =>
121− ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
123− toReifiedEndpoint endpoint _ =
124− ([], typeRep (Proxy @responseType), endpoint)
127− (ToReifiedEndpoint endpoint) =>
128− ToReifiedEndpoint ((x :: Symbol) :> endpoint)
130− toReifiedEndpoint endpoint _ =
131− toReifiedEndpoint endpoint (Proxy @endpoint)
134− (Typeable requestType, ToReifiedEndpoint endpoint) =>
135− ToReifiedEndpoint (Capture name requestType :> endpoint)
137− toReifiedEndpoint endpoint _ =
138− toReifiedEndpoint endpoint (Proxy @endpoint)
139− & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
142− (Typeable requestType, ToReifiedEndpoint endpoint) =>
143− ToReifiedEndpoint (ReqBody contentTypes requestType :> endpoint)
145− toReifiedEndpoint endpoint _ =
146− toReifiedEndpoint endpoint (Proxy @endpoint)
147− & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
149−newtype ApiOffset = ApiOffset Int
151− deriving newtype (Enum, Num)
153−-- | we need to specify an offset because it's entirely possible to have two
154−-- functions with the same arguments that do different things.
155−data Op (v :: * -> *) = Op ApiOffset [(TypeRep, Var (Opaque (IORef Dynamic)) v)]
157−deriving instance Show (Op Symbolic)
159−instance HTraversable Op where
160− htraverse r (Op offset args) = Op offset <$> traverse (\(t, v) -> (t,) <$> htraverse r v) args
162−callEndpoint :: (MonadGen n, MonadIO m) => ReifiedApi -> Command n m State
163−callEndpoint staticRoutes =
164− let gen :: MonadGen n => State Symbolic -> Maybe (n (Op Symbolic))
166− | any null options = Nothing
167− | otherwise = Just $ do
168− uncurry Op <$> chooseOne options
173− [(TypeRep, [Var (Opaque (IORef Dynamic)) Symbolic])]
178− [(TypeRep, Var (Opaque (IORef Dynamic)) Symbolic)]
181− (offset, args) <- Gen.element opts
182− (offset,) <$> mapM (\(tr, argOpts) -> (tr,) <$> Gen.element argOpts) args
183− options :: [(ApiOffset, [(TypeRep, [Var (Opaque (IORef Dynamic)) Symbolic])])]
186− ( \(offset, argreps, _retType, _dynCall) -> (offset,) <$> do
187− mapM (\x -> (x,) <$> fillableWith x) argreps
190− fillableWith :: TypeRep -> Maybe [Var (Opaque (IORef Dynamic)) Symbolic]
191− fillableWith tr = NEL.toList <$> Map.lookup tr stateRefs
195− m (Opaque (IORef Dynamic))
196− execute (Op (ApiOffset offset) args) = do
197− -- traceM (show (offset, args))
198− fmap Opaque . liftIO $ do
199− realArgs <- mapM (\(_tr, v) -> readIORef (opaque v)) args
200− let (_offset, _staticArgs, _ret, endpoint) = staticRoutes !! offset
201− -- now, magic happens: we apply some dynamic arguments to a dynamic
202− -- function and hopefully somtehing useful pops out the end.
203− func = foldr (\arg curr -> flip dynApply arg =<< curr) (Just endpoint) realArgs
204− let showable = map dynTypeRep (endpoint : realArgs)
206− Nothing -> error ("all screwed up: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
208− case fromDynamic f' of
209− Nothing -> error ("all screwed up: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
210− Just f -> liftIO f >>= \case
211− Left (serverError :: ServerError) -> error (show serverError)
212− Right (_typeRep :: SomeTypeRep, (dyn :: Dynamic)) -> newIORef dyn
216− [ Update $ \s@State {..} (Op (ApiOffset offset) _args) o' ->
219− let (_, _, tr, _) = staticRoutes !! offset
226− -- , Ensure (no-500 here)
229−prop_sequential :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> Property
230−prop_sequential server = do
231− let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
233− let initialState = State mempty
237− (Range.linear 1 100)
239− [callEndpoint reifiedApi]
240− executeSequential initialState actions
242−prop_concurrent :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> Property
243−prop_concurrent server =
244− let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api)) in
245− let initialState = State mempty
246− in withTests 1000 . withRetries 10 . property $ do
253− [callEndpoint reifiedApi]
255− executeParallel initialState actions