@@ -42,6 +42,7 @@ import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
42import Data.Int
42import Data.Int
43import Data.IntSet (IntSet)
43import Data.IntSet (IntSet)
44import Data.IntMap (IntMap)
44import Data.IntMap (IntMap)
45+import Data.List (sort)
45import Data.List.NonEmpty.Compat (NonEmpty)
46import Data.List.NonEmpty.Compat (NonEmpty)
46import Data.Map (Map)
47import Data.Map (Map)
47import Data.Maybe (fromMaybe)
48import Data.Maybe (fromMaybe)
@@ -156,13 +157,19 @@ declareSchema = fmap _namedSchemaSchema . declareNamedSchema
157-- >>> toNamedSchema (Proxy :: Proxy String) ^. name
158-- >>> toNamedSchema (Proxy :: Proxy String) ^. name
158-- Nothing
159-- Nothing
159−-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy String) ^. schema)
160+-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy String) ^. schema)
160−-- {"type":"string"}
162+-- "type": "string"
162-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
165-- >>> toNamedSchema (Proxy :: Proxy Day) ^. name
163-- Just "Day"
166-- Just "Day"
164−-- >>> BSL.putStrLn $ encode (toNamedSchema (Proxy :: Proxy Day) ^. schema)
167+-- >>> BSL.putStrLn $ encodePretty (toNamedSchema (Proxy :: Proxy Day) ^. schema)
165−-- {"example":"2016-07-22","format":"date","type":"string"}
169+-- "example": "2016-07-22",
166toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
173toNamedSchema :: ToSchema a => Proxy a -> NamedSchema
167toNamedSchema = undeclare . declareNamedSchema
174toNamedSchema = undeclare . declareNamedSchema
@@ -178,22 +185,35 @@ schemaName = _namedSchemaName . toNamedSchema
179-- | Convert a type into a schema.
186-- | Convert a type into a schema.
181−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy Int8)
188+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy Int8)
182−-- {"maximum":127,"minimum":-128,"type":"integer"}
192+-- "type": "integer"
184−-- >>> BSL.putStrLn $ encode $ toSchema (Proxy :: Proxy [Day])
195+-- >>> BSL.putStrLn $ encodePretty $ toSchema (Proxy :: Proxy [Day])
185−-- {"items":{"$ref":"#/components/schemas/Day"},"type":"array"}
198+-- "$ref": "#/components/schemas/Day"
186toSchema :: ToSchema a => Proxy a -> Schema
202toSchema :: ToSchema a => Proxy a -> Schema
187toSchema = _namedSchemaSchema . toNamedSchema
203toSchema = _namedSchemaSchema . toNamedSchema
189-- | Convert a type into a referenced schema if possible.
205-- | Convert a type into a referenced schema if possible.
190-- Only named schemas can be referenced, nameless schemas are inlined.
206-- Only named schemas can be referenced, nameless schemas are inlined.
192−-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Integer)
208+-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Integer)
193−-- {"type":"integer"}
210+-- "type": "integer"
195−-- >>> BSL.putStrLn $ encode $ toSchemaRef (Proxy :: Proxy Day)
213+-- >>> BSL.putStrLn $ encodePretty $ toSchemaRef (Proxy :: Proxy Day)
196−-- {"$ref":"#/components/schemas/Day"}
215+-- "$ref": "#/components/schemas/Day"
197toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
217toSchemaRef :: ToSchema a => Proxy a -> Referenced Schema
198toSchemaRef = undeclare . declareSchemaRef
218toSchemaRef = undeclare . declareSchemaRef
@@ -261,8 +281,15 @@ inlineAllSchemas = inlineSchemasWhen (const True)
262-- | Convert a type into a schema without references.
282-- | Convert a type into a schema without references.
264−-- >>> BSL.putStrLn $ encode $ toInlinedSchema (Proxy :: Proxy [Day])
284+-- >>> BSL.putStrLn $ encodePretty $ toInlinedSchema (Proxy :: Proxy [Day])
265−-- {"items":{"example":"2016-07-22","format":"date","type":"string"},"type":"array"}
287+-- "example": "2016-07-22",
267-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
294-- __WARNING:__ @'toInlinedSchema'@ will produce infinite schema
268-- when inlining recursive schemas.
295-- when inlining recursive schemas.
@@ -295,19 +322,64 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs
295-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
322-- | Make an unrestrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
296-- Produced schema can be used for further refinement.
323-- Produced schema can be used for further refinement.
298−-- >>> BSL.putStrLn $ encode $ sketchSchema "hello"
325+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema "hello"
299−-- {"example":"hello","type":"string"}
327+-- "example": "hello",
301−-- >>> BSL.putStrLn $ encode $ sketchSchema (1, 2, 3)
331+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (1, 2, 3)
302−-- {"example":[1,2,3],"items":{"type":"number"},"type":"array"}
339+-- "type": "number"
304−-- >>> BSL.putStrLn $ encode $ sketchSchema ("Jack", 25)
344+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema ("Jack", 25)
305−-- {"example":["Jack",25],"items":[{"type":"string"},{"type":"number"}],"type":"array"}
345+-- {
307-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
361-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
308-- >>> instance ToJSON Person
362-- >>> instance ToJSON Person
309−-- >>> BSL.putStrLn $ encode $ sketchSchema (Person "Jack" 25)
363+-- >>> BSL.putStrLn $ encodePretty $ sketchSchema (Person "Jack" 25)
310−-- {"example":{"age":25,"name":"Jack"},"required":["age","name"],"type":"object","properties":{"age":{"type":"number"},"name":{"type":"string"}}}
364+-- {
311sketchSchema :: ToJSON a => a -> Schema
383sketchSchema :: ToJSON a => a -> Schema
312sketchSchema = sketch . toJSON
384sketchSchema = sketch . toJSON
@@ -333,25 +405,139 @@ sketchSchema = sketch . toJSON
333 _ -> Nothing
405 _ -> Nothing
334 go (Object o) = mempty
406 go (Object o) = mempty
335 & type_ ?~ OpenApiObject
407 & type_ ?~ OpenApiObject
336− & required .~ HashMap.keys o
408+ & required .~ sort (HashMap.keys o)
337 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
409 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
339-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
411-- | Make a restrictive sketch of a @'Schema'@ based on a @'ToJSON'@ instance.
340-- Produced schema uses as much constraints as possible.
412-- Produced schema uses as much constraints as possible.
342−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema "hello"
414+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema "hello"
343−-- {"maxLength":5,"pattern":"hello","minLength":5,"type":"string","enum":["hello"]}
415+-- {
421+-- "pattern": "hello",
345−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (1, 2, 3)
425+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (1, 2, 3)
346−-- {"minItems":3,"uniqueItems":true,"items":[{"maximum":1,"minimum":1,"multipleOf":1,"type":"number","enum":[1]},{"maximum":2,"minimum":2,"multipleOf":2,"type":"number","enum":[2]},{"maximum":3,"minimum":3,"multipleOf":3,"type":"number","enum":[3]}],"maxItems":3,"type":"array","enum":[[1,2,3]]}
426+-- {
466+-- "uniqueItems": true
348−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema ("Jack", 25)
469+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema ("Jack", 25)
349−-- {"minItems":2,"uniqueItems":true,"items":[{"maxLength":4,"pattern":"Jack","minLength":4,"type":"string","enum":["Jack"]},{"maximum":25,"minimum":25,"multipleOf":25,"type":"number","enum":[25]}],"maxItems":2,"type":"array","enum":[["Jack",25]]}
470+-- {
484+-- "pattern": "Jack",
500+-- "uniqueItems": true
351-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
503-- >>> data Person = Person { name :: String, age :: Int } deriving (Generic)
352-- >>> instance ToJSON Person
504-- >>> instance ToJSON Person
353−-- >>> BSL.putStrLn $ encode $ sketchStrictSchema (Person "Jack" 25)
505+-- >>> BSL.putStrLn $ encodePretty $ sketchStrictSchema (Person "Jack" 25)
354−-- {"minProperties":2,"required":["age","name"],"maxProperties":2,"type":"object","enum":[{"age":25,"name":"Jack"}],"properties":{"age":{"maximum":25,"minimum":25,"multipleOf":25,"type":"number","enum":[25]},"name":{"maxLength":4,"pattern":"Jack","minLength":4,"type":"string","enum":["Jack"]}}}
506+-- {
513+-- "maxProperties": 2,
514+-- "minProperties": 2,
531+-- "pattern": "Jack",
355sketchStrictSchema :: ToJSON a => a -> Schema
541sketchStrictSchema :: ToJSON a => a -> Schema
356sketchStrictSchema = go . toJSON
542sketchStrictSchema = go . toJSON
@@ -383,7 +569,7 @@ sketchStrictSchema = go . toJSON
383 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
569 allUnique = sz == HashSet.size (HashSet.fromList (V.toList xs))
384 go js@(Object o) = mempty
570 go js@(Object o) = mempty
385 & type_ ?~ OpenApiObject
571 & type_ ?~ OpenApiObject
386− & required .~ names
572+ & required .~ sort names
387 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
573 & properties .~ fmap (Inline . go) (InsOrdHashMap.fromHashMap o)
388 & maxProperties ?~ fromIntegral (length names)
574 & maxProperties ?~ fromIntegral (length names)
389 & minProperties ?~ fromIntegral (length names)
575 & minProperties ?~ fromIntegral (length names)
@@ -553,8 +739,12 @@ instance ToSchema a => ToSchema (Identity a) where declareNamedSchema _ = declar
554-- | Default schema for @'Bounded'@, @'Integral'@ types.
740-- | Default schema for @'Bounded'@, @'Integral'@ types.
556−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
742+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedIntegral (Proxy :: Proxy Int16)
557−-- {"maximum":32767,"minimum":-32768,"type":"integer"}
744+-- "maximum": 32767,
745+-- "minimum": -32768,
558toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
748toSchemaBoundedIntegral :: forall a. (Bounded a, Integral a) => Proxy a -> Schema
559toSchemaBoundedIntegral _ = mempty
749toSchemaBoundedIntegral _ = mempty
560 & type_ ?~ OpenApiInteger
750 & type_ ?~ OpenApiInteger
@@ -586,8 +776,27 @@ genericDeclareNamedSchemaNewtype opts f proxy = genericNameSchema opts proxy <$>
586-- >>> instance ToSchema ButtonState
776-- >>> instance ToSchema ButtonState
587-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
777-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
588-- >>> type ImageUrl = T.Text
778-- >>> type ImageUrl = T.Text
589−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
779+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
590−-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
780+-- {
592-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
801-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
593-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
802-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -614,8 +823,27 @@ declareSchemaBoundedEnumKeyMapping _ = case toJSONKey :: ToJSONKeyFunction key o
614-- >>> instance ToSchema ButtonState
823-- >>> instance ToSchema ButtonState
615-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
824-- >>> instance ToJSONKey ButtonState where toJSONKey = toJSONKeyText (T.pack . show)
616-- >>> type ImageUrl = T.Text
825-- >>> type ImageUrl = T.Text
617−-- >>> BSL.putStrLn $ encode $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
826+-- >>> BSL.putStrLn $ encodePretty $ toSchemaBoundedEnumKeyMapping (Proxy :: Proxy (Map ButtonState ImageUrl))
618−-- {"type":"object","properties":{"Focus":{"type":"string"},"Disabled":{"type":"string"},"Active":{"type":"string"},"Neutral":{"type":"string"},"Hover":{"type":"string"}}}
827+-- {
620-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
848-- Note: this is only useful when @key@ is encoded with 'ToJSONKeyText'.
621-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
849-- If it is encoded with 'ToJSONKeyValue' then a regular schema for @[(key, value)]@ is used.
@@ -908,6 +1136,7 @@ instance (ToSchema1 f, Generic (f a), GToSchema (Rep (f a)), Typeable (f a), ToS
908>>> import Data.OpenApi
1136>>> import Data.OpenApi
909>>> import Data.Aeson (encode)
1137>>> import Data.Aeson (encode)
910>>> import Data.Aeson.Types (toJSONKeyText)
1138>>> import Data.Aeson.Types (toJSONKeyText)
1139+>>> import Data.OpenApi.Internal.Utils
911>>> :set -XScopedTypeVariables
1140>>> :set -XScopedTypeVariables
912>>> :set -XDeriveAnyClass
1141>>> :set -XDeriveAnyClass
913>>> :set -XStandaloneDeriving
1142>>> :set -XStandaloneDeriving