@@ -1,13 +1,13 @@
1โmodule OpenAPI.Checker.Report
1+module Data.OpenApi.Compare.Report
2โ ( generateReport
2+ ( generateReport,
3โ , CheckerOutput(..)
3+ CheckerOutput (..),
4โ , ReportInput (..)
4+ ReportInput (..),
5โ , segregateIssues
5+ segregateIssues,
6โ , ReportStatus (..)
6+ ReportStatus (..),
8โ , ReportConfig (..)
8+ ReportConfig (..),
9โ , ReportTreeStyle (..)
9+ ReportTreeStyle (..),
10โ , ReportMode (..)
10+ ReportMode (..),
@@ -23,6 +23,15 @@ import qualified Data.Map as M
23import Data.Map.Ordered (OMap)
23import Data.Map.Ordered (OMap)
24import qualified Data.Map.Ordered as OM
24import qualified Data.Map.Ordered as OM
25import Data.Maybe
25import Data.Maybe
26+import Data.OpenApi.Compare.Behavior
27+import Data.OpenApi.Compare.Paths
28+import Data.OpenApi.Compare.PathsPrefixTree hiding (empty)
29+import qualified Data.OpenApi.Compare.PathsPrefixTree as P hiding (empty)
30+import Data.OpenApi.Compare.Report.Jet
31+import Data.OpenApi.Compare.Subtree (invertIssueOrientationP)
32+import Data.OpenApi.Compare.Validate.OpenApi
33+import Data.OpenApi.Compare.Validate.Schema.Issues
34+import Data.OpenApi.Compare.Validate.Schema.TypedJson
26import Data.OpenUnion
35import Data.OpenUnion
27import Data.OpenUnion.Extra
36import Data.OpenUnion.Extra
28import Data.Set
37import Data.Set
@@ -32,15 +41,6 @@ import qualified Data.Text as T
32import Data.TypeRepMap hiding (empty)
41import Data.TypeRepMap hiding (empty)
33import Data.Typeable
42import Data.Typeable
34import Generic.Data
43import Generic.Data
35โimport OpenAPI.Checker.Behavior
36โimport OpenAPI.Checker.Paths
37โimport OpenAPI.Checker.PathsPrefixTree hiding (empty)
38โimport qualified OpenAPI.Checker.PathsPrefixTree as P hiding (empty)
39โimport OpenAPI.Checker.Report.Jet
40โimport OpenAPI.Checker.Subtree (invertIssueOrientationP)
41โimport OpenAPI.Checker.Validate.OpenApi
42โimport OpenAPI.Checker.Validate.Schema.TypedJson
43โimport OpenAPI.Checker.Validate.Schema.Issues
44import Text.Pandoc.Builder
44import Text.Pandoc.Builder
46type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
46type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
@@ -54,22 +54,27 @@ data CheckerOutput = CheckerOutput
54 deriving anyclass (ToJSON)
54 deriving anyclass (ToJSON)
56data ReportInput = ReportInput
56data ReportInput = ReportInput
57+ { -- | forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
57โ { breakingChanges :: Changes -- ^ forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
58+ breakingChanges :: Changes
58โ , nonBreakingChanges :: Changes -- ^ backward 'CertainIssue', 'ProbablyIssue' and 'Comment', except those shadowed by 'relatedIssues'
59+ , -- | backward 'CertainIssue', 'ProbablyIssue' and 'Comment', except those shadowed by 'relatedIssues'
60+ nonBreakingChanges :: Changes
61+ , -- | forward and backward 'Unsupported' (assumed to be the same anyway)
59โ , unsupportedChanges :: Changes -- ^ forward and backward 'Unsupported' (assumed to be the same anyway)
62+ unsupportedChanges :: Changes
60โ , schemaIssues :: Changes -- ^ forward and backward 'SchemaInvalid' (assumed to be the same anyway)
63+ , -- | forward and backward 'SchemaInvalid' (assumed to be the same anyway)
64+ schemaIssues :: Changes
62 deriving stock (Generic)
66 deriving stock (Generic)
63 deriving (Semigroup, Monoid) via (Generically ReportInput)
67 deriving (Semigroup, Monoid) via (Generically ReportInput)
64 deriving anyclass (ToJSON)
68 deriving anyclass (ToJSON)
66segregateIssues :: CheckerOutput -> ReportInput
70segregateIssues :: CheckerOutput -> ReportInput
67โsegregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} = ReportInput
71+segregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} =
68โ { breakingChanges = P.filter isBreaking fwd
73+ { breakingChanges = P.filter isBreaking fwd
69โ , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
74+ , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
70โ , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
75+ , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
71โ , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
76+ , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
74 isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
79 isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
75 isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
80 isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
@@ -104,10 +109,9 @@ data ReportTreeStyle = HeadersTreeStyle | FoldingBlockquotesTreeStyle
104twoRowTable :: [(Inlines, Inlines)] -> Blocks
109twoRowTable :: [(Inlines, Inlines)] -> Blocks
105twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
110twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
107โgenerateReport :: ReportConfig -> ReportInput -> (Pandoc, ReportStatus)
112+generateReport :: ReportConfig -> ReportInput -> (Blocks, ReportStatus)
108generateReport cfg inp =
113generateReport cfg inp =
110โ schemaIssuesPresent = not $ P.null $ schemaIssues inp
114+ let schemaIssuesPresent = not $ P.null $ schemaIssues inp
111 breakingChangesPresent = not $ P.null $ breakingChanges inp
115 breakingChangesPresent = not $ P.null $ breakingChanges inp
112 nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
116 nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
113 unsupportedChangesPresent = not $ P.null $ unsupportedChanges inp
117 unsupportedChangesPresent = not $ P.null $ unsupportedChanges inp
@@ -116,46 +120,55 @@ generateReport cfg inp =
116 OnlyErrors -> False
120 OnlyErrors -> False
117 builder = buildReport cfg
121 builder = buildReport cfg
120โ header 1 "Summary"
123+ header 1 "Summary"
121โ <> twoRowTable
124+ <> twoRowTable
122โ (when'
125+ ( when'
123โ schemaIssuesPresent
126+ schemaIssuesPresent
124โ [ ( refOpt schemaIssuesPresent schemaIssuesId "โ Schema issues"
128+ ( refOpt schemaIssuesPresent schemaIssuesId "โผ๏ธ Schema issues"
125โ , show' $ P.size $ schemaIssues inp
129+ , show' $ P.size $ schemaIssues inp
129โ [ ( refOpt breakingChangesPresent breakingChangesId "โ ๏ธ Breaking changes"
133+ ( refOpt breakingChangesPresent breakingChangesId "โ Breaking changes"
130 , show' $ P.size $ breakingChanges inp
134 , show' $ P.size $ breakingChanges inp
133โ ++ when'
137+ ++ when'
134โ nonBreakingChangesShown
138+ nonBreakingChangesShown
135โ [ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "๐ Non-breaking changes"
140+ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "โ ๏ธ Non-breaking changes"
136โ , show' $ P.size $ nonBreakingChanges inp
141+ , show' $ P.size $ nonBreakingChanges inp
145+ unsupportedChangesPresent
139โ ++ [ ( refOpt unsupportedChangesPresent unsupportedChangesId "๐คท Unsupported feature changes"
147+ ( refOpt unsupportedChangesPresent unsupportedChangesId "โ Unsupported feature changes"
140โ , show' $ P.size $ unsupportedChanges inp
148+ , show' $ P.size $ unsupportedChanges inp
143โ <> when'
152+ <> when'
144โ schemaIssuesPresent
153+ schemaIssuesPresent
145โ (header 1 (anchor schemaIssuesId <> "โ Schema issues")
154+ ( header 1 (anchor schemaIssuesId <> "โผ๏ธ Schema issues")
146โ <> builder (showErrs $ schemaIssues inp))
155+ <> builder (showErrs $ schemaIssues inp)
147โ <> when'
157+ <> when'
148โ breakingChangesPresent
158+ breakingChangesPresent
149โ (header 1 (anchor breakingChangesId <> "โ ๏ธ Breaking changes")
159+ ( header 1 (anchor breakingChangesId <> "โ Breaking changes")
150โ <> builder (showErrs $ breakingChanges inp))
160+ <> builder (showErrs $ breakingChanges inp)
151โ <> when'
162+ <> when'
152โ (nonBreakingChangesPresent && nonBreakingChangesShown)
163+ (nonBreakingChangesPresent && nonBreakingChangesShown)
153โ (header 1 (anchor nonBreakingChangesId <> "๐ Non-breaking changes")
164+ ( header 1 (anchor nonBreakingChangesId <> "โ ๏ธ Non-breaking changes")
154โ <> builder (showErrs $ nonBreakingChanges inp))
165+ <> builder (showErrs $ nonBreakingChanges inp)
155โ <> when'
167+ <> when'
156โ unsupportedChangesPresent
168+ unsupportedChangesPresent
157โ (header 1 (anchor unsupportedChangesId <> "๐คท Unsupported feature changes")
169+ ( header 1 (anchor unsupportedChangesId <> "โ Unsupported feature changes")
158โ <> builder (showErrs $ unsupportedChanges inp))
170+ <> builder (showErrs $ unsupportedChanges inp)
161 | breakingChangesPresent -> BreakingChanges
174 | breakingChangesPresent -> BreakingChanges
@@ -185,13 +198,14 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
185 let -- Extract this pattern if more cases like this arise
198 let -- Extract this pattern if more cases like this arise
186 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
199 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
187 , otherIssues :: Set (AnIssue a)
200 , otherIssues :: Set (AnIssue a)
188โ ) = case eqT @a @'APILevel of
201+ ) = case eqT @a @ 'APILevel of
189 Just Refl
202 Just Refl
190 | (S.toList -> p@((AnIssue ori _) : _), o) <-
203 | (S.toList -> p@((AnIssue ori _) : _), o) <-
191 S.partition
204 S.partition
192โ (\((AnIssue _ u)) -> case u of
205+ ( \((AnIssue _ u)) -> case u of
193โ NoPathsMatched {} -> True
206+ NoPathsMatched {} -> True
194โ AllPathsFailed {} -> True)
207+ AllPathsFailed {} -> True
195 currentIssues ->
209 currentIssues ->
196 let p' = p <&> (\(AnIssue _ i) -> i)
210 let p' = p <&> (\(AnIssue _ i) -> i)
197 in (Just (ori, p'), o)
211 in (Just (ori, p'), o)
@@ -202,9 +216,10 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
202 paths = case removedPaths of
216 paths = case removedPaths of
203 Just (ori, ps) -> do
217 Just (ori, ps) -> do
204 singletonHeader
218 singletonHeader
205โ (case ori of
219+ ( case ori of
206โ Forward -> "Removed paths"
220+ Forward -> "Removed paths"
207โ Backward -> "Added paths")
221+ Backward -> "Added paths"
208 $ singletonBody $
223 $ singletonBody $
209 bulletList $
224 bulletList $
210 ps <&> \case
225 ps <&> \case
@@ -240,60 +255,62 @@ jets =
240 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
255 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
241 strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
256 strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
242 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
257 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
243โ "๐ฑโฌ
๏ธ JSON Response โ " <> str (T.pack . show $ c) :: Inlines
258+ "โฌ
๏ธโ๏ธ JSON Response โ " <> str (T.pack . show $ c) :: Inlines
244โ , constructReportJet $ \InRequest InPayload PayloadSchema -> "๐ฑโก๏ธ JSON Request" :: Inlines
259+ , constructReportJet $ \InRequest InPayload PayloadSchema -> "โก๏ธโ๏ธ JSON Request" :: Inlines
247 unwrapReportJetResult :: ReportJetResult Behave x -> ReportJet' Behave x
262 unwrapReportJetResult :: ReportJetResult Behave x -> ReportJet' Behave x
248 unwrapReportJetResult (Pure _) = error "There really shouldn't be any results here."
263 unwrapReportJetResult (Pure _) = error "There really shouldn't be any results here."
249 unwrapReportJetResult (Free f) = f
264 unwrapReportJetResult (Free f) = f
251โ jsonPathJet
266+ jsonPathJet ::
252โ :: NonEmpty
267+ NonEmpty
253โ ( Union
268+ ( Union
254โ '[ Behave 'SchemaLevel 'TypedSchemaLevel
269+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
255โ , Behave 'TypedSchemaLevel 'SchemaLevel
270+ , Behave 'TypedSchemaLevel 'SchemaLevel
258โ -> Inlines
273+ Inlines
259 jsonPathJet x = code $ "$" <> showParts (NE.toList x)
274 jsonPathJet x = code $ "$" <> showParts (NE.toList x)
261โ showParts
276+ showParts ::
262โ :: [ Union
277+ [ Union
263โ '[ Behave 'SchemaLevel 'TypedSchemaLevel
278+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
264โ , Behave 'TypedSchemaLevel 'SchemaLevel
279+ , Behave 'TypedSchemaLevel 'SchemaLevel
268 showParts [] = mempty
283 showParts [] = mempty
269 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion (InProperty _)) : _)) = showParts xs
284 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion (InProperty _)) : _)) = showParts xs
270 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion InAdditionalProperty) : _)) = showParts xs
285 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion InAdditionalProperty) : _)) = showParts xs
271 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion InItems : _)) = showParts xs
286 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion InItems : _)) = showParts xs
272 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion (InItem _) : _)) = showParts xs
287 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion (InItem _) : _)) = showParts xs
273 showParts (y : ys) =
288 showParts (y : ys) =
274โ ((\(OfType t) -> "(" <> describeJSONType t <> ")")
289+ ( (\(OfType t) -> "(" <> describeJSONType t <> ")")
275โ @@> (\case
290+ @@> ( \case
276โ InItems -> "[*]"
291+ InItems -> "[*]"
277โ InItem i -> "[" <> T.pack (show i) <> "]"
292+ InItem i -> "[" <> T.pack (show i) <> "]"
278โ InProperty p -> "." <> p
293+ InProperty p -> "." <> p
279โ InAdditionalProperty -> ".*")
294+ InAdditionalProperty -> ".*"
280โ @@> typesExhausted)
296+ @@> typesExhausted
282 <> showParts ys
299 <> showParts ys
284โobserveJetShowErrs
301+observeJetShowErrs ::
285โ :: ReportJet' Behave (Maybe Inlines)
302+ ReportJet' Behave (Maybe Inlines) ->
286โ -> P.PathsPrefixTree Behave AnIssue a
303+ P.PathsPrefixTree Behave AnIssue a ->
287โ -> (Report, P.PathsPrefixTree Behave AnIssue a)
304+ (Report, P.PathsPrefixTree Behave AnIssue a)
288observeJetShowErrs jet p = case observeJetShowErrs' jet p of
305observeJetShowErrs jet p = case observeJetShowErrs' jet p of
289 Just m -> m
306 Just m -> m
290 Nothing -> (mempty, p)
307 Nothing -> (mempty, p)
292โobserveJetShowErrs'
309+observeJetShowErrs' ::
293โ :: forall a.
310+ forall a.
294โ ReportJet' Behave (Maybe Inlines)
311+ ReportJet' Behave (Maybe Inlines) ->
295โ -> P.PathsPrefixTree Behave AnIssue a
312+ P.PathsPrefixTree Behave AnIssue a ->
296โ -> Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
313+ Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
297observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues) =
314observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues) =
298 let results =
315 let results =
299 subIssues >>= \(WrapTypeable (AStep m)) ->
316 subIssues >>= \(WrapTypeable (AStep m)) ->
@@ -301,21 +318,23 @@ observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues)
301 maybe (Left $ embed (step bhv) subErrs) Right . listToMaybe $
318 maybe (Left $ embed (step bhv) subErrs) Right . listToMaybe $
302 jet @_ @_ @[] bhv
319 jet @_ @_ @[] bhv
303 & mapMaybe
320 & mapMaybe
304โ (\case
321+ ( \case
305โ Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
322+ Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
306โ Pure (Just h) ->
323+ Pure (Just h) ->
307โ if P.null subErrs
324+ if P.null subErrs
308โ then Just mempty
325+ then Just mempty
309โ else Just (singletonHeader h (showErrs subErrs), mempty)
326+ else Just (singletonHeader h (showErrs subErrs), mempty)
310โ Pure Nothing -> Nothing)
327+ Pure Nothing -> Nothing
311 in (fmap . fmap) (PathsPrefixNode currentIssues mempty <>) $
329 in (fmap . fmap) (PathsPrefixNode currentIssues mempty <>) $
312 if any isRight results
330 if any isRight results
316โ (\case
334+ ( \case
317โ Left e -> (mempty, e)
335+ Left e -> (mempty, e)
318โ Right m -> m)
336+ Right m -> m
320 else Nothing
339 else Nothing
@@ -338,8 +357,9 @@ buildReport cfg = case treeStyle cfg of
338 body rprt
357 body rprt
339 <> foldOMapWithKey
358 <> foldOMapWithKey
340 (headers rprt)
359 (headers rprt)
341โ (\k v ->
360+ ( \k v ->
342โ header level k <> subBuilder v)
361+ header level k <> subBuilder v
344 subBuilder = headerStyleBuilder (level + 1)
364 subBuilder = headerStyleBuilder (level + 1)
@@ -348,16 +368,17 @@ buildReport cfg = case treeStyle cfg of
348 body rprt
368 body rprt
349 <> foldOMapWithKey
369 <> foldOMapWithKey
350 (headers rprt)
370 (headers rprt)
351โ (\k v ->
371+ ( \k v ->
352โ if (OM.size . headers $ rprt) < 2
372+ if (OM.size . headers $ rprt) < 2
353โ then para k <> blockQuote (subBuilder v)
373+ then para k <> blockQuote (subBuilder v)
355โ rawHtml "<details>"
375+ rawHtml "<details>"
356โ <> rawHtml "<summary>"
376+ <> rawHtml "<summary>"
357โ <> plain k
377+ <> plain k
358โ <> rawHtml "</summary>"
378+ <> rawHtml "</summary>"
359โ <> blockQuote (subBuilder v)
379+ <> blockQuote (subBuilder v)
360โ <> rawHtml "</details>")
380+ <> rawHtml "</details>"
362 subBuilder = foldingStyleBuilder
383 subBuilder = foldingStyleBuilder