@@ -1,4 +1,5 @@
11{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ConstraintKinds #-}
23{-# LANGUAGE DefaultSignatures #-}
34{-# LANGUAGE FlexibleContexts #-}
45{-# LANGUAGE FlexibleInstances #-}
@@ -13,6 +14,13 @@
1314{-# LANGUAGE TypeFamilies #-}
1415{-# LANGUAGE TypeOperators #-}
1516{-# LANGUAGE TypeSynonymInstances #-}
17+{-# LANGUAGE UndecidableInstances #-}
18+#if __GLASGOW_HASKELL__ >= 800
19+-- Few generics related redundant constraints
20+{-# OPTIONS_GHC -Wno-redundant-constraints #-}
21+-- For TypeErrors
22+{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
23+#endif
1624#include "overlapping-compat.h"
1725module Data.Swagger.Internal.Schema where
1826
@@ -22,27 +30,25 @@ import Prelude.Compat
2230import Control.Lens
2331import Data.Data.Lens (template)
2432
25−import Control.Applicative
2633import Control.Monad
2734import Control.Monad.Writer
28−import Data.Aeson
29−import qualified Data.Aeson.Types as Aeson
35+import Data.Aeson (ToJSON (..), ToJSONKey (..), ToJSONKeyFunction (..), Value (..), Object(..))
3036import Data.Char
3137import Data.Data (Data)
3238import Data.Foldable (traverse_)
33−import Data.Function (on)
3439import Data.HashMap.Strict (HashMap)
3540import qualified Data.HashMap.Strict as HashMap
3641import "unordered-containers" Data.HashSet (HashSet)
3742import qualified "unordered-containers" Data.HashSet as HashSet
38−import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
3943import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
4044import Data.Int
4145import Data.IntSet (IntSet)
4246import Data.IntMap (IntMap)
47+import Data.List.NonEmpty.Compat (NonEmpty)
4348import Data.Map (Map)
4449import Data.Proxy
4550import Data.Scientific (Scientific)
51+import Data.Fixed (Fixed, HasResolution, Pico)
4652import Data.Set (Set)
4753import qualified Data.Text as T
4854import qualified Data.Text.Lazy as TL
@@ -51,17 +57,25 @@ import qualified Data.Vector as V
5157import qualified Data.Vector.Primitive as VP
5258import qualified Data.Vector.Storable as VS
5359import qualified Data.Vector.Unboxed as VU
60+import Data.Version (Version)
61+import Numeric.Natural.Compat (Natural)
5462import Data.Word
5563import GHC.Generics
64+import qualified Data.UUID.Types as UUID
5665
5766import Data.Swagger.Declare
5867import Data.Swagger.Internal
5968import Data.Swagger.Internal.ParamSchema (ToParamSchema(..))
6069import Data.Swagger.Lens hiding (name, schema)
70+import qualified Data.Swagger.Lens as Swagger
6171import Data.Swagger.SchemaOptions
72+import Data.Swagger.Internal.TypeShape
6273
63−#ifdef __DOCTEST__
64−import Data.Swagger.Lens (name, schema)
74+#if __GLASGOW_HASKELL__ < 800
75+#else
76+import qualified Data.ByteString as BS
77+import qualified Data.ByteString.Lazy as BSL
78+import GHC.TypeLits (TypeError, ErrorMessage(..))
6579#endif
6680
6781unnamed :: Schema -> NamedSchema
@@ -88,14 +102,16 @@ rename name (NamedSchema _ schema) = NamedSchema name schema
88102-- {-\# LANGUAGE OverloadedLists \#-} -- allows to write 'Map' and 'HashMap' as lists
89103--
90104-- import Control.Lens
105+-- import Data.Proxy
106+-- import Data.Swagger
91107--
92108-- data Coord = Coord { x :: Double, y :: Double }
93109--
94110-- instance ToSchema Coord where
95−-- declareNamedSchema = do
111+-- declareNamedSchema _ = do
96112-- doubleSchema <- declareSchemaRef (Proxy :: Proxy Double)
97−-- return $ NamedSchema (Just \"Coord\")) $ mempty
98−-- & type_ .~ SwaggerObject
113+-- return $ NamedSchema (Just \"Coord\") $ mempty
114+-- & type_ ?~ SwaggerObject
99115-- & properties .~
100116-- [ (\"x\", doubleSchema)
101117-- , (\"y\", doubleSchema)
@@ -126,12 +142,17 @@ class ToSchema a where
126142 -- together with all used definitions.
127143 -- Note that the schema itself is included in definitions
128144 -- only if it is recursive (and thus needs its definition in scope).
129− declareNamedSchema :: proxy a -> Declare (Definitions Schema) NamedSchema
130− default declareNamedSchema :: (Generic a, GToSchema (Rep a)) => proxy a -> Declare (Definitions Schema) NamedSchema
145+ declareNamedSchema :: Proxy a -> Declare (Definitions Schema) NamedSchema
146+ default declareNamedSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
147+ Proxy a -> Declare (Definitions Schema) NamedSchema
131148 declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
132149
150+instance ToSchema TimeOfDay where
151+ declareNamedSchema _ = pure $ named "TimeOfDay" $ timeSchema "hh:MM:ss"
152+ & example ?~ toJSON (TimeOfDay 12 33 15)
153+
133154-- | Convert a type into a schema and declare all used schema definitions.
134−declareSchema :: ToSchema a => proxy a -> Declare (Definitions Schema) Schema
155+declareSchema :: ToSchema a => Proxy a -> Declare (Definitions Schema) Schema
135156declareSchema = fmap _namedSchemaSchema . declareNamedSchema
136157
137158-- | Convert a type into an optionally named schema.
@@ -144,8 +165,8 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema
144165-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
145166-- Just "Day"
146167-- >>> encode (toNamedSchema (Proxy :: Proxy Day) ^. schema)
147−-- "{\"format\":\"date\",\"type\":\"string\"}"
148−toNamedSchema :: ToSchema a => proxy a -> NamedSchema
168+-- "{\"example\":\"2016-07-22\",\"format\":\"date\",\"type\":\"string\"}"
169+toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
149170toNamedSchema = undeclare . declareNamedSchema
150171
151172-- | Get type's schema name according to its @'ToSchema'@ instance.
@@ -155,7 +176,7 @@ toNamedSchema = undeclare . declareNamedSchema
155176--
156177-- >>> schemaName (Proxy :: Proxy UTCTime)
157178-- Just "UTCTime"
158−schemaName :: ToSchema a => proxy a -> Maybe T.Text
179+schemaName :: ToSchema a => Proxy a -> Maybe T.Text
159180schemaName = _namedSchemaName . toNamedSchema
160181
161182-- | Convert a type into a schema.
@@ -165,7 +186,7 @@ schemaName = _namedSchemaName . toNamedSchema
165186--
166187-- >>> encode $ toSchema (Proxy :: Proxy [Day])
167188-- "{\"items\":{\"$ref\":\"#/definitions/Day\"},\"type\":\"array\"}"
168−toSchema :: ToSchema a => proxy a -> Schema
189+toSchema :: ToSchema a => Proxy a -> Schema
169190toSchema = _namedSchemaSchema . toNamedSchema
170191
171192-- | Convert a type into a referenced schema if possible.
@@ -176,7 +197,7 @@ toSchema = _namedSchemaSchema . toNamedSchema
176197--
177198-- >>> encode $ toSchemaRef (Proxy :: Proxy Day)
178199-- "{\"$ref\":\"#/definitions/Day\"}"
179−toSchemaRef :: ToSchema a => proxy a -> Referenced Schema
200+toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
180201toSchemaRef = undeclare . declareSchemaRef
181202
182203-- | Convert a type into a referenced schema if possible
@@ -186,7 +207,7 @@ toSchemaRef = undeclare . declareSchemaRef
186207-- Schema definitions are typically declared for every referenced schema.
187208-- If @'declareSchemaRef'@ returns a reference, a corresponding schema
188209-- will be declared (regardless of whether it is recusive or not).
189−declareSchemaRef :: ToSchema a => proxy a -> Declare (Definitions Schema) (Referenced Schema)
210+declareSchemaRef :: ToSchema a => Proxy a -> Declare (Definitions Schema) (Referenced Schema)
190211declareSchemaRef proxy = do
191212 case toNamedSchema proxy of
192213 NamedSchema (Just name) schema -> do
@@ -244,11 +265,11 @@ inlineAllSchemas = inlineSchemasWhen (const True)
244265-- | Convert a type into a schema without references.
245266--
246267-- >>> encode $ toInlinedSchema (Proxy :: Proxy [Day])
247−-- "{\"items\":{\"format\":\"date\",\"type\":\"string\"},\"type\":\"array\"}"
268+-- "{\"items\":{\"example\":\"2016-07-22\",\"format\":\"date\",\"type\":\"string\"},\"type\":\"array\"}"
248269--
249270-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
250271-- when inlining recursive schemas.
251−toInlinedSchema :: ToSchema a => proxy a -> Schema
272+toInlinedSchema :: ToSchema a => Proxy a -> Schema
252273toInlinedSchema proxy = inlineAllSchemas defs schema
253274 where
254275 (defs, schema) = runDeclare (declareSchema proxy) mempty
@@ -277,20 +298,20 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs
277298-- | Default schema for binary data (any sequence of octets).
278299binarySchema :: Schema
279300binarySchema = mempty
280− & type_ .~ SwaggerString
301+ & type_ ?~ SwaggerString
281302 & format ?~ "binary"
282303
283304-- | Default schema for binary data (base64 encoded).
284305byteSchema :: Schema
285306byteSchema = mempty
286− & type_ .~ SwaggerString
307+ & type_ ?~ SwaggerString
287308 & format ?~ "byte"
288309
289310-- | Default schema for password string.
290311-- @"password"@ format is used to hint UIs the input needs to be obscured.
291312passwordSchema :: Schema
292313passwordSchema = mempty
293− & type_ .~ SwaggerString
314+ & type_ ?~ SwaggerString
294315 & format ?~ "password"
295316
296317-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
@@ -316,12 +337,12 @@ sketchSchema = sketch . toJSON
316337 sketch js@(Bool _) = go js
317338 sketch js = go js & example ?~ js
318339
319− go Null = mempty & type_ .~ SwaggerNull
320− go js@(Bool _) = mempty & type_ .~ SwaggerBoolean
321− go js@(String s) = mempty & type_ .~ SwaggerString
322− go js@(Number n) = mempty & type_ .~ SwaggerNumber
323− go js@(Array xs) = mempty
324− & type_ .~ SwaggerArray
340+ go Null = mempty & type_ ?~ SwaggerNull
341+ go (Bool _) = mempty & type_ ?~ SwaggerBoolean
342+ go (String _) = mempty & type_ ?~ SwaggerString
343+ go (Number _) = mempty & type_ ?~ SwaggerNumber
344+ go (Array xs) = mempty
345+ & type_ ?~ SwaggerArray
325346 & items ?~ case ischema of
326347 Just s -> SwaggerItemsObject (Inline s)
327348 _ -> SwaggerItemsArray (map Inline ys)
@@ -330,10 +351,10 @@ sketchSchema = sketch . toJSON
330351 allSame = and ((zipWith (==)) ys (tail ys))
331352
332353 ischema = case ys of
333− (z:zs) | allSame -> Just z
334− _ -> Nothing
335− go js@(Object o) = mempty
336− & type_ .~ SwaggerObject
354+ (z:_) | allSame -> Just z
355+ _ -> Nothing
356+ go (Object o) = mempty
357+ & type_ ?~ SwaggerObject
337358 & required .~ HashMap.keys o
338359 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
339360
@@ -356,24 +377,24 @@ sketchSchema = sketch . toJSON
356377sketchStrictSchema :: ToJSON a => a -> Schema
357378sketchStrictSchema = go . toJSON
358379 where
359− go Null = mempty & type_ .~ SwaggerNull
380+ go Null = mempty & type_ ?~ SwaggerNull
360381 go js@(Bool _) = mempty
361− & type_ .~ SwaggerBoolean
382+ & type_ ?~ SwaggerBoolean
362383 & enum_ ?~ [js]
363384 go js@(String s) = mempty
364− & type_ .~ SwaggerString
385+ & type_ ?~ SwaggerString
365386 & maxLength ?~ fromIntegral (T.length s)
366387 & minLength ?~ fromIntegral (T.length s)
367388 & pattern ?~ s
368389 & enum_ ?~ [js]
369390 go js@(Number n) = mempty
370− & type_ .~ SwaggerNumber
391+ & type_ ?~ SwaggerNumber
371392 & maximum_ ?~ n
372393 & minimum_ ?~ n
373394 & multipleOf ?~ n
374395 & enum_ ?~ [js]
375396 go js@(Array xs) = mempty
376− & type_ .~ SwaggerArray
397+ & type_ ?~ SwaggerArray
377398 & maxItems ?~ fromIntegral sz
378399 & minItems ?~ fromIntegral sz
379400 & items ?~ SwaggerItemsArray (map (Inline . go) (V.toList xs))
@@ -383,7 +404,7 @@ sketchStrictSchema = go . toJSON
383404 sz = length xs
384405 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
385406 go js@(Object o) = mempty
386− & type_ .~ SwaggerObject
407+ & type_ ?~ SwaggerObject
387408 & required .~ names
388409 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
389410 & maxProperties ?~ fromIntegral (length names)
@@ -393,18 +414,19 @@ sketchStrictSchema = go . toJSON
393414 names = HashMap.keys o
394415
395416class GToSchema (f :: * -> *) where
396− gdeclareNamedSchema :: SchemaOptions -> proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
417+ gdeclareNamedSchema :: SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
397418
398419instance OVERLAPPABLE_ ToSchema a => ToSchema [a] where
399420 declareNamedSchema _ = do
400421 ref <- declareSchemaRef (Proxy :: Proxy a)
401422 return $ unnamed $ mempty
402− & type_ .~ SwaggerArray
423+ & type_ ?~ SwaggerArray
403424 & items ?~ SwaggerItemsObject ref
404425
405426instance OVERLAPPING_ ToSchema String where declareNamedSchema = plain . paramSchemaToSchema
406427instance ToSchema Bool where declareNamedSchema = plain . paramSchemaToSchema
407428instance ToSchema Integer where declareNamedSchema = plain . paramSchemaToSchema
429+instance ToSchema Natural where declareNamedSchema = plain . paramSchemaToSchema
408430instance ToSchema Int where declareNamedSchema = plain . paramSchemaToSchema
409431instance ToSchema Int8 where declareNamedSchema = plain . paramSchemaToSchema
410432instance ToSchema Int16 where declareNamedSchema = plain . paramSchemaToSchema
@@ -416,11 +438,16 @@ instance ToSchema Word16 where declareNamedSchema = plain . paramSchemaToSchema
416438instance ToSchema Word32 where declareNamedSchema = plain . paramSchemaToSchema
417439instance ToSchema Word64 where declareNamedSchema = plain . paramSchemaToSchema
418440
419−instance ToSchema Char where declareNamedSchema = plain . paramSchemaToSchema
441+instance ToSchema Char where
442+ declareNamedSchema proxy = plain (paramSchemaToSchema proxy)
443+ & mapped.Swagger.schema.example ?~ toJSON '?'
444+
420445instance ToSchema Scientific where declareNamedSchema = plain . paramSchemaToSchema
421446instance ToSchema Double where declareNamedSchema = plain . paramSchemaToSchema
422447instance ToSchema Float where declareNamedSchema = plain . paramSchemaToSchema
423448
449+instance HasResolution a => ToSchema (Fixed a) where declareNamedSchema = plain . paramSchemaToSchema
450+
424451instance ToSchema a => ToSchema (Maybe a) where
425452 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
426453
@@ -429,6 +456,11 @@ instance (ToSchema a, ToSchema b) => ToSchema (Either a b)
429456instance ToSchema () where
430457 declareNamedSchema _ = pure (NamedSchema Nothing nullarySchema)
431458
459+-- | For 'ToJSON' instance, see <http://hackage.haskell.org/package/uuid-aeson uuid-aeson> package.
460+instance ToSchema UUID.UUID where
461+ declareNamedSchema p = pure $ named "UUID" $ paramSchemaToSchema p
462+ & example ?~ toJSON (UUID.toText UUID.nil)
463+
432464instance (ToSchema a, ToSchema b) => ToSchema (a, b)
433465instance (ToSchema a, ToSchema b, ToSchema c) => ToSchema (a, b, c)
434466instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d) => ToSchema (a, b, c, d)
@@ -438,46 +470,81 @@ instance (ToSchema a, ToSchema b, ToSchema c, ToSchema d, ToSchema e, ToSchema f
438470
439471timeSchema :: T.Text -> Schema
440472timeSchema fmt = mempty
441− & type_ .~ SwaggerString
473+ & type_ ?~ SwaggerString
442474 & format ?~ fmt
443475
444476-- | Format @"date"@ corresponds to @yyyy-mm-dd@ format.
445477instance ToSchema Day where
446− declareNamedSchema _ = pure $ named "Day" (timeSchema "date")
478+ declareNamedSchema _ = pure $ named "Day" $ timeSchema "date"
479+ & example ?~ toJSON (fromGregorian 2016 7 22)
447480
448481-- |
449482-- >>> toSchema (Proxy :: Proxy LocalTime) ^. format
450483-- Just "yyyy-mm-ddThh:MM:ss"
451484instance ToSchema LocalTime where
452− declareNamedSchema _ = pure $ named "LocalTime" (timeSchema "yyyy-mm-ddThh:MM:ss")
485+ declareNamedSchema _ = pure $ named "LocalTime" $ timeSchema "yyyy-mm-ddThh:MM:ss"
486+ & example ?~ toJSON (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0))
453487
454488-- | Format @"date"@ corresponds to @yyyy-mm-ddThh:MM:ss(Z|+hh:MM)@ format.
455489instance ToSchema ZonedTime where
456490 declareNamedSchema _ = pure $ named "ZonedTime" $ timeSchema "date-time"
491+ & example ?~ toJSON (ZonedTime (LocalTime (fromGregorian 2016 7 22) (TimeOfDay 7 40 0)) (hoursToTimeZone 3))
457492
458493instance ToSchema NominalDiffTime where
459− declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy Integer)
494+ declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy Pico)
460495
461496-- |
462497-- >>> toSchema (Proxy :: Proxy UTCTime) ^. format
463498-- Just "yyyy-mm-ddThh:MM:ssZ"
464499instance ToSchema UTCTime where
465− declareNamedSchema _ = pure $ named "UTCTime" (timeSchema "yyyy-mm-ddThh:MM:ssZ")
500+ declareNamedSchema _ = pure $ named "UTCTime" $ timeSchema "yyyy-mm-ddThh:MM:ssZ"
501+ & example ?~ toJSON (UTCTime (fromGregorian 2016 7 22) 0)
466502
467503instance ToSchema T.Text where declareNamedSchema = plain . paramSchemaToSchema
468504instance ToSchema TL.Text where declareNamedSchema = plain . paramSchemaToSchema
469505
506+instance ToSchema Version where declareNamedSchema = plain . paramSchemaToSchema
507+
508+#if __GLASGOW_HASKELL__ < 800
509+#else
510+type family ToSchemaByteStringError bs where
511+ ToSchemaByteStringError bs = TypeError
512+ ( Text "Impossible to have an instance " :<>: ShowType (ToSchema bs) :<>: Text "."
513+ :$$: Text "Please, use a newtype wrapper around " :<>: ShowType bs :<>: Text " instead."
514+ :$$: Text "Consider using byteSchema or binarySchema templates." )
515+
516+instance ToSchemaByteStringError BS.ByteString => ToSchema BS.ByteString where declareNamedSchema = error "impossible"
517+instance ToSchemaByteStringError BSL.ByteString => ToSchema BSL.ByteString where declareNamedSchema = error "impossible"
518+#endif
519+
470520instance ToSchema IntSet where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Set Int))
471521
472522-- | NOTE: This schema does not account for the uniqueness of keys.
473523instance ToSchema a => ToSchema (IntMap a) where
474524 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [(Int, a)])
475525
526+#if MIN_VERSION_aeson(1,0,0)
527+instance (ToJSONKey k, ToSchema k, ToSchema v) => ToSchema (Map k v) where
528+ declareNamedSchema _ = case toJSONKey :: ToJSONKeyFunction k of
529+ ToJSONKeyText _ _ -> declareObjectMapSchema
530+ ToJSONKeyValue _ _ -> declareNamedSchema (Proxy :: Proxy [(k, v)])
531+ where
532+ declareObjectMapSchema = do
533+ schema <- declareSchemaRef (Proxy :: Proxy v)
534+ return $ unnamed $ mempty
535+ & type_ ?~ SwaggerObject
536+ & additionalProperties ?~ AdditionalPropertiesSchema schema
537+
538+instance (ToJSONKey k, ToSchema k, ToSchema v) => ToSchema (HashMap k v) where
539+ declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map k v))
540+
541+#else
542+
476543instance ToSchema a => ToSchema (Map String a) where
477544 declareNamedSchema _ = do
478545 schema <- declareSchemaRef (Proxy :: Proxy a)
479546 return $ unnamed $ mempty
480− & type_ .~ SwaggerObject
547+ & type_ ?~ SwaggerObject
481548 & additionalProperties ?~ schema
482549
483550instance ToSchema a => ToSchema (Map T.Text a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map String a))
@@ -487,6 +554,14 @@ instance ToSchema a => ToSchema (HashMap String a) where declareNamedSchema _ =
487554instance ToSchema a => ToSchema (HashMap T.Text a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map String a))
488555instance ToSchema a => ToSchema (HashMap TL.Text a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Map String a))
489556
557+#endif
558+
559+instance OVERLAPPING_ ToSchema Object where
560+ declareNamedSchema _ = pure $ NamedSchema (Just "Object") $ mempty
561+ & type_ ?~ SwaggerObject
562+ & description ?~ "Arbitrary JSON object."
563+ & additionalProperties ?~ AdditionalPropertiesAllowed True
564+
490565instance ToSchema a => ToSchema (V.Vector a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [a])
491566instance ToSchema a => ToSchema (VU.Vector a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [a])
492567instance ToSchema a => ToSchema (VS.Vector a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy [a])
@@ -500,6 +575,13 @@ instance ToSchema a => ToSchema (Set a) where
500575
501576instance ToSchema a => ToSchema (HashSet a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy (Set a))
502577
578+-- | @since 2.2.1
579+instance ToSchema a => ToSchema (NonEmpty a) where
580+ declareNamedSchema _ = do
581+ schema <- declareSchema (Proxy :: Proxy [a])
582+ return $ unnamed $ schema
583+ & minItems .~ Just 1
584+
503585instance ToSchema All where declareNamedSchema = plain . paramSchemaToSchema
504586instance ToSchema Any where declareNamedSchema = plain . paramSchemaToSchema
505587
@@ -509,61 +591,141 @@ instance ToSchema a => ToSchema (First a) where declareNamedSchema _ = unname
509591instance ToSchema a => ToSchema (Last a) where declareNamedSchema _ = unname <$> declareNamedSchema (Proxy :: Proxy a)
510592instance ToSchema a => ToSchema (Dual a) where declareNamedSchema _ = unname <$> declareNamedSchema (Proxy :: Proxy a)
511593
594+instance ToSchema a => ToSchema (Identity a) where declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy a)
595+
512596-- | Default schema for @'Bounded'@, @'Integral'@ types.
513597--
514598-- >>> encode $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
515599-- "{\"maximum\":32767,\"minimum\":-32768,\"type\":\"integer\"}"
516−toSchemaBoundedIntegral :: forall a proxy. (Bounded a, Integral a) => proxy a -> Schema
600+toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
517601toSchemaBoundedIntegral _ = mempty
518− & type_ .~ SwaggerInteger
602+ & type_ ?~ SwaggerInteger
519603 & minimum_ ?~ fromInteger (toInteger (minBound :: a))
520604 & maximum_ ?~ fromInteger (toInteger (maxBound :: a))
521605
522606-- | Default generic named schema for @'Bounded'@, @'Integral'@ types.
523−genericToNamedSchemaBoundedIntegral :: forall a d f proxy.
607+genericToNamedSchemaBoundedIntegral :: forall a d f.
524608 ( Bounded a, Integral a
525609 , Generic a, Rep a ~ D1 d f, Datatype d)
526− => SchemaOptions -> proxy a -> NamedSchema
610+ => SchemaOptions -> Proxy a -> NamedSchema
527611genericToNamedSchemaBoundedIntegral opts proxy
528− = NamedSchema (gdatatypeSchemaName opts (Proxy :: Proxy d)) (toSchemaBoundedIntegral proxy)
612+ = genericNameSchema opts proxy (toSchemaBoundedIntegral proxy)
613+
614+-- | Declare a named schema for a @newtype@ wrapper.
615+genericDeclareNamedSchemaNewtype :: forall a d c s i inner.
616+ (Generic a, Datatype d, Rep a ~ D1 d (C1 c (S1 s (K1 i inner))))
617+ => SchemaOptions -- ^ How to derive the name.
618+ -> (Proxy inner -> Declare (Definitions Schema) Schema) -- ^ How to create a schema for the wrapped type.
619+ -> Proxy a
620+ -> Declare (Definitions Schema) NamedSchema
621+genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$> f (Proxy :: Proxy inner)
622+
623+-- | Declare 'Schema' for a mapping with 'Bounded' 'Enum' keys.
624+-- This makes a much more useful schema when there aren't many options for key values.
625+--
626+-- >>> data ButtonState = Neutral | Focus | Active | Hover | Disabled deriving (Show, Bounded, Enum, Generic)
627+-- >>> instance ToJSON ButtonState
628+-- >>> instance ToSchema ButtonState
629+-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
630+-- >>> type ImageUrl = T.Text
631+-- >>> encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
632+-- "{\"properties\":{\"Neutral\":{\"type\":\"string\"},\"Focus\":{\"type\":\"string\"},\"Active\":{\"type\":\"string\"},\"Hover\":{\"type\":\"string\"},\"Disabled\":{\"type\":\"string\"}},\"type\":\"object\"}"
633+--
634+-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
635+-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
636+declareSchemaBoundedEnumKeyMapping :: forall map key value.
637+ (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
638+ => Proxy (map key value) -> Declare (Definitions Schema) Schema
639+declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key of
640+ ToJSONKeyText keyToText _ -> objectSchema keyToText
641+ ToJSONKeyValue _ _ -> declareSchema (Proxy :: Proxy [(key, value)])
642+ where
643+ objectSchema keyToText = do
644+ valueRef <- declareSchemaRef (Proxy :: Proxy value)
645+ let allKeys = [minBound..maxBound :: key]
646+ mkPair k = (keyToText k, valueRef)
647+ return $ mempty
648+ & type_ ?~ SwaggerObject
649+ & properties .~ InsOrdHashMap.fromList (map mkPair allKeys)
650+
651+-- | A 'Schema' for a mapping with 'Bounded' 'Enum' keys.
652+-- This makes a much more useful schema when there aren't many options for key values.
653+--
654+-- >>> data ButtonState = Neutral | Focus | Active | Hover | Disabled deriving (Show, Bounded, Enum, Generic)
655+-- >>> instance ToJSON ButtonState
656+-- >>> instance ToSchema ButtonState
657+-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
658+-- >>> type ImageUrl = T.Text
659+-- >>> encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
660+-- "{\"properties\":{\"Neutral\":{\"type\":\"string\"},\"Focus\":{\"type\":\"string\"},\"Active\":{\"type\":\"string\"},\"Hover\":{\"type\":\"string\"},\"Disabled\":{\"type\":\"string\"}},\"type\":\"object\"}"
661+--
662+-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
663+-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
664+toSchemaBoundedEnumKeyMapping :: forall map key value.
665+ (Bounded key, Enum key, ToJSONKey key, ToSchema key, ToSchema value)
666+ => Proxy (map key value) -> Schema
667+toSchemaBoundedEnumKeyMapping = flip evalDeclare mempty . declareSchemaBoundedEnumKeyMapping
529668
530669-- | A configurable generic @'Schema'@ creator.
531−genericDeclareSchema :: (Generic a, GToSchema (Rep a)) => SchemaOptions -> proxy a -> Declare (Definitions Schema) Schema
532−genericDeclareSchema opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchema opts proxy
670+genericDeclareSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareSchemaUnrestricted") =>
671+ SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
672+genericDeclareSchema = genericDeclareSchemaUnrestricted
533673
534674-- | A configurable generic @'NamedSchema'@ creator.
535675-- This function applied to @'defaultSchemaOptions'@
536676-- is used as the default for @'declareNamedSchema'@
537677-- when the type is an instance of @'Generic'@.
538−genericDeclareNamedSchema :: forall a proxy. (Generic a, GToSchema (Rep a)) => SchemaOptions -> proxy a -> Declare (Definitions Schema) NamedSchema
539−genericDeclareNamedSchema opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
678+genericDeclareNamedSchema :: (Generic a, GToSchema (Rep a), TypeHasSimpleShape a "genericDeclareNamedSchemaUnrestricted") =>
679+ SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
680+genericDeclareNamedSchema = genericDeclareNamedSchemaUnrestricted
681+
682+-- | A configurable generic @'Schema'@ creator.
683+--
684+-- Unlike 'genericDeclareSchema' also works for mixed sum types.
685+-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
686+genericDeclareSchemaUnrestricted :: (Generic a, GToSchema (Rep a)) => SchemaOptions -> Proxy a -> Declare (Definitions Schema) Schema
687+genericDeclareSchemaUnrestricted opts proxy = _namedSchemaSchema <$> genericDeclareNamedSchemaUnrestricted opts proxy
688+
689+-- | A configurable generic @'NamedSchema'@ creator.
690+--
691+-- Unlike 'genericDeclareNamedSchema' also works for mixed sum types.
692+-- Use with care since some Swagger tools do not support well schemas for mixed sum types.
693+genericDeclareNamedSchemaUnrestricted :: forall a. (Generic a, GToSchema (Rep a)) =>
694+ SchemaOptions -> Proxy a -> Declare (Definitions Schema) NamedSchema
695+genericDeclareNamedSchemaUnrestricted opts _ = gdeclareNamedSchema opts (Proxy :: Proxy (Rep a)) mempty
696+
697+-- | Derive a 'Generic'-based name for a datatype and assign it to a given 'Schema'.
698+genericNameSchema :: forall a d f.
699+ (Generic a, Rep a ~ D1 d f, Datatype d)
700+ => SchemaOptions -> Proxy a -> Schema -> NamedSchema
701+genericNameSchema opts _ = NamedSchema (gdatatypeSchemaName opts (Proxy :: Proxy d))
540702
541−gdatatypeSchemaName :: forall proxy d. Datatype d => SchemaOptions -> proxy d -> Maybe T.Text
542−gdatatypeSchemaName opts _ = case name of
703+gdatatypeSchemaName :: forall d. Datatype d => SchemaOptions -> Proxy d -> Maybe T.Text
704+gdatatypeSchemaName opts _ = case orig of
543705 (c:_) | isAlpha c && isUpper c -> Just (T.pack name)
544706 _ -> Nothing
545707 where
546− name = datatypeNameModifier opts (datatypeName (Proxy3 :: Proxy3 d f a))
708+ orig = datatypeName (Proxy3 :: Proxy3 d f a)
709+ name = datatypeNameModifier opts orig
547710
548711-- | Lift a plain @'ParamSchema'@ into a model @'NamedSchema'@.
549−paramSchemaToNamedSchema :: forall a d f proxy.
550− (ToParamSchema a, Generic a, Rep a ~ D1 d f, Datatype d)
551− => SchemaOptions -> proxy a -> NamedSchema
552−paramSchemaToNamedSchema opts proxy = NamedSchema (gdatatypeSchemaName opts (Proxy :: Proxy d)) (paramSchemaToSchema proxy)
712+paramSchemaToNamedSchema :: (ToParamSchema a, Generic a, Rep a ~ D1 d f, Datatype d) =>
713+ SchemaOptions -> Proxy a -> NamedSchema
714+paramSchemaToNamedSchema opts proxy = genericNameSchema opts proxy (paramSchemaToSchema proxy)
553715
554716-- | Lift a plain @'ParamSchema'@ into a model @'Schema'@.
555−paramSchemaToSchema :: forall a proxy. ToParamSchema a => proxy a -> Schema
556−paramSchemaToSchema _ = mempty & paramSchema .~ toParamSchema (Proxy :: Proxy a)
717+paramSchemaToSchema :: ToParamSchema a => Proxy a -> Schema
718+paramSchemaToSchema proxy = mempty & paramSchema .~ toParamSchema proxy
557719
558720nullarySchema :: Schema
559721nullarySchema = mempty
560− & type_ .~ SwaggerArray
722+ & type_ ?~ SwaggerArray
561723 & items ?~ SwaggerItemsArray []
562724
563−gtoNamedSchema :: GToSchema f => SchemaOptions -> proxy f -> NamedSchema
725+gtoNamedSchema :: GToSchema f => SchemaOptions -> Proxy f -> NamedSchema
564726gtoNamedSchema opts proxy = undeclare $ gdeclareNamedSchema opts proxy mempty
565727
566−gdeclareSchema :: GToSchema f => SchemaOptions -> proxy f -> Declare (Definitions Schema) Schema
728+gdeclareSchema :: GToSchema f => SchemaOptions -> Proxy f -> Declare (Definitions Schema) Schema
567729gdeclareSchema opts proxy = _namedSchemaSchema <$> gdeclareNamedSchema opts proxy mempty
568730
569731instance (GToSchema f, GToSchema g) => GToSchema (f :*: g) where
@@ -583,7 +745,7 @@ instance OVERLAPPING_ Constructor c => GToSchema (C1 c U1) where
583745 gdeclareNamedSchema = gdeclareNamedSumSchema
584746
585747-- | Single field constructor.
586−instance (Selector s, GToSchema f) => GToSchema (C1 c (S1 s f)) where
748+instance (Selector s, GToSchema f, GToSchema (S1 s f)) => GToSchema (C1 c (S1 s f)) where
587749 gdeclareNamedSchema opts _ s
588750 | unwrapUnaryRecords opts = fieldSchema
589751 | otherwise =
@@ -597,7 +759,7 @@ instance (Selector s, GToSchema f) => GToSchema (C1 c (S1 s f)) where
597759 recordSchema = gdeclareNamedSchema opts (Proxy :: Proxy (S1 s f)) s
598760 fieldSchema = gdeclareNamedSchema opts (Proxy :: Proxy f) s
599761
600−gdeclareSchemaRef :: GToSchema a => SchemaOptions -> proxy a -> Declare (Definitions Schema) (Referenced Schema)
762+gdeclareSchemaRef :: GToSchema a => SchemaOptions -> Proxy a -> Declare (Definitions Schema) (Referenced Schema)
601763gdeclareSchemaRef opts proxy = do
602764 case gtoNamedSchema opts proxy of
603765 NamedSchema (Just name) schema -> do
@@ -616,7 +778,7 @@ gdeclareSchemaRef opts proxy = do
616778 return $ Ref (Reference name)
617779 _ -> Inline <$> gdeclareSchema opts proxy
618780
619−appendItem :: Referenced Schema -> Maybe (SwaggerItems SwaggerKindSchema) -> Maybe (SwaggerItems SwaggerKindSchema)
781+appendItem :: Referenced Schema -> Maybe (SwaggerItems 'SwaggerKindSchema) -> Maybe (SwaggerItems 'SwaggerKindSchema)
620782appendItem x Nothing = Just (SwaggerItemsArray [x])
621783appendItem x (Just (SwaggerItemsArray xs)) = Just (SwaggerItemsArray (xs ++ [x]))
622784appendItem _ _ = error "GToSchema.appendItem: cannot append to SwaggerItemsObject"
@@ -628,10 +790,12 @@ withFieldSchema opts _ isRequiredField schema = do
628790 return $
629791 if T.null fname
630792 then schema
631− & type_ .~ SwaggerArray
793+ & type_ ?~ SwaggerArray
632794 & items %~ appendItem ref
795+ & maxItems %~ Just . maybe 1 (+1) -- increment maxItems
796+ & minItems %~ Just . maybe 1 (+1) -- increment minItems
633797 else schema
634− & type_ .~ SwaggerObject
798+ & type_ ?~ SwaggerObject
635799 & properties . at fname ?~ ref
636800 & if isRequiredField
637801 then required %~ (++ [fname])
@@ -653,10 +817,13 @@ instance OVERLAPPING_ ToSchema c => GToSchema (K1 i (Maybe c)) where
653817instance OVERLAPPABLE_ ToSchema c => GToSchema (K1 i c) where
654818 gdeclareNamedSchema _ _ _ = declareNamedSchema (Proxy :: Proxy c)
655819
656−instance (GSumToSchema f, GSumToSchema g) => GToSchema (f :+: g) where
820+instance ( GSumToSchema f
821+ , GSumToSchema g
822+ ) => GToSchema (f :+: g)
823+ where
657824 gdeclareNamedSchema = gdeclareNamedSumSchema
658825
659−gdeclareNamedSumSchema :: GSumToSchema f => SchemaOptions -> proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
826+gdeclareNamedSumSchema :: GSumToSchema f => SchemaOptions -> Proxy f -> Schema -> Declare (Definitions Schema) NamedSchema
660827gdeclareNamedSumSchema opts proxy s
661828 | allNullaryToStringTag opts && allNullary = pure $ unnamed (toStringTag sumSchema)
662829 | otherwise = (unnamed . fst) <$> runWriterT declareSumSchema
@@ -665,29 +832,29 @@ gdeclareNamedSumSchema opts proxy s
665832 (sumSchema, All allNullary) = undeclare (runWriterT declareSumSchema)
666833
667834 toStringTag schema = mempty
668− & type_ .~ SwaggerString
835+ & type_ ?~ SwaggerString
669836 & enum_ ?~ map toJSON (schema ^.. properties.ifolded.asIndex)
670837
671838type AllNullary = All
672839
673−class GSumToSchema f where
674− gsumToSchema :: SchemaOptions -> proxy f -> Schema -> WriterT AllNullary (Declare (Definitions Schema)) Schema
840+class GSumToSchema (f :: * -> *) where
841+ gsumToSchema :: SchemaOptions -> Proxy f -> Schema -> WriterT AllNullary (Declare (Definitions Schema)) Schema
675842
676843instance (GSumToSchema f, GSumToSchema g) => GSumToSchema (f :+: g) where
677844 gsumToSchema opts _ = gsumToSchema opts (Proxy :: Proxy f) >=> gsumToSchema opts (Proxy :: Proxy g)
678845
679−gsumConToSchemaWith :: forall c f proxy. (GToSchema (C1 c f), Constructor c) =>
680− Referenced Schema -> SchemaOptions -> proxy (C1 c f) -> Schema -> Schema
846+gsumConToSchemaWith :: forall c f. (GToSchema (C1 c f), Constructor c) =>
847+ Referenced Schema -> SchemaOptions -> Proxy (C1 c f) -> Schema -> Schema
681848gsumConToSchemaWith ref opts _ schema = schema
682− & type_ .~ SwaggerObject
849+ & type_ ?~ SwaggerObject
683850 & properties . at tag ?~ ref
684851 & maxProperties ?~ 1
685852 & minProperties ?~ 1
686853 where
687854 tag = T.pack (constructorTagModifier opts (conName (Proxy3 :: Proxy3 c f p)))
688855
689−gsumConToSchema :: forall c f proxy. (GToSchema (C1 c f), Constructor c) =>
690− SchemaOptions -> proxy (C1 c f) -> Schema -> Declare (Definitions Schema) Schema
856+gsumConToSchema :: (GToSchema (C1 c f), Constructor c) =>
857+ SchemaOptions -> Proxy (C1 c f) -> Schema -> Declare (Definitions Schema) Schema
691858gsumConToSchema opts proxy schema = do
692859 ref <- gdeclareSchemaRef opts proxy
693860 return $ gsumConToSchemaWith ref opts proxy schema
@@ -709,3 +876,7 @@ data Proxy2 a b = Proxy2
709876
710877data Proxy3 a b c = Proxy3
711878
879+-- $setup
880+-- >>> import Data.Swagger
881+-- >>> import Data.Aeson (encode)
882+-- >>> import Data.Aeson.Types (toJSONKeyText)