on master help sign in
working generic breakdown
commitb32a660df2bba8669d0276d81815532140bd915d authorMark Wotton <mwotton@gmail.com> dateTue 22 Dec 2020, 10:20 (6 years ago) parent9a40123
6 files changed+112−40
roboservant.cabal+2−1 src/Roboservant/Types/Breakdown.hs+38−32 src/Roboservant/Types/Internal.hs+5−1 src/Roboservant/Types/ReifiedApi.hs+2−2 test/Breakdown.hs+50 test/Spec.hs+15−4
roboservant.cabal+2−1
@@ -4,7 +4,7 @@ cabal-version: 1.12
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: 259bc9e689003f4c9cb6d24bb6e68043f20c1401cf4031df677aac6909983f33
7+-- hash: 3b8eaf26233853cd94e20de7f84c25d6124cd919072681c3287a761526f82b9e
88
99name: roboservant
1010version: 0.1.0.2
@@ -66,6 +66,7 @@ test-suite roboservant-test
6666 type: exitcode-stdio-1.0
6767 main-is: Spec.hs
6868 other-modules:
69+ Breakdown
6970 Foo
7071 Headers
7172 Post
src/Roboservant/Types/Breakdown.hs+38−32
@@ -1,59 +1,65 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE DerivingVia #-}
4−{-# LANGUAGE DerivingVia #-}
5−
6−{-# LANGUAGE StandaloneDeriving #-}
7−{-# LANGUAGE StandaloneDeriving #-}
8−
9−{-# LANGUAGE FlexibleInstances #-}
104{-# LANGUAGE FlexibleInstances #-}
11−
125{-# LANGUAGE GADTs #-}
13−{-# LANGUAGE GADTs #-}
14−
15−{-# LANGUAGE DefaultSignatures #-}
16−{-# LANGUAGE DerivingStrategies #-}
17−{-# LANGUAGE FlexibleInstances #-}
18−{-# LANGUAGE GADTs #-}
19−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
206{-# LANGUAGE LambdaCase #-}
217{-# LANGUAGE MultiParamTypeClasses #-}
228{-# LANGUAGE PolyKinds #-}
239{-# LANGUAGE ScopedTypeVariables #-}
24−{-# LANGUAGE TypeApplications #-}
10+{-# LANGUAGE StandaloneDeriving #-}
2511{-# LANGUAGE TypeFamilies #-}
12+{-# LANGUAGE TypeOperators #-}
2613{-# LANGUAGE UndecidableInstances #-}
27−{-# LANGUAGE StandaloneDeriving #-}
28−{-# LANGUAGE DerivingVia #-}
29−
3014
3115module Roboservant.Types.Breakdown where
3216
33−import Data.Dynamic (Dynamic, fromDynamic, toDyn)
17+import Data.Dynamic (Dynamic, toDyn)
3418import Data.List.NonEmpty (NonEmpty (..))
35−import qualified Data.List.NonEmpty as NEL
36−import Data.Maybe (fromMaybe)
37−import GHC.Generics(Generic)
19+import GHC.Generics
3820import Data.Typeable (Typeable)
3921import Roboservant.Types.Internal
4022import Data.Hashable
23+import Data.Kind
24+import qualified Data.List.NonEmpty as NEL
4125
42−-- | only use this when we are using the internal typerep map.
43−promisedDyn :: Typeable a => Dynamic -> a
44−promisedDyn = fromMaybe (error "internal error, typerep map misconstructed") . fromDynamic
26+breakdown :: (Hashable x, Typeable x, Breakdown x)
27+ => x -> NonEmpty (Dynamic, Int)
28+breakdown x = hashedDyn x :| breakdownExtras x
4529
4630class Breakdown x where
47− breakdown :: x -> NonEmpty (Dynamic,Int)
48−-- default breakdown :: Typeable x => x -> NonEmpty Dynamic
49−-- breakdown = pure . toDyn
50−
31+ breakdownExtras :: x -> [(Dynamic,Int)]
5132
5233instance (Hashable x, Typeable x) => Breakdown (Atom x) where
53− breakdown (Atom x) = pure (toDyn x, hash x)
34+ breakdownExtras (Atom x) = [(toDyn x, hash x)]
5435
5536deriving via (Atom ()) instance Breakdown ()
5637deriving via (Atom Int) instance Breakdown Int
38+deriving via (Atom [Char]) instance Breakdown [Char]
39+
40+class GBreakdown (f :: k -> *) where
41+ gBreakdownExtras :: f a -> [(Dynamic,Int)]
42+
43+instance (Hashable x, Typeable x, Generic x, GBreakdown (Rep x)) => Breakdown (Compound (x::Type)) where
44+ breakdownExtras = gBreakdownExtras . from . unCompound
45+
46+instance GBreakdown f => GBreakdown (M1 S c f ) where
47+ gBreakdownExtras (M1 f) = gBreakdownExtras f
48+
49+instance GBreakdown b => GBreakdown (M1 D a b) where
50+ gBreakdownExtras (M1 f) = gBreakdownExtras f
51+
52+
53+instance GBreakdown b => GBreakdown (M1 C a b) where
54+ gBreakdownExtras (M1 f) = gBreakdownExtras f
55+
56+instance (GBreakdown a, GBreakdown b) => GBreakdown (a :*: b) where
57+ gBreakdownExtras (a :*: b) = gBreakdownExtras a <> gBreakdownExtras b
58+
59+instance (GBreakdown a, GBreakdown b) => GBreakdown (a :+: b) where
60+ gBreakdownExtras = \case
61+ L1 a -> gBreakdownExtras a
62+ R1 a -> gBreakdownExtras a
5763
58−-- instance (Typeable a, Breakdown a) => Breakdown [a] where
59−-- breakdown x = toDyn x :| mconcat (map (NEL.toList . breakdown) x)
64+instance (Hashable a, Typeable a, Breakdown a) => GBreakdown (K1 R a) where
65+ gBreakdownExtras (K1 c) = NEL.toList $ breakdown c
src/Roboservant/Types/Internal.hs+5−1
@@ -12,9 +12,10 @@ import Data.Dependent.Map (DMap)
1212import qualified Type.Reflection as R
1313import Data.Dependent.Sum
1414import Data.IntSet(IntSet)
15−import Data.Hashable(Hashable)
15+import Data.Hashable(Hashable,hash)
1616import GHC.Generics(Generic)
1717import Data.Typeable(Typeable)
18+import Data.Dynamic(Dynamic,toDyn)
1819
1920data Provenance
2021 = Provenance R.SomeTypeRep Int
@@ -43,3 +44,6 @@ newtype Atom x = Atom { unAtom :: x }
4344-- | can be broken down and built up from generic pieces
4445newtype Compound x = Compound { unCompound :: x }
4546 deriving newtype (Hashable,Typeable)
47+
48+hashedDyn :: (Hashable a, Typeable a) => a -> (Dynamic, Int)
49+hashedDyn a = (toDyn a, hash a)
src/Roboservant/Types/ReifiedApi.hs+2−2
@@ -38,7 +38,7 @@ import qualified Data.Text as T
3838import qualified Data.Vinyl as V
3939import qualified Data.Vinyl.Curry as V
4040import qualified Type.Reflection as R
41−
41+import Data.Hashable(Hashable)
4242
4343newtype ApiOffset = ApiOffset Int
4444 deriving (Eq, Show, Ord)
@@ -101,7 +101,7 @@ instance NormalizeFunction x => NormalizeFunction (r -> x) where
101101 type Normal (r -> x) = r -> Normal x
102102 normalize = fmap normalize
103103
104−instance (Typeable x, Breakdown x) => NormalizeFunction (Handler x) where
104+instance (Typeable x, Hashable x, Breakdown x) => NormalizeFunction (Handler x) where
105105 type Normal (Handler x) = IO (Either ServerError (NonEmpty (Dynamic,Int)))
106106 normalize handler = (runExceptT . runHandler') handler >>= \case
107107 Left serverError -> pure (Left serverError)
test/Breakdown.hsnew file+50
@@ -0,0 +1,50 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE DerivingStrategies #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE TypeOperators #-}
7+
8+module Breakdown where
9+
10+import Data.Aeson
11+import Data.Typeable (Typeable)
12+import GHC.Generics (Generic)
13+import Servant
14+import Data.Hashable
15+
16+data Foo = Foo Int String
17+ deriving (Generic, Eq, Show, Typeable)
18+
19+instance Hashable Foo
20+instance ToJSON Foo
21+instance FromJSON Foo
22+
23+
24+data SomeSum = A Int | B String
25+ deriving (Generic, Eq, Show, Typeable)
26+
27+instance Hashable SomeSum
28+instance ToJSON SomeSum
29+instance FromJSON SomeSum
30+
31+
32+type ProductApi = "item" :> ReqBody '[JSON] Int :> Post '[JSON] ()
33+ :<|> "getFoo" :> Get '[JSON] Foo
34+
35+eliminate :: Int -> Handler ()
36+eliminate _ = throwError $ err500 {errBody = "eliminate blew up, oh no!"}
37+
38+productServer :: Server ProductApi
39+productServer = eliminate :<|> pure (Foo 12 "abc")
40+
41+
42+type SumApi
43+ = "item" :> ReqBody '[JSON] Int :> Post '[JSON] ()
44+ :<|> "getFoo1" :> Get '[JSON] SomeSum
45+ :<|> "getFoo2" :> Get '[JSON] SomeSum
46+
47+
48+
49+sumServer :: Server SumApi
50+sumServer = eliminate :<|> pure (B "hi") :<|> pure (A 3)
test/Spec.hs+15−4
@@ -13,6 +13,7 @@ import qualified Valid
1313import qualified Headers
1414import qualified Post
1515import qualified Product
16+import qualified Breakdown
1617
1718import Test.Hspec.Core.Spec
1819
@@ -72,6 +73,7 @@ spec = do
7273 RS.fuzz @Foo.Api Foo.server defaultConfig noCheck
7374 >>= (`shouldSatisfy` serverFailure)
7475
76+ describe "BuildFrom" $ do
7577 describe "headers (and sum types)" $ do
7678 it "should find a failure that's dependent on using header info" $ do
7779 RS.fuzz @Headers.Api Headers.server defaultConfig noCheck
@@ -79,12 +81,18 @@ spec = do
7981
8082 describe "product types" $ do
8183 it "should find a failure that's dependent on creating a product" $ do
82− RS.fuzz @Product.Api Product.server defaultConfig { RS.seed = [hashedDyn 'a', hashedDyn (1::Int)]} noCheck
84+ RS.fuzz @Product.Api Product.server defaultConfig { RS.seed = [RS.hashedDyn 'a', RS.hashedDyn (1::Int)]} noCheck
8385 >>= (`shouldSatisfy` serverFailure)
84− -- >>= (`shouldSatisfy` isJust)
8586
86−hashedDyn :: (Hashable a, Typeable a) => a -> (Dynamic, Int)
87−hashedDyn a = (toDyn a, hash a)
87+ describe "Breakdown" $ do
88+ it "handles products" $ do
89+ RS.fuzz @Breakdown.ProductApi Breakdown.productServer defaultConfig noCheck
90+ >>= (`shouldSatisfy` serverFailure)
91+
92+ it "handles sums" $ do
93+ RS.fuzz @Breakdown.SumApi Breakdown.sumServer defaultConfig noCheck
94+ >>= (`shouldSatisfy` serverFailure)
95+
8896
8997serverFailure :: Maybe RS.Report -> Bool
9098serverFailure = \case
@@ -133,6 +141,9 @@ deriving via (RS.Atom Post.FooPost) instance RS.BuildFrom Post.FooPost
133141deriving via (RS.Atom Post.FooPost) instance RS.Breakdown Post.FooPost
134142
135143deriving via (RS.Compound Product.Foo) instance RS.BuildFrom Product.Foo
144+deriving via (RS.Compound Breakdown.Foo) instance RS.Breakdown Breakdown.Foo
145+deriving via (RS.Compound Breakdown.SomeSum) instance RS.Breakdown Breakdown.SomeSum
146+
136147
137148
138149--deriving via (Compound RS.BuildFrom.Wrapped) instance RS.BuildFrom RS.BuildFrom.Wrapped