git.iko.soy/typeable/swagger2
on master help sign in
Improve type shape restrictions

- Explain mixed sum types problem better - Provide unrestricted genericDeclareSchema and genericDeclareNamedSchema versions

commita4bab47d60dccffe1e114f70b25d021a7689113f authorNickolay Kudasov <nickolay.kudasov@gmail.com> dateThu 7 Sep 2017, 03:37 (9 years ago) parent28bf349
3 files changed+75−56
src/Data/Swagger/Internal/Schema.hs+24−8 src/Data/Swagger/Internal/TypeShape.hs+47−48 src/Data/Swagger/Schema.hs+4
src/Data/Swagger/Internal/Schema.hs+24−8
@@ -138,7 +138,8 @@ class ToSchema a where
138138 -- Note that the schema itself is included in definitions
139139 -- only if it is recursive (and thus needs its definition in scope).
140140 declareNamedSchema :: proxy a -> Declare (Definitions Schema) NamedSchema
141− default declareNamedSchema :: (Generic a, GToSchema (Rep a)) => proxy a -> Declare (Definitions Schema) NamedSchema
141+ default declareNamedSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
142+ proxy a -> Declare (Definitions Schema) NamedSchema
142143 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
143144
144145-- | Convert a type into a schema and declare all used schema definitions.
@@ -582,15 +583,32 @@ genericToNamedSchemaBoundedIntegral opts proxy
582583 = NamedSchema (gdatatypeSchemaName opts (Proxy :: Proxy d)) (toSchemaBoundedIntegral proxy)
583584
584585-- | A configurable generic @'Schema'@ creator.
585−genericDeclareSchema :: (Generic a, GToSchema (Rep a)) => SchemaOptions -> proxy a -> Declare (Definitions Schema) Schema
586−genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchema opts proxy
586+genericDeclareSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareSchemaUnrestricted") =>
587+ SchemaOptions -> proxy a -> Declare (Definitions Schema) Schema
588+genericDeclareSchema = genericDeclareSchemaUnrestricted
587589
588590-- | A configurable generic @'NamedSchema'@ creator.
589591-- This function applied to @'defaultSchemaOptions'@
590592-- is used as the default for @'declareNamedSchema'@
591593-- when the type is an instance of @'Generic'@.
592−genericDeclareNamedSchema :: forall a proxy. (Generic a, GToSchema (Rep a)) => SchemaOptions -> proxy a -> Declare (Definitions Schema) NamedSchema
593−genericDeclareNamedSchema opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
594+genericDeclareNamedSchema :: forall a proxy. (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
595+ SchemaOptions -> proxy a -> Declare (Definitions Schema) NamedSchema
596+genericDeclareNamedSchema = genericDeclareNamedSchemaUnrestricted
597+
598+-- | A configurable generic @'Schema'@ creator.
599+--
600+-- Unlike 'genericDeclareSchema' also works for mixed sum types.
601+-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
602+genericDeclareSchemaUnrestricted :: (Generic a, GToSchema (Rep a)) => SchemaOptions -> proxy a -> Declare (Definitions Schema) Schema
603+genericDeclareSchemaUnrestricted opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchemaUnrestricted opts proxy
604+
605+-- | A configurable generic @'NamedSchema'@ creator.
606+--
607+-- Unlike 'genericDeclareNamedSchema' also works for mixed sum types.
608+-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
609+genericDeclareNamedSchemaUnrestricted :: forall a proxy. (Generic a, GToSchema (Rep a)) =>
610+ SchemaOptions -> proxy a -> Declare (Definitions Schema) NamedSchema
611+genericDeclareNamedSchemaUnrestricted opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
594612
595613gdatatypeSchemaName :: forall proxy d. Datatype d => SchemaOptions -> proxy d -> Maybe T.Text
596614gdatatypeSchemaName opts _ = case name of
@@ -711,9 +729,7 @@ instance OVERLAPPABLE_ ToSchema c => GToSchema (K1 i c) where
711729
712730instance ( GSumToSchema f
713731 , GSumToSchema g
714− , LegalShape (GenericShape (f :+: g))
715−
716− ) => GToSchema (f :+: g)
732+ ) => GToSchema (f :+: g)
717733 where
718734 gdeclareNamedSchema = gdeclareNamedSumSchema
719735
src/Data/Swagger/Internal/TypeShape.hs+47−48
@@ -1,60 +1,59 @@
1−
2−{-# LANGUAGE KindSignatures #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE TypeFamilies #-}
5−{-# LANGUAGE TypeOperators #-}
6−{-# LANGUAGE UndecidableInstances #-}
7−
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE KindSignatures #-}
4+{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE UndecidableInstances #-}
87
98module Data.Swagger.Internal.TypeShape where
109
11−
1210import Data.Proxy
1311import GHC.Generics
1412import GHC.TypeLits
15−
16−data TypeShape = EnumShape
17− | NestedShape
18− | IlegalShape
19−
20−
21−type family ProdCombine (a :: TypeShape ) (b :: TypeShape) :: TypeShape where
22−
23− ProdCombine IlegalShape b = IlegalShape
24− ProdCombine a IlegalShape = IlegalShape
25− ProdCombine a b = NestedShape
26−
27−
28−type family SumCombine (a :: TypeShape ) (b :: TypeShape) :: TypeShape where
29−
30− SumCombine EnumShape EnumShape = EnumShape
31− SumCombine NestedShape NestedShape = NestedShape
32− SumCombine a b = IlegalShape
33−
34−
35−
36−
37−class LegalShape (a :: TypeShape) where
38−
39−instance LegalShape EnumShape
40−instance LegalShape NestedShape
41−
42−instance TypeError
43− ( Text "Cannot auto derive swagger class, for that to be possible, make sure that any constructor "
44− :$$: Text "for this type, only holds 0 arguments if there are no other contructors with more than 0 arguments,"
45− :$$: Text "otherwise, every constructor have to hold at least 1 argument"
46− ) => LegalShape IlegalShape
47−
48−
49−
50−type family GenericShape ( g :: * -> * ) :: TypeShape
51−
13+import GHC.Exts (Constraint)
14+
15+-- | Shape of a datatype.
16+data TypeShape
17+ = Enumeration -- ^ A simple enumeration.
18+ | SumOfProducts -- ^ A product or a sum of non-unit products.
19+ | Mixed -- ^ Mixed sum type with both unit and non-unit constructors.
20+
21+-- | A combined shape for a product type.
22+type family ProdCombine (a :: TypeShape) (b :: TypeShape) :: TypeShape where
23+ ProdCombine Mixed b = Mixed -- technically this cannot happen since Haskell types are sums of products
24+ ProdCombine a Mixed = Mixed -- technically this cannot happen since Haskell types are sums of products
25+ ProdCombine a b = SumOfProducts
26+
27+-- | A combined shape for a sum type.
28+type family SumCombine (a :: TypeShape) (b :: TypeShape) :: TypeShape where
29+ SumCombine Enumeration Enumeration = Enumeration
30+ SumCombine SumOfProducts SumOfProducts = SumOfProducts
31+ SumCombine a b = Mixed
32+
33+type family TypeHasSimpleShape t (f :: Symbol) :: Constraint where
34+ TypeHasSimpleShape t f = GenericHasSimpleShape t f (GenericShape (Rep t))
35+
36+type family GenericHasSimpleShape t (f :: Symbol) (s :: TypeShape) :: Constraint where
37+ GenericHasSimpleShape t f Enumeration = ()
38+ GenericHasSimpleShape t f SumOfProducts = ()
39+ GenericHasSimpleShape t f Mixed =
40+ TypeError
41+ ( Text "Cannot derive Generic-based Swagger Schema for " :<>: ShowType t
42+ :$$: ShowType t :<>: Text " is a mixed sum type (has both unit and non-unit constructors)."
43+ :$$: Text "Swagger does not have a good representation for these types."
44+ :$$: Text "Use " :<>: Text f :<>: Text " if you want to derive schema"
45+ :$$: Text "that matches aeson's Generic-based toJSON,"
46+ :$$: Text "but that's not supported by some Swagger tools."
47+ )
48+
49+-- | Infer a 'TypeShape' for a generic representation of a type.
50+type family GenericShape (g :: * -> *) :: TypeShape
5251
5352type instance GenericShape (f :*: g) = ProdCombine (GenericShape f) (GenericShape g)
5453type instance GenericShape (f :+: g) = SumCombine (GenericShape f) (GenericShape g)
5554type instance GenericShape (D1 d f) = GenericShape f
56−type instance GenericShape (C1 c U1) = EnumShape
57−type instance GenericShape (C1 c (S1 s f)) = NestedShape
58−type instance GenericShape (C1 c (f :*: g)) = NestedShape
55+type instance GenericShape (C1 c U1) = Enumeration
56+type instance GenericShape (C1 c (S1 s f)) = SumOfProducts
57+type instance GenericShape (C1 c (f :*: g)) = SumOfProducts
5958
6059
src/Data/Swagger/Schema.hs+4
@@ -22,6 +22,10 @@ module Data.Swagger.Schema (
2222 paramSchemaToNamedSchema,
2323 paramSchemaToSchema,
2424
25+ -- ** Unrestricted versions
26+ genericDeclareNamedSchemaUnrestricted,
27+ genericDeclareSchemaUnrestricted,
28+
2529 -- * Schema templates
2630 passwordSchema,
2731 binarySchema,