@@ -22,12 +22,13 @@ module Data.Swagger.Internal.Schema where
22import Prelude ()
22import Prelude ()
23import Prelude.Compat
23import Prelude.Compat
25−import Control.Lens
25+import Control.Lens hiding (allOf)
26import Data.Data.Lens (template)
26import Data.Data.Lens (template)
28import Control.Monad
28import Control.Monad
29import Control.Monad.Writer
29import Control.Monad.Writer
30−import Data.Aeson (ToJSON (..), ToJSONKey (..), ToJSONKeyFunction (..), Value (..), Object(..))
30+import Data.Aeson (Object (..), SumEncoding (..), ToJSON (..), ToJSONKey (..),
31+ ToJSONKeyFunction (..), Value (..))
31import Data.Char
32import Data.Char
32import Data.Data (Data)
33import Data.Data (Data)
33import Data.Foldable (traverse_)
34import Data.Foldable (traverse_)
@@ -41,6 +42,7 @@ import Data.IntSet (IntSet)
41import Data.IntMap (IntMap)
42import Data.IntMap (IntMap)
42import Data.List.NonEmpty.Compat (NonEmpty)
43import Data.List.NonEmpty.Compat (NonEmpty)
43import Data.Map (Map)
44import Data.Map (Map)
45+import Data.Maybe (fromMaybe)
44import Data.Proxy
46import Data.Proxy
45import Data.Scientific (Scientific)
47import Data.Scientific (Scientific)
46import Data.Fixed (Fixed, HasResolution, Pico)
48import Data.Fixed (Fixed, HasResolution, Pico)
@@ -135,7 +137,7 @@ class ToSchema a where
135 -- Note that the schema itself is included in definitions
137 -- Note that the schema itself is included in definitions
136 -- only if it is recursive (and thus needs its definition in scope).
138 -- only if it is recursive (and thus needs its definition in scope).
137 declareNamedSchema :: Proxy a -> Declare (Definitions Schema) NamedSchema
139 declareNamedSchema :: Proxy a -> Declare (Definitions Schema) NamedSchema
138− default declareNamedSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
140+ default declareNamedSchema :: (Generic a, GToSchema (Rep a)) =>
139 Proxy a -> Declare (Definitions Schema) NamedSchema
141 Proxy a -> Declare (Definitions Schema) NamedSchema
140 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
142 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
@@ -443,7 +445,9 @@ instance HasResolution a => ToSchema (Fixed a) where declareNamedSchema = plain
443instance ToSchema a => ToSchema (Maybe a) where
445instance ToSchema a => ToSchema (Maybe a) where
444 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
446 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
446−instance (ToSchema a, ToSchema b) => ToSchema (Either a b)
448+instance (ToSchema a, ToSchema b) => ToSchema (Either a b) where
449+ -- To match Aeson instance
450+ declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions { sumEncoding = ObjectWithSingleField }
448instance ToSchema () where
452instance ToSchema () where
449 declareNamedSchema _ = pure (NamedSchema Nothing nullarySchema)
453 declareNamedSchema _ = pure (NamedSchema Nothing nullarySchema)
@@ -637,32 +641,17 @@ toSchemaBoundedEnumKeyMapping :: forall map key value.
637toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
641toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
639-- | A configurable generic @'Schema'@ creator.
643-- | A configurable generic @'Schema'@ creator.
640−genericDeclareSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareSchemaUnrestricted") =>
644+genericDeclareSchema :: (Generic a, GToSchema (Rep a)) =>
641 SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
645 SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
642−genericDeclareSchema = genericDeclareSchemaUnrestricted
646+genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchema opts proxy
644-- | A configurable generic @'NamedSchema'@ creator.
648-- | A configurable generic @'NamedSchema'@ creator.
645-- This function applied to @'defaultSchemaOptions'@
649-- This function applied to @'defaultSchemaOptions'@
646-- is used as the default for @'declareNamedSchema'@
650-- is used as the default for @'declareNamedSchema'@
647-- when the type is an instance of @'Generic'@.
651-- when the type is an instance of @'Generic'@.
648−genericDeclareNamedSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
652+genericDeclareNamedSchema :: forall a. (Generic a, GToSchema (Rep a)) =>
649 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
653 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
650−genericDeclareNamedSchema = genericDeclareNamedSchemaUnrestricted
652−-- | A configurable generic @'Schema'@ creator.
654−-- Unlike 'genericDeclareSchema' also works for mixed sum types.
655−-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
656−genericDeclareSchemaUnrestricted :: (Generic a, GToSchema (Rep a)) => SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
657−genericDeclareSchemaUnrestricted opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchemaUnrestricted opts proxy
659−-- | A configurable generic @'NamedSchema'@ creator.
661−-- Unlike 'genericDeclareNamedSchema' also works for mixed sum types.
662−-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
663−genericDeclareNamedSchemaUnrestricted :: forall a. (Generic a, GToSchema (Rep a)) =>
664− SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
665−genericDeclareNamedSchemaUnrestricted opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
654+genericDeclareNamedSchema opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
667-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
656-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
668genericNameSchema :: forall a d f.
657genericNameSchema :: forall a d f.
@@ -791,56 +780,98 @@ instance ( GSumToSchema f
791 , GSumToSchema g
780 , GSumToSchema g
792 ) => GToSchema (f :+: g)
781 ) => GToSchema (f :+: g)
783+ -- Aeson does not unwrap unary record in sum types.
794− gdeclareNamedSchema = gdeclareNamedSumSchema
784+ gdeclareNamedSchema opts p s = gdeclareNamedSumSchema (opts { unwrapUnaryRecords = False } )p s
796gdeclareNamedSumSchema :: GSumToSchema f => SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
786gdeclareNamedSumSchema :: GSumToSchema f => SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
797−gdeclareNamedSumSchema opts proxy s
787+gdeclareNamedSumSchema opts proxy _
798− | allNullaryToStringTag opts && allNullary = pure $ unnamed (toStringTag sumSchema)
788+ | allNullaryToStringTag opts && allNullary = pure $ unnamed (toStringTag sumSchemas)
799− | otherwise = (unnamed . fst) <$> runWriterT declareSumSchema
789+ | otherwise = do
790+ (schemas, _) <- runWriterT declareSumSchema
791+ return $ unnamed $ mempty
792+ & type_ ?~ SwaggerObject
793+ & oneOf ?~ (snd <$> schemas)
801− declareSumSchema = gsumToSchema opts proxy s
795+ declareSumSchema = gsumToSchema opts proxy
802− (sumSchema, All allNullary) = undeclare (runWriterT declareSumSchema)
796+ (sumSchemas, All allNullary) = undeclare (runWriterT declareSumSchema)
804− toStringTag schema = mempty
798+ toStringTag schemas = mempty
805 & type_ ?~ SwaggerString
799 & type_ ?~ SwaggerString
806− & enum_ ?~ map toJSON (schema ^.. properties.ifolded.asIndex)
800+ & enum_ ?~ map (String . fst) sumSchemas
808type AllNullary = All
802type AllNullary = All
810class GSumToSchema (f :: * -> *) where
804class GSumToSchema (f :: * -> *) where
811− gsumToSchema :: SchemaOptions -> Proxy f -> Schema -> WriterT AllNullary (Declare (Definitions Schema)) Schema
805+ gsumToSchema :: SchemaOptions -> Proxy f -> WriterT AllNullary (Declare (Definitions Schema)) [(T.Text, Referenced Schema)]
813instance (GSumToSchema f, GSumToSchema g) => GSumToSchema (f :+: g) where
807instance (GSumToSchema f, GSumToSchema g) => GSumToSchema (f :+: g) where
808+ gsumToSchema opts _ =
814− gsumToSchema opts _ = gsumToSchema opts (Proxy :: Proxy f) >=> gsumToSchema opts (Proxy :: Proxy g)
809+ (<>) <$> gsumToSchema opts (Proxy :: Proxy f) <*> gsumToSchema opts (Proxy :: Proxy g)
811+-- | Convert one component of the sum to schema, to be later combined with @oneOf@.
816gsumConToSchemaWith :: forall c f. (GToSchema (C1 c f), Constructor c) =>
812gsumConToSchemaWith :: forall c f. (GToSchema (C1 c f), Constructor c) =>
817− Referenced Schema -> SchemaOptions -> Proxy (C1 c f) -> Schema -> Schema
813+ Maybe (Referenced Schema) -> SchemaOptions -> Proxy (C1 c f) -> (T.Text, Referenced Schema)
818−gsumConToSchemaWith ref opts _ schema = schema
814+gsumConToSchemaWith ref opts _ = (tag, schema)
819− & type_ ?~ SwaggerObject
820− & properties . at tag ?~ ref
821− & maxProperties ?~ 1
822− & minProperties ?~ 1
816+ schema = case sumEncoding opts of
817+ TaggedObject tagField contentsField ->
819+ -- If subschema is an object and constructor is a record, we add tag directly
820+ -- to the record, as Aeson does it.
821+ Just (Inline sub) | sub ^. type_ == Just SwaggerObject && isRecord -> Inline $ sub
822+ & required <>~ [T.pack tagField]
823+ & properties . at (T.pack tagField) ?~ (Inline $ mempty & type_ ?~ SwaggerString & enum_ ?~ [String tag])
825+ -- If it is not a record, we need to put subschema into "contents" field.
826+ _ | not isRecord -> Inline $ mempty
827+ & type_ ?~ SwaggerObject
828+ & required .~ [T.pack tagField]
829+ & properties . at (T.pack tagField) ?~ (Inline $ mempty & type_ ?~ SwaggerString & enum_ ?~ [String tag])
830+ -- If constructor is nullary, there is no content.
832+ Just r -> (properties . at (T.pack contentsField) ?~ r) . (required <>~ [T.pack contentsField])
835+ -- In the remaining cases we combine "tag" object and "contents" object using allOf.
836+ _ -> Inline $ mempty
837+ & type_ ?~ SwaggerObject
838+ & allOf ?~ [Inline $ mempty
839+ & type_ ?~ SwaggerObject
840+ & required .~ (T.pack tagField : if isRecord then [] else [T.pack contentsField])
841+ & properties . at (T.pack tagField) ?~ (Inline $ mempty & type_ ?~ SwaggerString & enum_ ?~ [String tag])]
843+ then allOf . _Just <>~ [refOrNullary]
844+ else allOf . _Just <>~ [Inline $ mempty & type_ ?~ SwaggerObject & properties . at (T.pack contentsField) ?~ refOrNullary]
845+ UntaggedValue -> refOrEnum -- Aeson encodes nullary constructors as strings in this case.
846+ ObjectWithSingleField -> Inline $ mempty
847+ & type_ ?~ SwaggerObject
849+ & properties . at tag ?~ refOrNullary
850+ TwoElemArray -> error "unrepresentable in OpenAPI 3"
824 tag = T.pack (constructorTagModifier opts (conName (Proxy3 :: Proxy3 c f p)))
852 tag = T.pack (constructorTagModifier opts (conName (Proxy3 :: Proxy3 c f p)))
853+ isRecord = conIsRecord (Proxy3 :: Proxy3 c f p)
854+ refOrNullary = fromMaybe (Inline nullarySchema) ref
855+ refOrEnum = fromMaybe (Inline $ mempty & type_ ?~ SwaggerString & enum_ ?~ [String tag]) ref
826gsumConToSchema :: (GToSchema (C1 c f), Constructor c) =>
857gsumConToSchema :: (GToSchema (C1 c f), Constructor c) =>
827− SchemaOptions -> Proxy (C1 c f) -> Schema -> Declare (Definitions Schema) Schema
858+ SchemaOptions -> Proxy (C1 c f) -> Declare (Definitions Schema) [(T.Text, Referenced Schema)]
828−gsumConToSchema opts proxy schema = do
859+gsumConToSchema opts proxy = do
829 ref <- gdeclareSchemaRef opts proxy
860 ref <- gdeclareSchemaRef opts proxy
830− return $ gsumConToSchemaWith ref opts proxy schema
861+ return [gsumConToSchemaWith (Just ref) opts proxy]
832instance {-# OVERLAPPABLE #-} (Constructor c, GToSchema f) => GSumToSchema (C1 c f) where
863instance {-# OVERLAPPABLE #-} (Constructor c, GToSchema f) => GSumToSchema (C1 c f) where
833− gsumToSchema opts proxy schema = do
864+ gsumToSchema opts proxy = do
834 tell (All False)
865 tell (All False)
835− lift $ gsumConToSchema opts proxy schema
866+ lift $ gsumConToSchema opts proxy
837instance (Constructor c, Selector s, GToSchema f) => GSumToSchema (C1 c (S1 s f)) where
868instance (Constructor c, Selector s, GToSchema f) => GSumToSchema (C1 c (S1 s f)) where
838− gsumToSchema opts proxy schema = do
869+ gsumToSchema opts proxy = do
839 tell (All False)
870 tell (All False)
840− lift $ gsumConToSchema opts proxy schema
871+ lift $ gsumConToSchema opts proxy
842instance Constructor c => GSumToSchema (C1 c U1) where
873instance Constructor c => GSumToSchema (C1 c U1) where
843− gsumToSchema opts proxy = pure . gsumConToSchemaWith (Inline nullarySchema) opts proxy
874+ gsumToSchema opts proxy = pure $ (:[]) $ gsumConToSchemaWith Nothing opts proxy
845data Proxy2 a b = Proxy2
876data Proxy2 a b = Proxy2