@@ -1,13 +1,13 @@
1−module Data.OpenApi.Compare.Report
2− ( generateReport,
3− CheckerOutput (..),
4− ReportInput (..),
5− segregateIssues,
6− ReportStatus (..),
7− Pandoc,
8− ReportConfig (..),
9− ReportTreeStyle (..),
10− ReportMode (..),
1+module OpenAPI.Checker.Report
2+ ( generateReport
3+ , CheckerOutput(..)
4+ , ReportInput (..)
5+ , segregateIssues
6+ , ReportStatus (..)
7+ , Pandoc
8+ , ReportConfig (..)
9+ , ReportTreeStyle (..)
10+ , ReportMode (..)
1111 )
1212where
1313
@@ -23,15 +23,6 @@ import qualified Data.Map as M
2323import Data.Map.Ordered (OMap)
2424import qualified Data.Map.Ordered as OM
2525import 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
3526import Data.OpenUnion
3627import Data.OpenUnion.Extra
3728import Data.Set
@@ -41,6 +32,15 @@ import qualified Data.Text as T
4132import Data.TypeRepMap hiding (empty)
4233import Data.Typeable
4334import 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
4444import Text.Pandoc.Builder
4545
4646type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
@@ -54,27 +54,22 @@ data CheckerOutput = CheckerOutput
5454 deriving anyclass (ToJSON)
5555
5656data ReportInput = ReportInput
57− { -- | forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
58− breakingChanges :: Changes
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)
62− unsupportedChanges :: Changes
63− , -- | forward and backward 'SchemaInvalid' (assumed to be the same anyway)
64− schemaIssues :: Changes
57+ { breakingChanges :: Changes -- ^ forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
58+ , nonBreakingChanges :: Changes -- ^ backward 'CertainIssue', 'ProbablyIssue' and 'Comment', except those shadowed by 'relatedIssues'
59+ , unsupportedChanges :: Changes -- ^ forward and backward 'Unsupported' (assumed to be the same anyway)
60+ , schemaIssues :: Changes -- ^ forward and backward 'SchemaInvalid' (assumed to be the same anyway)
6561 }
6662 deriving stock (Generic)
6763 deriving (Semigroup, Monoid) via (Generically ReportInput)
6864 deriving anyclass (ToJSON)
6965
7066segregateIssues :: CheckerOutput -> ReportInput
71−segregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} =
72− ReportInput
73− { breakingChanges = P.filter isBreaking fwd
74− , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
75− , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
76− , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
77− }
67+segregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} = ReportInput
68+ { breakingChanges = P.filter isBreaking fwd
69+ , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
70+ , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
71+ , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
72+ }
7873 where
7974 isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
8075 isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
@@ -111,7 +106,8 @@ twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
111106
112107generateReport :: ReportConfig -> ReportInput -> (Blocks, ReportStatus)
113108generateReport cfg inp =
114− let schemaIssuesPresent = not $ P.null $ schemaIssues inp
109+ let
110+ schemaIssuesPresent = not $ P.null $ schemaIssues inp
115111 breakingChangesPresent = not $ P.null $ breakingChanges inp
116112 nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
117113 unsupportedChangesPresent = not $ P.null $ unsupportedChanges inp
@@ -120,55 +116,45 @@ generateReport cfg inp =
120116 OnlyErrors -> False
121117 builder = buildReport cfg
122118 report =
123− header 1 "Summary"
124− <> twoRowTable
125− ( when'
126− schemaIssuesPresent
127− [
128− ( refOpt schemaIssuesPresent schemaIssuesId "‼️ Schema issues"
129− , show' $ P.size $ schemaIssues inp
130− )
131− ]
132− ++ [
133− ( refOpt breakingChangesPresent breakingChangesId "❌ Breaking changes"
119+ header 1 "Summary"
120+ <> twoRowTable
121+ (when'
122+ schemaIssuesPresent
123+ [ ( refOpt schemaIssuesPresent schemaIssuesId "❌ Schema issues"
124+ , show' $ P.size $ schemaIssues inp
125+ )
126+ ]
127+ ++
128+ [ ( refOpt breakingChangesPresent breakingChangesId "⚠️ Breaking changes"
134129 , show' $ P.size $ breakingChanges inp
135130 )
136131 ]
137− ++ when'
138− nonBreakingChangesShown
139− [
140− ( refOpt nonBreakingChangesPresent nonBreakingChangesId "⚠️ Non-breaking changes"
141− , show' $ P.size $ nonBreakingChanges inp
142− )
143− ]
144− ++ when'
145− unsupportedChangesPresent
146− [
147− ( refOpt unsupportedChangesPresent unsupportedChangesId "❓ Unsupported feature changes"
148− , show' $ P.size $ unsupportedChanges inp
149− )
150− ]
151− )
152− <> when'
153− schemaIssuesPresent
154− ( header 1 (anchor schemaIssuesId <> "‼️ Schema issues")
155− <> builder (showErrs $ schemaIssues inp)
156− )
157− <> when'
158− breakingChangesPresent
159− ( header 1 (anchor breakingChangesId <> "❌ Breaking changes")
160− <> builder (showErrs $ breakingChanges inp)
161− )
162− <> when'
163− (nonBreakingChangesPresent && nonBreakingChangesShown)
164− ( header 1 (anchor nonBreakingChangesId <> "⚠️ Non-breaking changes")
165− <> builder (showErrs $ nonBreakingChanges inp)
166− )
167− <> when'
168− unsupportedChangesPresent
169− ( header 1 (anchor unsupportedChangesId <> "❓ Unsupported feature changes")
170− <> builder (showErrs $ unsupportedChanges inp)
171− )
132+ ++ when'
133+ nonBreakingChangesShown
134+ [ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "🙆 Non-breaking changes"
135+ , show' $ P.size $ nonBreakingChanges inp
136+ )
137+ ]
138+ ++ [ ( refOpt unsupportedChangesPresent unsupportedChangesId "🤷 Unsupported feature changes"
139+ , show' $ P.size $ unsupportedChanges inp
140+ )
141+ ])
142+ <> when'
143+ schemaIssuesPresent
144+ (header 1 (anchor schemaIssuesId <> "❌ Schema issues")
145+ <> builder (showErrs $ schemaIssues inp))
146+ <> when'
147+ breakingChangesPresent
148+ (header 1 (anchor breakingChangesId <> "⚠️ Breaking changes")
149+ <> builder (showErrs $ breakingChanges inp))
150+ <> when'
151+ (nonBreakingChangesPresent && nonBreakingChangesShown)
152+ (header 1 (anchor nonBreakingChangesId <> "🙆 Non-breaking changes")
153+ <> builder (showErrs $ nonBreakingChanges inp))
154+ <> when'
155+ unsupportedChangesPresent
156+ (header 1 (anchor unsupportedChangesId <> "🤷 Unsupported feature changes")
157+ <> builder (showErrs $ unsupportedChanges inp))
172158 status =
173159 if
174160 | breakingChangesPresent -> BreakingChanges
@@ -198,14 +184,13 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
198184 let -- Extract this pattern if more cases like this arise
199185 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
200186 , otherIssues :: Set (AnIssue a)
201− ) = case eqT @a @ 'APILevel of
187+ ) = case eqT @a @'APILevel of
202188 Just Refl
203189 | (S.toList -> p@((AnIssue ori _) : _), o) <-
204190 S.partition
205− ( \((AnIssue _ u)) -> case u of
206− NoPathsMatched {} -> True
207− AllPathsFailed {} -> True
208− )
191+ (\((AnIssue _ u)) -> case u of
192+ NoPathsMatched {} -> True
193+ AllPathsFailed {} -> True)
209194 currentIssues ->
210195 let p' = p <&> (\(AnIssue _ i) -> i)
211196 in (Just (ori, p'), o)
@@ -216,10 +201,9 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
216201 paths = case removedPaths of
217202 Just (ori, ps) -> do
218203 singletonHeader
219− ( case ori of
220− Forward -> "Removed paths"
221− Backward -> "Added paths"
222− )
204+ (case ori of
205+ Forward -> "Removed paths"
206+ Backward -> "Added paths")
223207 $ singletonBody $
224208 bulletList $
225209 ps <&> \case
@@ -255,62 +239,60 @@ jets =
255239 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
256240 strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
257241 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
258− "⬅️☁️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
259− , constructReportJet $ \InRequest InPayload PayloadSchema -> "➡️☁️ JSON Request" :: Inlines
242+ "📱⬅️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
243+ , constructReportJet $ \InRequest InPayload PayloadSchema -> "📱➡️ JSON Request" :: Inlines
260244 ]
261245 where
262246 unwrapReportJetResult :: ReportJetResult Behave x -> ReportJet' Behave x
263247 unwrapReportJetResult (Pure _) = error "There really shouldn't be any results here."
264248 unwrapReportJetResult (Free f) = f
265249
266− jsonPathJet ::
267− NonEmpty
268− ( Union
269− '[ Behave 'SchemaLevel 'TypedSchemaLevel
270− , Behave 'TypedSchemaLevel 'SchemaLevel
271− ]
272− ) ->
273− Inlines
250+ jsonPathJet
251+ :: NonEmpty
252+ ( Union
253+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
254+ , Behave 'TypedSchemaLevel 'SchemaLevel
255+ ]
256+ )
257+ -> Inlines
274258 jsonPathJet x = code $ "$" <> showParts (NE.toList x)
275259 where
276− showParts ::
277− [ Union
278− '[ Behave 'SchemaLevel 'TypedSchemaLevel
279− , Behave 'TypedSchemaLevel 'SchemaLevel
280− ]
281− ] ->
282− Text
260+ showParts
261+ :: [ Union
262+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
263+ , Behave 'TypedSchemaLevel 'SchemaLevel
264+ ]
265+ ]
266+ -> Text
283267 showParts [] = mempty
284268 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion (InProperty _)) : _)) = showParts xs
285269 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion InAdditionalProperty) : _)) = showParts xs
286270 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion InItems : _)) = showParts xs
287271 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion (InItem _) : _)) = showParts xs
288272 showParts (y : ys) =
289− ( (\(OfType t) -> "(" <> describeJSONType t <> ")")
290− @@> ( \case
291− InItems -> "[*]"
292− InItem i -> "[" <> T.pack (show i) <> "]"
293− InProperty p -> "." <> p
294− InAdditionalProperty -> ".*"
295− )
296− @@> typesExhausted
297− )
273+ ((\(OfType t) -> "(" <> describeJSONType t <> ")")
274+ @@> (\case
275+ InItems -> "[*]"
276+ InItem i -> "[" <> T.pack (show i) <> "]"
277+ InProperty p -> "." <> p
278+ InAdditionalProperty -> ".*")
279+ @@> typesExhausted)
298280 y
299281 <> showParts ys
300282
301−observeJetShowErrs ::
302− ReportJet' Behave (Maybe Inlines) ->
303− P.PathsPrefixTree Behave AnIssue a ->
304− (Report, P.PathsPrefixTree Behave AnIssue a)
283+observeJetShowErrs
284+ :: ReportJet' Behave (Maybe Inlines)
285+ -> P.PathsPrefixTree Behave AnIssue a
286+ -> (Report, P.PathsPrefixTree Behave AnIssue a)
305287observeJetShowErrs jet p = case observeJetShowErrs' jet p of
306288 Just m -> m
307289 Nothing -> (mempty, p)
308290
309−observeJetShowErrs' ::
310− forall a.
311− ReportJet' Behave (Maybe Inlines) ->
312− P.PathsPrefixTree Behave AnIssue a ->
313− Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
291+observeJetShowErrs'
292+ :: forall a.
293+ ReportJet' Behave (Maybe Inlines)
294+ -> P.PathsPrefixTree Behave AnIssue a
295+ -> Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
314296observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues) =
315297 let results =
316298 subIssues >>= \(WrapTypeable (AStep m)) ->
@@ -318,23 +300,21 @@ observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues)
318300 maybe (Left $ embed (step bhv) subErrs) Right . listToMaybe $
319301 jet @_ @_ @[] bhv
320302 & mapMaybe
321− ( \case
322− Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
323− Pure (Just h) ->
324− if P.null subErrs
325− then Just mempty
326− else Just (singletonHeader h (showErrs subErrs), mempty)
327− Pure Nothing -> Nothing
328− )
303+ (\case
304+ Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
305+ Pure (Just h) ->
306+ if P.null subErrs
307+ then Just mempty
308+ else Just (singletonHeader h (showErrs subErrs), mempty)
309+ Pure Nothing -> Nothing)
329310 in (fmap . fmap) (PathsPrefixNode currentIssues mempty <>) $
330311 if any isRight results
331312 then
332313 Just $
333314 foldMap
334− ( \case
335− Left e -> (mempty, e)
336− Right m -> m
337− )
315+ (\case
316+ Left e -> (mempty, e)
317+ Right m -> m)
338318 results
339319 else Nothing
340320
@@ -357,9 +337,8 @@ buildReport cfg = case treeStyle cfg of
357337 body rprt
358338 <> foldOMapWithKey
359339 (headers rprt)
360− ( \k v ->
361− header level k <> subBuilder v
362− )
340+ (\k v ->
341+ header level k <> subBuilder v)
363342 where
364343 subBuilder = headerStyleBuilder (level + 1)
365344
@@ -368,17 +347,16 @@ buildReport cfg = case treeStyle cfg of
368347 body rprt
369348 <> foldOMapWithKey
370349 (headers rprt)
371− ( \k v ->
372− if (OM.size . headers $ rprt) < 2
373− then para k <> blockQuote (subBuilder v)
374− else
375− rawHtml "<details>"
376− <> rawHtml "<summary>"
377− <> plain k
378− <> rawHtml "</summary>"
379− <> blockQuote (subBuilder v)
380− <> rawHtml "</details>"
381− )
350+ (\k v ->
351+ if (OM.size . headers $ rprt) < 2
352+ then para k <> blockQuote (subBuilder v)
353+ else
354+ rawHtml "<details>"
355+ <> rawHtml "<summary>"
356+ <> plain k
357+ <> rawHtml "</summary>"
358+ <> blockQuote (subBuilder v)
359+ <> rawHtml "</details>")
382360 where
383361 subBuilder = foldingStyleBuilder
384362