Skip to content

Commit 73efcec

Browse files
committed
Add pretty-printed JSON value and Swagger Schema in validation report
1 parent 0c785a2 commit 73efcec

File tree

2 files changed

+84
-23
lines changed

2 files changed

+84
-23
lines changed

servant-swagger.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ library
7373
Servant.Swagger.Internal.TypeLevel.TMap
7474
hs-source-dirs: src
7575
build-depends: aeson >=0.11.2.0 && <1.5
76+
, aeson-pretty >=0.4 && <0.9
7677
, base >=4.7.0.0 && <4.12
7778
, bytestring >=0.10.4.0 && <0.11
7879
, http-media >=0.6.3 && <0.8

src/Servant/Swagger/Internal/Test.hs

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
{-# LANGUAGE DataKinds #-}
2-
{-# LANGUAGE FlexibleContexts #-}
3-
{-# LANGUAGE RankNTypes #-}
1+
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE DataKinds #-}
3+
{-# LANGUAGE FlexibleContexts #-}
4+
{-# LANGUAGE RankNTypes #-}
45
{-# LANGUAGE ScopedTypeVariables #-}
5-
{-# LANGUAGE TypeOperators #-}
6-
{-# LANGUAGE ConstraintKinds #-}
6+
{-# LANGUAGE TypeOperators #-}
77
module Servant.Swagger.Internal.Test where
88

9-
import Data.Aeson (ToJSON)
10-
import Data.Swagger
11-
import Data.Text (Text)
12-
import Data.Typeable
13-
import Test.Hspec
14-
import Test.Hspec.QuickCheck
15-
import Test.QuickCheck (Arbitrary, Property, property, counterexample)
9+
import Data.Aeson (ToJSON (..))
10+
import Data.Aeson.Encode.Pretty (encodePretty)
11+
import Data.Swagger (Pattern, ToSchema,
12+
toSchema)
13+
import Data.Swagger.Schema.Validation
14+
import Data.Text (Text)
15+
import qualified Data.Text.Lazy as TL
16+
import qualified Data.Text.Lazy.Encoding as TL
17+
import Data.Typeable
18+
import Test.Hspec
19+
import Test.Hspec.QuickCheck
20+
import Test.QuickCheck (Arbitrary, Property,
21+
counterexample, property)
1622

17-
import Servant.API
18-
import Servant.Swagger.Internal.TypeLevel
23+
import Servant.API
24+
import Servant.Swagger.Internal.TypeLevel
1925

2026
-- $setup
2127
-- >>> import Control.Applicative
@@ -76,7 +82,7 @@ validateEveryToJSON :: forall proxy api. TMap (Every [Typeable, Show, Arbitrary,
7682
-> Spec
7783
validateEveryToJSON _ = props
7884
(Proxy :: Proxy [ToJSON, ToSchema])
79-
(reportErrors . validateToJSON)
85+
(maybeCounterExample . prettyValidateWith validateToJSON)
8086
(Proxy :: Proxy (BodyTypes JSON api))
8187

8288
-- | Verify that every type used with @'JSON'@ content type in a servant API
@@ -89,7 +95,7 @@ validateEveryToJSONWithPatternChecker :: forall proxy api. TMap (Every [Typeable
8995
-> Spec
9096
validateEveryToJSONWithPatternChecker checker _ = props
9197
(Proxy :: Proxy [ToJSON, ToSchema])
92-
(reportErrors . validateToJSONWithPatternChecker checker)
98+
(maybeCounterExample . prettyValidateWith (validateToJSONWithPatternChecker checker))
9399
(Proxy :: Proxy (BodyTypes JSON api))
94100

95101
-- * QuickCheck-related stuff
@@ -128,11 +134,65 @@ props _ f px = sequence_ specs
128134
aprop :: forall p' a. (EveryTF cs a, Typeable a, Show a, Arbitrary a) => p' a -> Spec
129135
aprop _ = prop (show (typeOf (undefined :: a))) (f :: a -> Property)
130136

131-
-- | A property that prints a nicely formatted list of errors if there are
132-
-- any.
133-
reportErrors :: [ValidationError] -> Property
134-
reportErrors [] = property True
135-
reportErrors ex = counterexample errString (property False)
137+
-- | Pretty print validation errors
138+
-- together with actual JSON and Swagger Schema
139+
-- (using 'encodePretty').
140+
--
141+
-- >>> import Data.Aeson
142+
-- >>> data Person = Person { name :: String, phone :: Integer } deriving (Generic)
143+
-- >>> instance ToJSON Person where toJSON p = object [ "name" .= name p ]
144+
-- >>> instance ToSchema Person
145+
-- >>> let person = Person { name = "John", phone = 123456 }
146+
-- >>> mapM_ putStrLn $ prettyValidateWith validateToJSON person
147+
-- Validation against the schema fails:
148+
-- * property "phone" is required, but not found in "{\"name\":\"John\"}"
149+
-- <BLANKLINE>
150+
-- JSON value:
151+
-- {
152+
-- "name": "John"
153+
-- }
154+
-- <BLANKLINE>
155+
-- Swagger Schema:
156+
-- {
157+
-- "required": [
158+
-- "name",
159+
-- "phone"
160+
-- ],
161+
-- "type": "object",
162+
-- "properties": {
163+
-- "phone": {
164+
-- "type": "integer"
165+
-- },
166+
-- "name": {
167+
-- "type": "string"
168+
-- }
169+
-- }
170+
-- }
171+
-- <BLANKLINE>
172+
--
173+
-- FIXME: this belongs in "Data.Swagger.Schema.Validation" (in @swagger2@).
174+
prettyValidateWith
175+
:: forall a. (ToJSON a, ToSchema a)
176+
=> (a -> [ValidationError]) -> a -> Maybe String
177+
prettyValidateWith f x =
178+
case f x of
179+
[] -> Nothing
180+
errors -> Just $ unlines
181+
[ "Validation against the schema fails:"
182+
, unlines (map (" * " ++) errors)
183+
, "JSON value:"
184+
, ppJSONString json
185+
, ""
186+
, "Swagger Schema:"
187+
, ppJSONString (toJSON schema)
188+
]
136189
where
137-
errString = unlines $ "Validation against the schema fails:"
138-
: map (" * " ++) ex
190+
ppJSONString = TL.unpack . TL.decodeUtf8 . encodePretty
191+
192+
json = toJSON x
193+
schema = toSchema (Proxy :: Proxy a)
194+
195+
-- | Provide a counterexample if there is any.
196+
maybeCounterExample :: Maybe String -> Property
197+
maybeCounterExample Nothing = property True
198+
maybeCounterExample (Just s) = counterexample s (property False)

0 commit comments

Comments
 (0)