module Servant.Aeson.Internal where
import Data.Aeson
import Data.Function
import Data.List
import Data.Proxy
import Data.Typeable
import GHC.TypeLits
import Servant.API
import Test.Hspec
import Test.QuickCheck
import Test.Aeson.Internal.GoldenSpecs
import Test.Aeson.Internal.RoundtripSpecs
apiRoundtripSpecs :: (HasGenericSpecs api) => Proxy api -> Spec
apiRoundtripSpecs = sequence_ . map roundtrip . mkRoundtripSpecs
apiGoldenSpecs :: HasGenericSpecs api => Proxy api -> Spec
apiGoldenSpecs proxy = sequence_ $ map golden $ mkRoundtripSpecs proxy
apiSpecs :: (HasGenericSpecs api) => Proxy api -> Spec
apiSpecs proxy = sequence_ $ map (\ ts -> roundtrip ts >> golden ts) $ mkRoundtripSpecs proxy
usedTypes :: (HasGenericSpecs api) => Proxy api -> [TypeRep]
usedTypes = map typ . mkRoundtripSpecs
mkRoundtripSpecs :: (HasGenericSpecs api) => Proxy api -> [TypeSpec]
mkRoundtripSpecs = normalize . collectRoundtripSpecs
where
normalize = nubBy ((==) `on` typ) . sortBy (compare `on` (show . typ))
class HasGenericSpecs api where
collectRoundtripSpecs :: Proxy api -> [TypeSpec]
instance (HasGenericSpecs a, HasGenericSpecs b) => HasGenericSpecs (a :<|> b) where
collectRoundtripSpecs Proxy =
collectRoundtripSpecs (Proxy :: Proxy a) ++
collectRoundtripSpecs (Proxy :: Proxy b)
#if MIN_VERSION_servant(0, 5, 0)
instance
(MkTypeSpecs response) =>
HasGenericSpecs (Verb (method :: StdMethod) returnStatus contentTypes response) where
collectRoundtripSpecs Proxy = do
mkTypeSpecs (Proxy :: Proxy response)
instance
HasGenericSpecs (Verb (method :: StdMethod) returnStatus contentTypes NoContent) where
collectRoundtripSpecs Proxy = []
#else
instance (MkTypeSpecs response) =>
HasGenericSpecs (Get contentTypes response) where
collectRoundtripSpecs Proxy = do
mkTypeSpecs (Proxy :: Proxy response)
instance (MkTypeSpecs response) =>
HasGenericSpecs (Post contentTypes response) where
collectRoundtripSpecs Proxy = mkTypeSpecs (Proxy :: Proxy response)
#endif
instance (MkTypeSpecs body, HasGenericSpecs api) =>
HasGenericSpecs (ReqBody contentTypes body :> api) where
collectRoundtripSpecs Proxy =
mkTypeSpecs (Proxy :: Proxy body) ++
collectRoundtripSpecs (Proxy :: Proxy api)
instance HasGenericSpecs api => HasGenericSpecs ((path :: Symbol) :> api) where
collectRoundtripSpecs Proxy = collectRoundtripSpecs (Proxy :: Proxy api)
#if !MIN_VERSION_servant(0, 5, 0)
instance HasGenericSpecs api => HasGenericSpecs (MatrixParam name a :> api) where
collectRoundtripSpecs Proxy = collectRoundtripSpecs (Proxy :: Proxy api)
#endif
data TypeSpec
= TypeSpec {
typ :: TypeRep,
roundtrip :: Spec,
golden :: Spec
}
class MkTypeSpecs a where
mkTypeSpecs :: Proxy a -> [TypeSpec]
instance (Typeable a, Eq a, Show a, Arbitrary a, ToJSON a, FromJSON a) => MkTypeSpecs a where
mkTypeSpecs proxy = pure $
TypeSpec {
typ = typeRep proxy,
roundtrip = roundtripSpecs proxy,
golden = goldenSpecs proxy
}
instance
(Eq a, Show a, Typeable a, Arbitrary a, ToJSON a, FromJSON a) =>
MkTypeSpecs [a] where
mkTypeSpecs Proxy = pure $
TypeSpec {
typ = typeRep proxy,
roundtrip = genericAesonRoundtripWithNote proxy (Just note),
golden = goldenSpecsWithNote proxy (Just note)
}
where
proxy = Proxy :: Proxy a
note = "(as element-type in [])"
instance
(Eq a, Show a, Typeable a, Arbitrary a, ToJSON a, FromJSON a) =>
MkTypeSpecs (Maybe a) where
mkTypeSpecs Proxy = pure $
TypeSpec {
typ = typeRep proxy,
roundtrip = genericAesonRoundtripWithNote proxy (Just note),
golden = goldenSpecsWithNote proxy (Just note)
}
where
proxy = Proxy :: Proxy a
note = "(as element-type in Maybe)"
instance
MkTypeSpecs () where
mkTypeSpecs Proxy = []