on master help sign in
Merge branch 'master' into morecoverage
commitec36997bfcb15a91b3793d32dfb46b426872e049 authorMark Wotton <mwotton@gmail.com> dateThu 26 Nov 2020, 14:48 (6 years ago) parentf6865054efbf03
7 files changed+325−177
package.yaml+4−2 roboservant.cabal+9−3 src/Roboservant/Direct.hs+113−70 src/Roboservant/Types/Breakdown.hs+40−20 src/Roboservant/Types/ReifiedApi.hs+95−61 stack.yaml+4−17 stack.yaml.lock+60−4
package.yaml+4−2
@@ -2,8 +2,6 @@ name: roboservant
22version: 0.1.0.2
33github: "mwotton/roboservant"
44license: BSD3
5−author: "Mark Wotton, Samuel Schlesinger"
6−maintainer: "mwotton@gmail.com"
75copyright: "2020 Mark Wotton, Samuel Schlesinger"
86synopsis: Automatic session-aware servant testing
97category: Web
@@ -27,6 +25,10 @@ dependencies:
2725- servant-flatten
2826- servant-server # >= 0.17
2927- string-conversions
28+- vinyl
29+- dependent-sum
30+- dependent-map
31+- text
3032- time
3133
3234ghc-options:
roboservant.cabal+9−3
@@ -4,7 +4,7 @@ cabal-version: 1.12
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: 4774250c208a2100460b0df7518257286ca38d8612b517ddcbf3e37678b598cf
7+-- hash: 134cfee817f86824342ecabc02ca2b48ec67539b75581c1954b95a9f9128e5a4
88
99name: roboservant
1010version: 0.1.0.2
@@ -13,8 +13,6 @@ description: Please see the README on GitHub at <https://github.com/mwotton/r
1313category: Web
1414homepage: https://github.com/mwotton/roboservant#readme
1515bug-reports: https://github.com/mwotton/roboservant/issues
16−author: Mark Wotton, Samuel Schlesinger
17−maintainer: mwotton@gmail.com
1816copyright: 2020 Mark Wotton, Samuel Schlesinger
1917license: BSD3
2018license-file: LICENSE
@@ -44,6 +42,8 @@ library
4442 base >=4.7 && <5
4543 , bytestring
4644 , containers
45+ , dependent-map
46+ , dependent-sum
4747 , lifted-base
4848 , monad-control
4949 , mtl
@@ -53,7 +53,9 @@ library
5353 , servant-flatten
5454 , servant-server
5555 , string-conversions
56+ , text
5657 , time
58+ , vinyl
5759 default-language: Haskell2010
5860
5961test-suite roboservant-test
@@ -76,6 +78,8 @@ test-suite roboservant-test
7678 , base >=4.7 && <5
7779 , bytestring
7880 , containers
81+ , dependent-map
82+ , dependent-sum
7983 , hspec
8084 , hspec-core
8185 , http-api-data
@@ -89,5 +93,7 @@ test-suite roboservant-test
8993 , servant-flatten
9094 , servant-server
9195 , string-conversions
96+ , text
9297 , time
98+ , vinyl
9399 default-language: Haskell2010
src/Roboservant/Direct.hs+113−70
@@ -6,6 +6,7 @@
66{-# LANGUAGE GADTs #-}
77{-# LANGUAGE LambdaCase #-}
88{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE RankNTypes #-}
910{-# LANGUAGE RecordWildCards #-}
1011{-# LANGUAGE ScopedTypeVariables #-}
1112{-# LANGUAGE TupleSections #-}
@@ -18,14 +19,20 @@ module Roboservant.Direct
1819 ( fuzz, Config(..)
1920
2021 -- TODO come up with something smarter than exporting all this, we should
21− -- have some nice error-display functions
22+ -- have some nice error-display functions
2223 , RoboservantException(..), FuzzState(..), FuzzOp(..), FailureType(..), Report(..)
2324 )
2425where
2526
2627import Control.Exception.Lifted(throw,handle,Handler(..), Exception,SomeException,SomeAsyncException, catch, catches)
2728import Control.Monad.State.Strict(MonadState,MonadIO,get,modify',liftIO,runStateT)
28−import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic)
29+import Control.Exception.Lifted(throw,Handler(..), Exception,SomeException,SomeAsyncException, catch, catches)
30+import Control.Monad(void,replicateM,forM_)
31+import Control.Monad.State.Strict(MonadState,MonadIO,get,modify',liftIO,execStateT)
32+import Control.Monad.Trans.Control(MonadBaseControl)
33+import Data.Dependent.Map (DMap)
34+import Data.Dynamic (Dynamic(..), dynApply, dynTypeRep, fromDynamic)
35+import Data.List.NonEmpty (NonEmpty(..))
2936import Data.Map.Strict(Map)
3037import qualified Data.Set as Set
3138import Data.Maybe (mapMaybe)
@@ -37,6 +44,16 @@ import qualified Data.Map.Strict as Map
3744import Data.Time.Clock
3845import Data.List(sortOn)
3946import Control.Arrow(second)
47+import System.Timeout.Lifted(timeout)
48+import GHC.Generics ((:*:)(..))
49+import qualified Data.Vinyl.Functor as V
50+import qualified Data.Vinyl.Curry as V
51+import qualified Data.Dependent.Map as DM
52+import qualified Data.Vinyl as V
53+import qualified Data.List.NonEmpty as NEL
54+import qualified Data.Map.Strict as Map
55+import Data.Time.Clock
56+import qualified Type.Reflection as R
4057
4158import Roboservant.Types.Breakdown
4259import Roboservant.Types
@@ -44,11 +61,19 @@ import Roboservant.Types
4461 FlattenServer (..),
4562-- ReifiedApi,
4663 ToReifiedApi (..),
47− ))
64+
65+ ReifiedEndpoint(..),
66+ Argument(..),
67+ TypedF
68+
69+ )
70+
71+
4872data RoboservantException
4973 = RoboservantException
5074 { failureReason :: FailureType
5175 , serverException :: Maybe SomeException
76+ , something :: Int
5277 , fuzzState :: FuzzState
5378 } deriving (Show)
5479
@@ -81,7 +106,12 @@ data FuzzState = FuzzState
81106 , stash :: Stash
82107 , currentRng :: StdGen
83108 }
84− deriving (Show)
109+ deriving Show
110+
111+data EndpointOption = forall as. (V.RecordToList as, V.RMap as) => EndpointOption
112+ { eoCall :: V.Curried as (IO (Either ServerError (NonEmpty Dynamic)))
113+ , eoArgs :: V.Rec (TypedF StashValue) as
114+ }
85115
86116data StopReason
87117 = TimedOut
@@ -107,9 +137,8 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
107137 deadline :: UTCTime <- addUTCTime (fromInteger $ maxRuntime * 1000000) <$> getCurrentTime
108138 (stopreason, fs ) <- runStateT
109139 (untilDone (maxReps, deadline) go <* (evaluateCoverage =<< get)) FuzzState{..}
140+ print stopreason
110141 pure Nothing
111− -- mapM_ (print . (\(offset, (args, dyn) ) -> (offset, map fst args, dyn))) reifiedApi
112−
113142 where
114143 -- something less terrible later
115144 formatException :: RoboservantException -> Report
@@ -159,67 +188,77 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
159188 modify' $ \st' -> st' { currentRng = newGen }
160189 pure (l !! index)
161190
162− genOp :: (MonadState FuzzState m, MonadIO m)
163− => m (FuzzOp, Dynamic, [Dynamic])
164− genOp = do -- fs@FuzzState{..} = do
191+ withOp :: (MonadState FuzzState m, MonadIO m)
192+ => (forall as. (V.RecordToList as, V.RMap as)
193+ => FuzzOp
194+ -> V.Curried as (IO (Either ServerError (NonEmpty Dynamic)))
195+ -> V.Rec (TypedF V.Identity) as -> m r
196+ )
197+ -> m r
198+ withOp callback = do
165199 -- choose a call to make, from the endpoints with fillable arguments.
166− (offset, dynCall, args) <- elementOrFail . options =<< get
167− r <- mapM (elementOrFail . zip [0..] . NEL.toList) args
168− let pathSegment = FuzzOp offset (map (\(index,(_,dyn) ) -> Provenance (dynTypeRep dyn) index) r)
200+ (offset, EndpointOption{..}) <- elementOrFail . options =<< get
201+ r <- V.rtraverse
202+ (\(tr :*: StashValue svs) -> elementOrFail $
203+ zipWith (\i xy -> V.Const i :*: tr :*: xy)
204+ [0..]
205+ (NEL.toList svs)
206+ )
207+ eoArgs
208+ let pathSegment = FuzzOp offset $
209+ recordToList'
210+ (\(V.Const index :*: tr :*: _) -> Provenance (R.SomeTypeRep tr) index)
211+ r
212+ argValues = V.rmap
213+ (\(_ :*: tr :*: (_, x)) -> tr :*: V.Identity x)
214+ r
169215 modify' (\f -> f { path = path f <> [pathSegment] })
170− pure (pathSegment, dynCall, fmap (snd . snd) r)
171−
216+ callback pathSegment eoCall argValues
172217 where
173− options :: FuzzState -> [(ApiOffset, Dynamic, [NEL.NonEmpty ([Provenance], Dynamic)])]
218+ options :: FuzzState -> [(ApiOffset, EndpointOption)]
174219 options FuzzState{..} =
175220 mapMaybe
176− ( \(offset, (argreps, dynCall)) -> (offset,dynCall,) <$>
177− mapM (\(_tr,bf) -> bf stash ) argreps
221+ ( \(offset, ReifiedEndpoint{..}) -> do
222+ args <- V.rtraverse (\(tr :*: Argument bf) -> (tr :*:) <$> bf stash) reArguments
223+ pure (offset, EndpointOption reEndpointFunc args)
178224 )
179225 reifiedApi
180226
181− execute :: (MonadState FuzzState m, MonadIO m)
182− => (FuzzOp,Dynamic,[Dynamic]) -> m ()
183− execute (fuzzop, dyncall, args) = do
184− -- liftIO $ print fuzzop
185− -- now, magic happens: we apply some dynamic arguments to a dynamic
186− -- function and hopefully something useful pops out the end.
187− let func = foldr (\arg curr -> flip dynApply arg =<< curr) (Just dyncall) (reverse args)
227+ execute :: (MonadState FuzzState m, MonadIO m, V.RecordToList as, V.RMap as)
228+ => FuzzOp
229+ -> V.Curried as (IO (Either ServerError (NonEmpty Dynamic)))
230+ -> V.Rec (TypedF V.Identity) as
231+ -> m ()
232+ execute fuzzop func args = do
233+ liftIO $ print fuzzop
188234 st <- get
189− let showable = unlines $ ("args":map (show . dynTypeRep) args)
235+ let showable = unlines $ ("args":map show argTypes)
190236 <> ["fuzzop"
191237 , show fuzzop
192− ,"dyncall"
193− ,show (dynTypeRep dyncall)
238+ -- ,"dyncall"
239+ -- ,show (dynTypeRep dyncall)
194240 ,"state"
195241 ,show st]
196− -- liftIO $ putStrLn showable
197−
198− case func of
199− Nothing -> error ("all screwed up 1: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
200− Just f' -> do
201− -- liftIO $ print
202− case fromDynamic f' of
203− Nothing -> error ("all screwed up 2: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
204− Just f -> liftIO f >>= \case
205− -- parameterise this
206− Left (serverError :: ServerError) ->
207− case errHTTPCode serverError of
208− 500 -> throw serverError
209− _ ->
210− liftIO $ print ("ignoring non-500 error" , serverError)
211−
212− Right (dyn :: NEL.NonEmpty Dynamic) -> do
213− -- liftIO $ print ("storing", fmap dynTypeRep dyn)
214− modify' (\fs@FuzzState{..} ->
215− fs { stash = addToStash (NEL.toList dyn) stash } )
216− pure ()
217−
218−-- go :: (MonadState FuzzState m, MonadIO m, MonadBaseControl IO m)
219−-- => m ()
220− go = do
221− op <- genOp
222− catches (execute op)
242+ liftIO $ putStrLn showable
243+ liftIO (V.runcurry' func argVals) >>= \case
244+ -- parameterise this
245+ Left (serverError :: ServerError) ->
246+ case errHTTPCode serverError of
247+ 500 -> throw serverError
248+ _ -> do
249+ liftIO $ print ("ignoring non-500 error" , serverError)
250+ Right (dyn :: NEL.NonEmpty Dynamic) -> do
251+ liftIO $ print ("storing", fmap dynTypeRep dyn)
252+ modify' (\fs@FuzzState{..} ->
253+ fs { stash = addToStash (NEL.toList dyn) stash } )
254+ where
255+ argVals = V.rmap (\(_ :*: V.Identity x) -> V.Identity x) args
256+ argTypes = recordToList' (\(tr :*: _) -> R.SomeTypeRep tr) args
257+
258+ go :: (MonadState FuzzState m, MonadIO m, MonadBaseControl IO m)
259+ => m ()
260+ go = withOp $ \op func args -> do
261+ catches (execute op func args)
223262 [ Handler (\(e :: SomeAsyncException) -> throw e)
224263 , Handler (\(e :: SomeException) -> do
225264 displayDiagnostics =<< get
@@ -228,25 +267,29 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
228267 catch (liftIO checker)
229268 (\(e :: SomeException) -> throw . RoboservantException CheckerFailed (Just e) =<< get)
230269
231− -- actions <-
232− -- forAll $ do
233− -- Gen.sequential
234− -- (Range.linear 1 100)
235− -- emptyState
236− -- (fmap preload seed <> [callEndpoint reifiedApi])
237− -- executeSequential emptyState actions
238270addToStash :: [Dynamic]
239− -> Map TypeRep (NEL.NonEmpty ([Provenance], Dynamic))
240− -> Map TypeRep (NEL.NonEmpty ([Provenance], Dynamic))
271+ -> Stash
272+ -> Stash
241273addToStash result stash =
242− foldr (\dyn dict -> let tr = dynTypeRep dyn in
243− Map.insertWith renumber tr (pure ([Provenance tr 0],dyn)) dict) stash result
244−-- Map.insertWith (flip (<>)) (dynTypeRep result) (_pure result) stash })
274+ foldr (\(Dynamic tr x) (Stash dict) -> Stash $
275+ DM.insertWith renumber tr (StashValue (([Provenance (R.SomeTypeRep tr) 0],x):|[])) dict
276+ )
277+ stash
278+ result
245279 where
246− renumber :: NEL.NonEmpty ([Provenance],Dynamic)
247− -> NEL.NonEmpty ([Provenance],Dynamic)
248− -> NEL.NonEmpty ([Provenance],Dynamic)
249− renumber singleDyn l = case NEL.toList singleDyn of
280+ renumber :: StashValue a
281+ -> StashValue a
282+ -> StashValue a
283+ renumber (StashValue singleDyn) (StashValue l) = StashValue $ case NEL.toList singleDyn of
250284 [([Provenance tr _], dyn)] -> l
251285 <> pure ([Provenance tr (length (NEL.last l) + 1)], dyn)
252286 _ -> error "should be impossible"
287+
288+
289+-- why isn't this in vinyl?
290+recordToList'
291+ :: (V.RecordToList as, V.RMap as)
292+ => (forall x. f x -> a)
293+ -> V.Rec f as
294+ -> [a]
295+recordToList' f = V.recordToList . V.rmap (V.Const . f)
src/Roboservant/Types/Breakdown.hs+40−20
@@ -2,10 +2,16 @@
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE DerivingVia #-}
44{-# LANGUAGE StandaloneDeriving #-}
5−
6−
75{-# LANGUAGE FlexibleInstances #-}
86{-# LANGUAGE GADTs #-}
7+{-# LANGUAGE DefaultSignatures #-}
8+{-# LANGUAGE DeriveFunctor #-}
9+{-# LANGUAGE DerivingStrategies #-}
10+{-# LANGUAGE FlexibleInstances #-}
11+{-# LANGUAGE GADTs #-}
12+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
13+{-# LANGUAGE LambdaCase #-}
14+{-# LANGUAGE MultiParamTypeClasses #-}
915{-# LANGUAGE PolyKinds #-}
1016{-# LANGUAGE ScopedTypeVariables #-}
1117{-# LANGUAGE TypeApplications #-}
@@ -18,30 +24,27 @@ import Data.Dynamic (Dynamic, fromDynamic, toDyn)
1824import Data.List.NonEmpty (NonEmpty(..))
1925import qualified Data.List.NonEmpty as NEL
2026import qualified Data.Map.Strict as Map
21−import Data.Map.Strict (Map)
2227import Data.Maybe (fromMaybe)
2328import Data.Proxy (Proxy (..))
2429import Data.Typeable (TypeRep, Typeable, typeRep)
2530import GHC.Generics(Generic)
2631import qualified GHC.Generics as Generics
32+import Data.Typeable (Typeable)
33+import qualified Data.Dependent.Map as DM
34+import Data.Dependent.Map (DMap)
35+import qualified Type.Reflection as R
36+import Data.Dependent.Sum
37+import Data.Kind
2738
2839data Provenance
29− = Provenance TypeRep Int
40+ = Provenance R.SomeTypeRep Int
3041 deriving (Show,Eq)
31−type Stash = Map TypeRep (NonEmpty ([Provenance], Dynamic))
32−
3342
3443class Breakdown x where
3544 breakdown :: x -> NonEmpty Dynamic
3645-- default breakdown :: Typeable x => x -> NonEmpty Dynamic
3746-- breakdown = pure . toDyn
3847
39−class Typeable x => BuildFrom x where
40− buildFrom :: Stash -> Maybe (NonEmpty ([Provenance],Dynamic))
41−
42−-- default buildFrom :: Stash -> Maybe (NonEmpty ([Provenance], Dynamic))
43−-- buildFrom = Map.lookup (typeRep (Proxy @x))
44−
4548
4649-- | Can't be built up from parts
4750newtype Atom x = Atom { unAtom :: x }
@@ -62,6 +65,22 @@ instance (Typeable x, Generic x) => Breakdown (Compound x) where
6265instance Typeable x => BuildFrom (Atom x) where
6366 buildFrom = Map.lookup (typeRep (Proxy @x))
6467
68+newtype StashValue a = StashValue { getStashValue :: NonEmpty ([Provenance], a) }
69+ deriving (Functor, Show)
70+
71+-- wrap in newtype to give a custom Show instance, since the normal
72+-- instance for DMap is not happy since StashValue needs Show a to show
73+newtype Stash = Stash { getStash :: DMap R.TypeRep StashValue }
74+ deriving (Semigroup, Monoid)
75+
76+instance Show Stash where
77+ showsPrec i (Stash x) = showsPrec i $
78+ Map.fromList . map (\(tr :=> StashValue vs) -> (R.SomeTypeRep tr, fmap fst vs)) $ DM.toList x
79+
80+class Typeable x => BuildFrom (x :: Type) where
81+ buildFrom :: Stash -> Maybe (StashValue x)
82+ default buildFrom :: Stash -> Maybe (StashValue x)
83+ buildFrom = DM.lookup R.typeRep . getStash
6584
6685 -- (fmap promisedDyn . NEL.toList) . Map.lookup (typeRep (Proxy @x))
6786
@@ -78,18 +97,19 @@ promisedDyn = fromMaybe (error "internal error, typerep map misconstructed") . f
7897deriving via (Atom Bool) instance BuildFrom Bool
7998
8099instance (Typeable x, BuildFrom x) => BuildFrom (Maybe x) where
81− buildFrom dict = Just $ fmap toDyn <$> options
82− where options :: NonEmpty ([Provenance], Maybe x)
83− options = ([],Nothing) :|
84− (maybe [] (NEL.toList . (fmap (fmap (Just . promisedDyn @x))))
85− $ buildFrom @x dict)
86−
87−instance Typeable a => Breakdown (Atom a) where
88− breakdown = pure . toDyn . unAtom
100+ buildFrom dict = Just options
101+ where options :: StashValue (Maybe x)
102+ options = StashValue $
103+ ([],Nothing) :|
104+ (maybe [] (NEL.toList . getStashValue . fmap Just) $ buildFrom @x dict
105+ )
89106
90107deriving via (Atom ()) instance Breakdown ()
91108deriving via (Atom Int) instance Breakdown Int
92109
110+instance Typeable a => Breakdown (Atom a) where
111+ breakdown = pure . toDyn . unAtom
112+
93113
94114instance (Typeable a, Breakdown a) => Breakdown [a] where
95115 breakdown x = toDyn x :| mconcat (map (NEL.toList . breakdown) x)
src/Roboservant/Types/ReifiedApi.hs+95−61
@@ -2,10 +2,13 @@
22{-# LANGUAGE AllowAmbiguousTypes #-}
33{-# LANGUAGE DataKinds #-}
44{-# LANGUAGE DerivingStrategies #-}
5+{-# LANGUAGE FlexibleContexts #-}
56{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE GADTs #-}
68{-# LANGUAGE GeneralizedNewtypeDeriving #-}
79{-# LANGUAGE LambdaCase #-}
810{-# LANGUAGE PolyKinds #-}
11+{-# LANGUAGE RecordWildCards #-}
912{-# LANGUAGE ScopedTypeVariables #-}
1013{-# LANGUAGE StandaloneDeriving #-}
1114{-# LANGUAGE TupleSections #-}
@@ -18,45 +21,75 @@ module Roboservant.Types.ReifiedApi where
1821
1922
2023import Control.Monad.Except (runExceptT)
21−import Data.Dynamic (Dynamic, toDyn)
22−import Data.Function ((&))
23−import Data.Typeable (TypeRep, Typeable, typeRep)
24+import Data.Bifunctor
25+-- import Data.Dependent.Sum
26+import Data.Dynamic (Dynamic)
27+import Data.Kind
28+import Data.List.NonEmpty (NonEmpty)
29+import Data.Typeable (Typeable)
30+import GHC.Generics ((:*:)(..))
2431import GHC.TypeLits (Symbol)
32+import Roboservant.Types.Breakdown
33+import Roboservant.Types.FlattenServer
2534import Servant
2635import Servant.API.Modifiers(FoldRequired,FoldLenient)
27−import Roboservant.Types.FlattenServer
28−import Roboservant.Types.Breakdown
29−import Data.List.NonEmpty (NonEmpty)
36+import qualified Data.Text as T
37+import qualified Data.Vinyl as V
38+import qualified Data.Vinyl.Curry as V
39+import qualified Type.Reflection as R
3040
3141
3242newtype ApiOffset = ApiOffset Int
3343 deriving (Eq, Show, Ord)
3444 deriving newtype (Enum, Num)
3545
36−type ReifiedEndpoint = ([(TypeRep
37− , Stash -> Maybe (NonEmpty ([Provenance], Dynamic)))]
38− , Dynamic)
46+type TypedF = (:*:) R.TypeRep
47+
48+newtype Argument a = Argument
49+ { getArgument :: Stash -> Maybe (StashValue a)
50+ }
51+
52+data ReifiedEndpoint = forall as. (V.RecordToList as, V.RMap as) => ReifiedEndpoint
53+ { reArguments :: V.Rec (TypedF Argument) as
54+ , reEndpointFunc :: V.Curried as (IO (Either ServerError (NonEmpty Dynamic)))
55+ }
3956
4057type ReifiedApi = [(ApiOffset, ReifiedEndpoint)]
4158
59+tagType :: Typeable a => f a -> TypedF f a
60+tagType = (R.typeRep :*:)
4261
4362class ToReifiedApi (endpoints :: [*]) where
4463 toReifiedApi :: Bundled endpoints -> Proxy endpoints -> ReifiedApi
4564
46−class ToReifiedEndpoint (endpoint :: *) where
47− toReifiedEndpoint :: Dynamic -> Proxy endpoint -> ReifiedEndpoint
65+class ( V.Curried (EndpointArgs endpoint) (Handler (EndpointRes endpoint)) ~ Server endpoint
66+ , V.RecordToList (EndpointArgs endpoint)
67+ , V.RMap (EndpointArgs endpoint)
68+ ) => ToReifiedEndpoint (endpoint :: *) where
69+ type EndpointArgs endpoint :: [Type]
70+ type EndpointRes endpoint :: Type
71+
72+ reifiedEndpointArguments :: V.Rec (TypedF Argument) (EndpointArgs endpoint)
4873
4974instance ToReifiedApi '[] where
5075 toReifiedApi NoEndpoints _ = []
5176
5277instance
53− (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
78+ ( Typeable (EndpointRes endpoint)
79+ , NormalizeFunction (ServerT endpoint Handler)
80+ , Normal (ServerT endpoint Handler) ~ V.Curried (EndpointArgs endpoint) (IO (Either ServerError (NonEmpty Dynamic)))
81+ , ToReifiedEndpoint endpoint
82+ , ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)
83+ ) =>
5484 ToReifiedApi (endpoint : endpoints)
5585 where
5686 toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
57− (0,) (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
58− : map
59− (\(n, x) -> (n + 1, x))
87+ (0, ReifiedEndpoint
88+ { reArguments = reifiedEndpointArguments @endpoint
89+ , reEndpointFunc = normalize endpoint
90+ }
91+ )
92+ : (map . first) (+1)
6093 (toReifiedApi endpoints (Proxy @endpoints))
6194
6295class NormalizeFunction m where
@@ -73,35 +106,37 @@ instance (Typeable x, Breakdown x) => NormalizeFunction (Handler x) where
73106 Left serverError -> pure (Left serverError)
74107 Right x -> pure $ Right $ breakdown x
75108
76−-- pure (Right (typeRep (Proxy @x), toDyn x))
77−
78109instance
79110 (Typeable responseType, Breakdown responseType) =>
80111 ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
81112 where
82− toReifiedEndpoint endpoint _ = id
83− ([], endpoint)
113+ type EndpointArgs (Verb method statusCode contentTypes responseType) = '[]
114+ type EndpointRes (Verb method statusCode contentTypes responseType) = responseType
115+ reifiedEndpointArguments = V.RNil
84116
85117instance
86118 (ToReifiedEndpoint endpoint) =>
87119 ToReifiedEndpoint ((x :: Symbol) :> endpoint)
88120 where
89− toReifiedEndpoint endpoint _ =
90− toReifiedEndpoint endpoint (Proxy @endpoint)
121+ type EndpointArgs ((x :: Symbol) :> endpoint) = EndpointArgs endpoint
122+ type EndpointRes ((x :: Symbol) :> endpoint) = EndpointRes endpoint
123+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
91124
92125instance
93126 (ToReifiedEndpoint endpoint) =>
94127 ToReifiedEndpoint (Description s :> endpoint)
95128 where
96− toReifiedEndpoint endpoint _ =
97− toReifiedEndpoint endpoint (Proxy @endpoint)
129+ type EndpointArgs (Description s :> endpoint) = EndpointArgs endpoint
130+ type EndpointRes (Description s :> endpoint) = EndpointRes endpoint
131+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
98132
99133instance
100134 (ToReifiedEndpoint endpoint) =>
101135 ToReifiedEndpoint (Summary s :> endpoint)
102136 where
103− toReifiedEndpoint endpoint _ =
104− toReifiedEndpoint endpoint (Proxy @endpoint)
137+ type EndpointArgs (Summary s :> endpoint) = EndpointArgs endpoint
138+ type EndpointRes (Summary s :> endpoint) = EndpointRes endpoint
139+ reifiedEndpointArguments = reifiedEndpointArguments @endpoint
105140
106141instance
107142 (Typeable requestType
@@ -109,58 +144,57 @@ instance
109144 ,ToReifiedEndpoint endpoint) =>
110145 ToReifiedEndpoint (QueryFlag name :> endpoint)
111146 where
112− toReifiedEndpoint endpoint _ =
113− toReifiedEndpoint endpoint (Proxy @endpoint)
114− & \(args, result) -> ((typeRep (Proxy @Bool),buildFrom @Bool) : args, result)
147+ type EndpointArgs (QueryFlag name :> endpoint) = Bool ': EndpointArgs endpoint
148+ type EndpointRes (QueryFlag name :> endpoint) = EndpointRes endpoint
149+ reifiedEndpointArguments = tagType (Argument (buildFrom @Bool)) V.:& reifiedEndpointArguments @endpoint
150+
151+type IfLenient s mods t = If (FoldLenient mods) (Either s t) t
152+type IfRequired mods t = If (FoldRequired mods) t (Maybe t)
153+type IfRequiredLenient s mods t = IfRequired mods (IfLenient s mods t)
115154
116155instance
117− ( Typeable (If (FoldRequired mods) paramType (Maybe paramType))
118− , BuildFrom (If (FoldRequired mods) paramType (Maybe paramType))
156+ ( BuildFrom (IfRequiredLenient T.Text mods paramType)
119157 , ToReifiedEndpoint endpoint
120− , SBoolI (FoldRequired mods)) =>
158+ ) =>
121159 ToReifiedEndpoint (QueryParam' mods name paramType :> endpoint)
122160 where
123− toReifiedEndpoint endpoint _ =
124− toReifiedEndpoint endpoint (Proxy @endpoint)
125− & \(args, result) ->
126− ((typeRep (Proxy @(If (FoldRequired mods) paramType (Maybe paramType)))
127− ,buildFrom @(If (FoldRequired mods) paramType (Maybe paramType)))
128− : args, result)
161+ type EndpointArgs (QueryParam' mods name paramType :> endpoint) = IfRequiredLenient T.Text mods paramType ': EndpointArgs endpoint
162+ type EndpointRes (QueryParam' mods name paramType :> endpoint) = EndpointRes endpoint
163+ reifiedEndpointArguments =
164+ tagType (Argument (buildFrom @(IfRequiredLenient T.Text mods paramType)))
165+ V.:& reifiedEndpointArguments @endpoint
166+
129167
130168instance
131− ( Typeable (If (FoldRequired mods) headerType (Maybe headerType))
132− , BuildFrom (If (FoldRequired mods) headerType (Maybe headerType))
169+ ( BuildFrom (IfRequiredLenient T.Text mods headerType)
133170 , ToReifiedEndpoint endpoint
134− , SBoolI (FoldRequired mods)) =>
171+ ) =>
135172 ToReifiedEndpoint (Header' mods headerName headerType :> endpoint)
136173 where
137− toReifiedEndpoint endpoint _ =
138− toReifiedEndpoint endpoint (Proxy @endpoint)
139− & \(args, result) -> ((typeRep (Proxy @(If (FoldRequired mods) headerType (Maybe headerType)))
140− ,buildFrom @(If (FoldRequired mods) headerType (Maybe headerType)))
141−
142− : args, result)
174+ type EndpointArgs (Header' mods headerName headerType :> endpoint) = IfRequiredLenient T.Text mods headerType ': EndpointArgs endpoint
175+ type EndpointRes (Header' mods headerName headerType :> endpoint) = EndpointRes endpoint
176+ reifiedEndpointArguments =
177+ tagType (Argument (buildFrom @(IfRequiredLenient T.Text mods headerType)))
178+ V.:& reifiedEndpointArguments @endpoint
143179
144180instance
145− ( Typeable captureType
146− , BuildFrom captureType
181+ ( BuildFrom (IfLenient String mods captureType)
147182 , ToReifiedEndpoint endpoint) =>
148183 ToReifiedEndpoint (Capture' mods name captureType :> endpoint)
149184 where
150− toReifiedEndpoint endpoint _ =
151− toReifiedEndpoint endpoint (Proxy @endpoint)
152− & \(args, result) -> ((typeRep (Proxy @captureType)
153− ,buildFrom @captureType)
154− : args, result)
185+ type EndpointArgs (Capture' mods name captureType :> endpoint) = IfLenient String mods captureType ': EndpointArgs endpoint
186+ type EndpointRes (Capture' mods name captureType :> endpoint) = EndpointRes endpoint
187+ reifiedEndpointArguments =
188+ tagType (Argument (buildFrom @(IfLenient String mods captureType)))
189+ V.:& reifiedEndpointArguments @endpoint
155190
156191instance
157− ( Typeable (If (FoldLenient mods) (Either String requestType) requestType)
158− , BuildFrom (If (FoldLenient mods) (Either String requestType) requestType)
159− , ToReifiedEndpoint endpoint, SBoolI (FoldLenient mods)) =>
192+ ( BuildFrom (IfLenient String mods requestType)
193+ , ToReifiedEndpoint endpoint) =>
160194 ToReifiedEndpoint (ReqBody' mods contentTypes requestType :> endpoint)
161195 where
162− toReifiedEndpoint endpoint _ =
163− toReifiedEndpoint endpoint (Proxy @endpoint)
164− & \(args, result) -> ((typeRep (Proxy @(If (FoldLenient mods) (Either String requestType) requestType))
165− ,buildFrom @(If (FoldLenient mods) (Either String requestType) requestType))
166− : args, result)
196+ type EndpointArgs (ReqBody' mods contentTypes requestType :> endpoint) = IfLenient String mods requestType ': EndpointArgs endpoint
197+ type EndpointRes (ReqBody' mods contentTypes requestType :> endpoint) = EndpointRes endpoint
198+ reifiedEndpointArguments =
199+ tagType (Argument (buildFrom @(IfLenient String mods requestType)))
200+ V.:& reifiedEndpointArguments @endpoint
stack.yaml+4−17
@@ -4,20 +4,7 @@ packages:
44
55extra-deps:
66- servant-flatten-0.2@sha256:276896f7c5cdec5b8f8493f6205fded0cc602d050b58fdb09a6d7c85c3bb0837,1234
7−# #- markov-chain-usage-model-0.0.0@sha256:1afa95faeb9213c4d960a669190078b41b89169462b8edd910472980671ba8c0,2112
8−# - servant-0.18
9−# - servant-client-0.18
10−# - servant-client-core-0.18
11−# - servant-server-0.18
12−# - hspec-hedgehog-0.0.1.2@sha256:aaeeb50a43d2bce8a1850baa3ddf934a964bfe38c64efd59306b0c1421e55b45,1405
13−#- QuickCheck-2.14.1@sha256:01e46d7b0a8d3148288ec977625f62d5516ebb5031a50c63f0453863301b4a79,7736
14−#- random-1.2.0
15−#- splitmix-0.1.0.1@sha256:22f9662e7e8b173421872241edd39350078a9ed4bb9e9f503948c5b483c79276,5253
16−# - git: https://github.com/hedgehogqa/haskell-hedgehog.git
17−# commit: 03714682586e43b5ddf5c00391035471a6e01238
18−# subdirs:
19−# - hedgehog
20−# - git: https://github.com/mwotton/hspec
21−# commit: cd9012414a93cf08f86e409544d4d3a8f282b507
22−# subdirs:
23−# - hspec-core
7+- vinyl-0.13.0
8+- dependent-sum-0.7.1.0
9+- constraints-extras-0.3.0.2
10+- dependent-map-0.4.0.0
stack.yaml.lock+60−4
@@ -11,9 +11,65 @@ packages:
1111 sha256: 04f12c7bef2c3f9a25d94eb9489752ed498db8e243069fe95838dbb51df1dcb3
1212 original:
1313 hackage: servant-flatten-0.2@sha256:276896f7c5cdec5b8f8493f6205fded0cc602d050b58fdb09a6d7c85c3bb0837,1234
14+- completed:
15+ hackage: vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724
16+ pantry-tree:
17+ size: 1857
18+ sha256: 860fb95820b595161cdbdec5f376100ebae2d14e5ef0dbe311546202f7525d01
19+ original:
20+ hackage: vinyl-0.13.0
21+- completed:
22+ hackage: dependent-sum-0.7.1.0@sha256:5599aa89637db434431b1dd3fa7c34bc3d565ee44f0519bfbc877be1927c2531,2068
23+ pantry-tree:
24+ size: 290
25+ sha256: 9cbfb32b5a8a782b7a1c941803fd517633cb699159b851c1d82267a9e9391b50
26+ original:
27+ hackage: dependent-sum-0.7.1.0
28+- completed:
29+ hackage: constraints-extras-0.3.0.2@sha256:013b8d0392582c6ca068e226718a4fe8be8e22321cc0634f6115505bf377ad26,1853
30+ pantry-tree:
31+ size: 594
32+ sha256: 3ce1012bfb02e4d7def9df19ce80b8cd2b472c691b25b181d9960638673fecd1
33+ original:
34+ hackage: constraints-extras-0.3.0.2
35+- completed:
36+ hackage: dependent-map-0.4.0.0@sha256:ca2b131046f4340a1c35d138c5a003fe4a5be96b14efc26291ed35fd08c62221,1657
37+ pantry-tree:
38+ size: 551
39+ sha256: 5defa30010904d2ad05a036f3eaf83793506717c93cbeb599f40db1a3632cfc5
40+ original:
41+ hackage: dependent-map-0.4.0.0
42+- completed:
43+ hackage: servant-0.18.2@sha256:f8c9f0e9891a3ada1337a3c0b369333a3b5a2d0909dd3cd09d79bc26adeaca44,5298
44+ pantry-tree:
45+ size: 2662
46+ sha256: e930e814de1aa4d24274bdf18341a50b7ed38604ae4734f730e09238ac5bf7e2
47+ original:
48+ hackage: servant-0.18.2
49+- completed:
50+ hackage: servant-client-0.18.2@sha256:82578ade7468873259bb2fdc9d62290a0f998550900683e1410a237ed4b05410,4591
51+ pantry-tree:
52+ size: 1300
53+ sha256: 6324892c77bedbce32f0d6f1612fc2cb0d82c163d3be39efb951d3cc3792ce4a
54+ original:
55+ hackage: servant-client-0.18.2
56+- completed:
57+ hackage: servant-client-core-0.18.2@sha256:4cac20d0b0d889a5613a95ec18526afd16abcaf0b4b7ba5fed35c7bf85800162,3638
58+ pantry-tree:
59+ size: 1444
60+ sha256: 90279e27ba5a7f8f4f1761e81d0dded343839316fdbf34d395954487701e29df
61+ original:
62+ hackage: servant-client-core-0.18.2
63+- completed:
64+ hackage: servant-server-0.18.2@sha256:56679af62ab8820a2108da6153d9ae9dde37199e62172365bdaea1458c3f7c2d,5482
65+ pantry-tree:
66+ size: 2614
67+ sha256: 3ac7430134439e4b67f0f5333f63b89d0cb7de5e2e07f0af7801c8e223942b9c
68+ original:
69+ hackage: servant-server-0.18.2
1470snapshots:
1571- completed:
16− size: 532379
17− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/8.yaml
18− sha256: 2ad3210d2ad35f3176005d68369a18e4d984517bfaa2caade76f28ed0b2e0521
19− original: lts-16.8
72+ size: 532380
73+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/16.yaml
74+ sha256: d6b004b095fe2a0b8b14fbc30014ee97e58843b9c9362ddb9244273dda62649e
75+ original: lts-16.16