@@ -22,12 +22,13 @@ module Data.Swagger.Internal.Schema where
2222import Prelude ()
2323import Prelude.Compat
2424
25−import Control.Lens
25+import Control.Lens hiding (allOf)
2626import Data.Data.Lens (template)
2727
2828import Control.Monad
2929import Control.Monad.Writer
30−import Data.Aeson (ToJSON (..), ToJSONKey (..), ToJSONKeyFunction (..), Value (..), Object(..))
30+import Data.Aeson (Object (..), SumEncoding (..), ToJSON (..), ToJSONKey (..),
31+ ToJSONKeyFunction (..), Value (..))
3132import Data.Char
3233import Data.Data (Data)
3334import Data.Foldable (traverse_)
@@ -41,6 +42,7 @@ import Data.IntSet (IntSet)
4142import Data.IntMap (IntMap)
4243import Data.List.NonEmpty.Compat (NonEmpty)
4344import Data.Map (Map)
45+import Data.Maybe (fromMaybe)
4446import Data.Proxy
4547import Data.Scientific (Scientific)
4648import Data.Fixed (Fixed, HasResolution, Pico)
@@ -135,7 +137,7 @@ class ToSchema a where
135137 -- Note that the schema itself is included in definitions
136138 -- only if it is recursive (and thus needs its definition in scope).
137139 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)) =>
139141 Proxy a -> Declare (Definitions Schema) NamedSchema
140142 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
141143
@@ -443,7 +445,9 @@ instance HasResolution a => ToSchema (Fixed a) where declareNamedSchema = plain
443445instance ToSchema a => ToSchema (Maybe a) where
444446 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
445447
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 }
447451
448452instance ToSchema () where
449453 declareNamedSchema _ = pure (NamedSchema Nothing nullarySchema)
@@ -637,32 +641,17 @@ toSchemaBoundedEnumKeyMapping :: forall map key value.
637641toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
638642
639643-- | A configurable generic @'Schema'@ creator.
640−genericDeclareSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareSchemaUnrestricted") =>
644+genericDeclareSchema :: (Generic a, GToSchema (Rep a)) =>
641645 SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
642−genericDeclareSchema = genericDeclareSchemaUnrestricted
646+genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchema opts proxy
643647
644648-- | A configurable generic @'NamedSchema'@ creator.
645649-- This function applied to @'defaultSchemaOptions'@
646650-- is used as the default for @'declareNamedSchema'@
647651-- 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)) =>
649653 SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
650−genericDeclareNamedSchema = genericDeclareNamedSchemaUnrestricted
651−
652−-- | A configurable generic @'Schema'@ creator.
653−--
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
658−
659−-- | A configurable generic @'NamedSchema'@ creator.
660−--
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
666655
667656-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
668657genericNameSchema :: forall a d f.
@@ -791,56 +780,98 @@ instance ( GSumToSchema f
791780 , GSumToSchema g
792781 ) => GToSchema (f :+: g)
793782 where
794− gdeclareNamedSchema = gdeclareNamedSumSchema
783+ -- Aeson does not unwrap unary record in sum types.
784+ gdeclareNamedSchema opts p s = gdeclareNamedSumSchema (opts { unwrapUnaryRecords = False } )p s
795785
796786gdeclareNamedSumSchema :: GSumToSchema f => SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
797−gdeclareNamedSumSchema opts proxy s
798− | allNullaryToStringTag opts && allNullary = pure $ unnamed (toStringTag sumSchema)
799− | otherwise = (unnamed . fst) <$> runWriterT declareSumSchema
787+gdeclareNamedSumSchema opts proxy _
788+ | allNullaryToStringTag opts && allNullary = pure $ unnamed (toStringTag sumSchemas)
789+ | otherwise = do
790+ (schemas, _) <- runWriterT declareSumSchema
791+ return $ unnamed $ mempty
792+ & type_ ?~ SwaggerObject
793+ & oneOf ?~ (snd <$> schemas)
800794 where
801− declareSumSchema = gsumToSchema opts proxy s
802− (sumSchema, All allNullary) = undeclare (runWriterT declareSumSchema)
795+ declareSumSchema = gsumToSchema opts proxy
796+ (sumSchemas, All allNullary) = undeclare (runWriterT declareSumSchema)
803797
804− toStringTag schema = mempty
798+ toStringTag schemas = mempty
805799 & type_ ?~ SwaggerString
806− & enum_ ?~ map toJSON (schema ^.. properties.ifolded.asIndex)
800+ & enum_ ?~ map (String . fst) sumSchemas
807801
808802type AllNullary = All
809803
810804class 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)]
812806
813807instance (GSumToSchema f, GSumToSchema g) => GSumToSchema (f :+: g) where
814− gsumToSchema opts _ = gsumToSchema opts (Proxy :: Proxy f) >=> gsumToSchema opts (Proxy :: Proxy g)
808+ gsumToSchema opts _ =
809+ (<>) <$> gsumToSchema opts (Proxy :: Proxy f) <*> gsumToSchema opts (Proxy :: Proxy g)
815810
811+-- | Convert one component of the sum to schema, to be later combined with @oneOf@.
816812gsumConToSchemaWith :: forall c f. (GToSchema (C1 c f), Constructor c) =>
817− Referenced Schema -> SchemaOptions -> Proxy (C1 c f) -> Schema -> Schema
818−gsumConToSchemaWith ref opts _ schema = schema
819− & type_ ?~ SwaggerObject
820− & properties . at tag ?~ ref
821− & maxProperties ?~ 1
822− & minProperties ?~ 1
813+ Maybe (Referenced Schema) -> SchemaOptions -> Proxy (C1 c f) -> (T.Text, Referenced Schema)
814+gsumConToSchemaWith ref opts _ = (tag, schema)
823815 where
816+ schema = case sumEncoding opts of
817+ TaggedObject tagField contentsField ->
818+ case ref of
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])
824+
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.
831+ & case ref of
832+ Just r -> (properties . at (T.pack contentsField) ?~ r) . (required <>~ [T.pack contentsField])
833+ Nothing -> id
834+
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])]
842+ & if isRecord
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
848+ & required .~ [tag]
849+ & properties . at tag ?~ refOrNullary
850+ TwoElemArray -> error "unrepresentable in OpenAPI 3"
851+
824852 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
825856
826857gsumConToSchema :: (GToSchema (C1 c f), Constructor c) =>
827− SchemaOptions -> Proxy (C1 c f) -> Schema -> Declare (Definitions Schema) Schema
828−gsumConToSchema opts proxy schema = do
858+ SchemaOptions -> Proxy (C1 c f) -> Declare (Definitions Schema) [(T.Text, Referenced Schema)]
859+gsumConToSchema opts proxy = do
829860 ref <- gdeclareSchemaRef opts proxy
830− return $ gsumConToSchemaWith ref opts proxy schema
861+ return [gsumConToSchemaWith (Just ref) opts proxy]
831862
832863instance {-# OVERLAPPABLE #-} (Constructor c, GToSchema f) => GSumToSchema (C1 c f) where
833− gsumToSchema opts proxy schema = do
864+ gsumToSchema opts proxy = do
834865 tell (All False)
835− lift $ gsumConToSchema opts proxy schema
866+ lift $ gsumConToSchema opts proxy
836867
837868instance (Constructor c, Selector s, GToSchema f) => GSumToSchema (C1 c (S1 s f)) where
838− gsumToSchema opts proxy schema = do
869+ gsumToSchema opts proxy = do
839870 tell (All False)
840− lift $ gsumConToSchema opts proxy schema
871+ lift $ gsumConToSchema opts proxy
841872
842873instance 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
844875
845876data Proxy2 a b = Proxy2
846877