1
- {-# LANGUAGE DataKinds #-}
2
- {-# LANGUAGE FlexibleContexts #-}
3
- {-# LANGUAGE RankNTypes #-}
1
+ {-# LANGUAGE ConstraintKinds #-}
2
+ {-# LANGUAGE DataKinds #-}
3
+ {-# LANGUAGE FlexibleContexts #-}
4
+ {-# LANGUAGE RankNTypes #-}
4
5
{-# LANGUAGE ScopedTypeVariables #-}
5
- {-# LANGUAGE TypeOperators #-}
6
- {-# LANGUAGE ConstraintKinds #-}
6
+ {-# LANGUAGE TypeOperators #-}
7
7
module Servant.Swagger.Internal.Test where
8
8
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 )
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 )
16
22
17
- import Servant.API
18
- import Servant.Swagger.Internal.TypeLevel
23
+ import Servant.API
24
+ import Servant.Swagger.Internal.TypeLevel
19
25
20
26
-- $setup
21
27
-- >>> import Control.Applicative
@@ -76,7 +82,7 @@ validateEveryToJSON :: forall proxy api. TMap (Every [Typeable, Show, Arbitrary,
76
82
-> Spec
77
83
validateEveryToJSON _ = props
78
84
(Proxy :: Proxy [ToJSON , ToSchema ])
79
- (null . validateToJSON)
85
+ (maybeCounterExample . prettyValidateWith validateToJSON)
80
86
(Proxy :: Proxy (BodyTypes JSON api ))
81
87
82
88
-- | Verify that every type used with @'JSON'@ content type in a servant API
@@ -89,7 +95,7 @@ validateEveryToJSONWithPatternChecker :: forall proxy api. TMap (Every [Typeable
89
95
-> Spec
90
96
validateEveryToJSONWithPatternChecker checker _ = props
91
97
(Proxy :: Proxy [ToJSON , ToSchema ])
92
- (null . validateToJSONWithPatternChecker checker)
98
+ (maybeCounterExample . prettyValidateWith ( validateToJSONWithPatternChecker checker) )
93
99
(Proxy :: Proxy (BodyTypes JSON api ))
94
100
95
101
-- * QuickCheck-related stuff
@@ -102,7 +108,7 @@ validateEveryToJSONWithPatternChecker checker _ = props
102
108
-- context "read . show == id" $
103
109
-- props
104
110
-- (Proxy :: Proxy [Eq, Show, Read])
105
- -- (\x -> read (show x) == x)
111
+ -- (\x -> read (show x) === x)
106
112
-- (Proxy :: Proxy [Bool, Int, String])
107
113
-- :}
108
114
-- <BLANKLINE>
@@ -116,15 +122,78 @@ validateEveryToJSONWithPatternChecker checker _ = props
116
122
-- Finished in ... seconds
117
123
-- 3 examples, 0 failures
118
124
props :: forall p p'' cs xs . TMap (Every (Typeable ': Show ': Arbitrary ': cs )) xs =>
119
- p cs -- ^ A list of constraints.
120
- -> (forall x . EveryTF cs x => x -> Bool ) -- ^ Property predicate.
121
- -> p'' xs -- ^ A list of types.
125
+ p cs -- ^ A list of constraints.
126
+ -> (forall x . EveryTF cs x => x -> Property ) -- ^ Property predicate.
127
+ -> p'' xs -- ^ A list of types.
122
128
-> Spec
123
129
props _ f px = sequence_ specs
124
130
where
125
131
specs :: [Spec ]
126
132
specs = tmapEvery (Proxy :: Proxy (Typeable ': Show ': Arbitrary ': cs )) aprop px
127
133
128
134
aprop :: forall p' a . (EveryTF cs a , Typeable a , Show a , Arbitrary a ) => p' a -> Spec
129
- aprop _ = prop (show (typeOf (undefined :: a ))) (f :: a -> Bool )
135
+ aprop _ = prop (show (typeOf (undefined :: a ))) (f :: a -> Property )
130
136
137
+ -- | Pretty print validation errors
138
+ -- together with actual JSON and Swagger Schema
139
+ -- (using 'encodePretty').
140
+ --
141
+ -- >>> import Data.Aeson
142
+ -- >>> import Data.Foldable (traverse_)
143
+ -- >>> data Person = Person { name :: String, phone :: Integer } deriving (Generic)
144
+ -- >>> instance ToJSON Person where toJSON p = object [ "name" .= name p ]
145
+ -- >>> instance ToSchema Person
146
+ -- >>> let person = Person { name = "John", phone = 123456 }
147
+ -- >>> traverse_ putStrLn $ prettyValidateWith validateToJSON person
148
+ -- Validation against the schema fails:
149
+ -- * property "phone" is required, but not found in "{\"name\":\"John\"}"
150
+ -- <BLANKLINE>
151
+ -- JSON value:
152
+ -- {
153
+ -- "name": "John"
154
+ -- }
155
+ -- <BLANKLINE>
156
+ -- Swagger Schema:
157
+ -- {
158
+ -- "required": [
159
+ -- "name",
160
+ -- "phone"
161
+ -- ],
162
+ -- "type": "object",
163
+ -- "properties": {
164
+ -- "phone": {
165
+ -- "type": "integer"
166
+ -- },
167
+ -- "name": {
168
+ -- "type": "string"
169
+ -- }
170
+ -- }
171
+ -- }
172
+ -- <BLANKLINE>
173
+ --
174
+ -- FIXME: this belongs in "Data.Swagger.Schema.Validation" (in @swagger2@).
175
+ prettyValidateWith
176
+ :: forall a . (ToJSON a , ToSchema a )
177
+ => (a -> [ValidationError ]) -> a -> Maybe String
178
+ prettyValidateWith f x =
179
+ case f x of
180
+ [] -> Nothing
181
+ errors -> Just $ unlines
182
+ [ " Validation against the schema fails:"
183
+ , unlines (map (" * " ++ ) errors)
184
+ , " JSON value:"
185
+ , ppJSONString json
186
+ , " "
187
+ , " Swagger Schema:"
188
+ , ppJSONString (toJSON schema)
189
+ ]
190
+ where
191
+ ppJSONString = TL. unpack . TL. decodeUtf8 . encodePretty
192
+
193
+ json = toJSON x
194
+ schema = toSchema (Proxy :: Proxy a )
195
+
196
+ -- | Provide a counterexample if there is any.
197
+ maybeCounterExample :: Maybe String -> Property
198
+ maybeCounterExample Nothing = property True
199
+ maybeCounterExample (Just s) = counterexample s (property False )
0 commit comments