@@ -27,6 +27,7 @@ import Control.Lens
27import Control.Monad (when)
27import Control.Monad (when)
29import Data.Aeson hiding (Result)
29import Data.Aeson hiding (Result)
30+import Data.Aeson.Encode.Pretty (encodePretty)
30import Data.Foldable (for_, sequenceA_,
31import Data.Foldable (for_, sequenceA_,
31 traverse_)
32 traverse_)
32import Data.HashMap.Strict (HashMap)
33import Data.HashMap.Strict (HashMap)
@@ -39,6 +40,8 @@ import Data.Proxy
39import Data.Scientific (Scientific, isInteger)
40import Data.Scientific (Scientific, isInteger)
40import Data.Text (Text)
41import Data.Text (Text)
41import qualified Data.Text as Text
42import qualified Data.Text as Text
43+import qualified Data.Text.Lazy as TL
44+import qualified Data.Text.Lazy.Encoding as TL
42import Data.Vector (Vector)
45import Data.Vector (Vector)
43import qualified Data.Vector as Vector
46import qualified Data.Vector as Vector
@@ -54,18 +57,101 @@ import Data.Swagger.Lens
55-- /NOTE:/ @'validateToJSON'@ does not perform string pattern validation.
58-- /NOTE:/ @'validateToJSON'@ does not perform string pattern validation.
56-- See @'validateToJSONWithPatternChecker'@.
59-- See @'validateToJSONWithPatternChecker'@.
61+-- See 'renderValidationErrors' on how the output is structured.
62+validatePrettyToJSON :: forall a. (ToJSON a, ToSchema a) => a -> Maybe String
63+validatePrettyToJSON = renderValidationErrors validateToJSON
65+-- | Variant of 'validatePrettyToJSON' with typed output.
57validateToJSON :: forall a. (ToJSON a, ToSchema a) => a -> [ValidationError]
66validateToJSON :: forall a. (ToJSON a, ToSchema a) => a -> [ValidationError]
58validateToJSON = validateToJSONWithPatternChecker (\_pattern _str -> True)
67validateToJSON = validateToJSONWithPatternChecker (\_pattern _str -> True)
60-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value and pattern checker.
69-- | Validate @'ToJSON'@ instance matches @'ToSchema'@ for a given value and pattern checker.
61-- This can be used with QuickCheck to ensure those instances are coherent.
70-- This can be used with QuickCheck to ensure those instances are coherent.
63−-- For validation without patterns see @'validateToJSON'@.
72+-- For validation without patterns see @'validateToJSON'@. See also:
73+-- 'renderValidationErrors'.
64validateToJSONWithPatternChecker :: forall a. (ToJSON a, ToSchema a) => (Pattern -> Text -> Bool) -> a -> [ValidationError]
74validateToJSONWithPatternChecker :: forall a. (ToJSON a, ToSchema a) => (Pattern -> Text -> Bool) -> a -> [ValidationError]
65validateToJSONWithPatternChecker checker = validateJSONWithPatternChecker checker defs sch . toJSON
75validateToJSONWithPatternChecker checker = validateJSONWithPatternChecker checker defs sch . toJSON
67 (defs, sch) = runDeclare (declareSchema (Proxy :: Proxy a)) mempty
77 (defs, sch) = runDeclare (declareSchema (Proxy :: Proxy a)) mempty
79+-- | Pretty print validation errors
80+-- together with actual JSON and Swagger Schema
81+-- (using 'encodePretty').
83+-- >>> import Data.Aeson as Aeson
84+-- >>> import Data.Foldable (traverse_)
85+-- >>> import GHC.Generics
86+-- >>> data Phone = Phone { value :: String } deriving (Generic)
87+-- >>> data Person = Person { name :: String, phone :: Phone } deriving (Generic)
88+-- >>> instance ToJSON Person where toJSON p = object [ "name" Aeson..= name p ]
89+-- >>> instance ToSchema Phone
90+-- >>> instance ToSchema Person
91+-- >>> let person = Person { name = "John", phone = Phone "123456" }
92+-- >>> traverse_ putStrLn $ renderValidationErrors validateToJSON person
93+-- Validation against the schema fails:
94+-- * property "phone" is required, but not found in "{\"name\":\"John\"}"
110+-- "$ref": "#/definitions/Phone"
118+-- Swagger Description Context:
133+renderValidationErrors
134+ :: forall a. (ToJSON a, ToSchema a)
135+ => (a -> [ValidationError]) -> a -> Maybe String
136+renderValidationErrors f x =
139+ errors -> Just $ unlines
140+ [ "Validation against the schema fails:"
141+ , unlines (map (" * " ++) errors)
143+ , ppJSONString (toJSON x)
146+ , ppJSONString (toJSON schema_)
148+ , "Swagger Description Context:"
149+ , ppJSONString (toJSON refs_)
152+ ppJSONString = TL.unpack . TL.decodeUtf8 . encodePretty
153+ (refs_, schema_) = runDeclare (declareSchema (Proxy :: Proxy a)) mempty
69-- | Validate JSON @'Value'@ against Swagger @'Schema'@.
155-- | Validate JSON @'Value'@ against Swagger @'Schema'@.
71-- prop> validateJSON mempty (toSchema (Proxy :: Proxy Int)) (toJSON (x :: Int)) == []
157-- prop> validateJSON mempty (toSchema (Proxy :: Proxy Int)) (toJSON (x :: Int)) == []