@@ -1,6 +1,8 @@
11module OpenAPI.Checker.Report
22 ( generateReport
3+ , CheckerOutput(..)
34 , ReportInput (..)
5+ , segregateIssues
46 , ReportStatus (..)
57 , Pandoc
68 , ReportConfig (..)
@@ -43,14 +45,38 @@ import Text.Pandoc.Builder
4345
4446type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
4547
48+data CheckerOutput = CheckerOutput
49+ { forwardChanges :: Changes
50+ , backwardChanges :: Changes
51+ }
52+ deriving stock (Generic)
53+ deriving (Semigroup, Monoid) via (Generically CheckerOutput)
54+ deriving anyclass (ToJSON)
55+
4656data ReportInput = ReportInput
47− { breakingChanges :: Changes
48− , nonBreakingChanges :: 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)
4961 }
5062 deriving stock (Generic)
5163 deriving (Semigroup, Monoid) via (Generically ReportInput)
5264 deriving anyclass (ToJSON)
5365
66+segregateIssues :: CheckerOutput -> ReportInput
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+ }
73+ where
74+ isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
75+ isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
76+ isNonBreaking xs i = isBreaking i && all (\j -> not $ relatedAnIssues i j) (P.lookup xs fwd)
77+ isUnsupported i = anIssueKind i == Unsupported
78+ isSchemaIssue i = anIssueKind i == SchemaInvalid
79+
5480data ReportStatus
5581 = BreakingChanges
5682 | NoBreakingChanges
@@ -80,13 +106,11 @@ twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
80106
81107generateReport :: ReportConfig -> ReportInput -> (Pandoc, ReportStatus)
82108generateReport cfg inp =
83− let partitionUnsupported = P.partition (\(AnIssue _ i) -> issueIsUnsupported i)
84− (bUnsupported, breaking) = partitionUnsupported $ breakingChanges inp
85− (nbUnsupported, invertIssueOrientationP -> nonBreaking) = partitionUnsupported $ nonBreakingChanges inp
86− unsupported = bUnsupported <> nbUnsupported
87− breakingChangesPresent = not $ P.null breaking
88− nonBreakingChangesPresent = not $ P.null nonBreaking
89− unsupportedChangesPresent = not $ P.null unsupported
109+ let
110+ schemaIssuesPresent = not $ P.null $ schemaIssues inp
111+ breakingChangesPresent = not $ P.null $ breakingChanges inp
112+ nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
113+ unsupportedChangesPresent = not $ P.null $ unsupportedChanges inp
90114 nonBreakingChangesShown = case reportMode cfg of
91115 All -> True
92116 OnlyErrors -> False
@@ -95,32 +119,43 @@ generateReport cfg inp =
95119 doc $
96120 header 1 "Summary"
97121 <> twoRowTable
98− ([ ( refOpt breakingChangesPresent breakingChangesId "⚠️ Breaking changes"
99− , show' $ P.size breaking
100− )
101− ]
122+ (when'
123+ schemaIssuesPresent
124+ [ ( refOpt schemaIssuesPresent schemaIssuesId "❌ Schema issues"
125+ , show' $ P.size $ schemaIssues inp
126+ )
127+ ]
128+ ++
129+ [ ( refOpt breakingChangesPresent breakingChangesId "⚠️ Breaking changes"
130+ , show' $ P.size $ breakingChanges inp
131+ )
132+ ]
102133 ++ when'
103134 nonBreakingChangesShown
104135 [ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "🙆 Non-breaking changes"
105− , show' $ P.size nonBreaking
136+ , show' $ P.size $ nonBreakingChanges inp
106137 )
107138 ]
108139 ++ [ ( refOpt unsupportedChangesPresent unsupportedChangesId "🤷 Unsupported feature changes"
109− , show' $ P.size unsupported
140+ , show' $ P.size $ unsupportedChanges inp
110141 )
111142 ])
143+ <> when'
144+ schemaIssuesPresent
145+ (header 1 (anchor schemaIssuesId <> "❌ Schema issues")
146+ <> builder (showErrs $ schemaIssues inp))
112147 <> when'
113148 breakingChangesPresent
114149 (header 1 (anchor breakingChangesId <> "⚠️ Breaking changes")
115− <> builder (showErrs breaking))
150+ <> builder (showErrs $ breakingChanges inp))
116151 <> when'
117152 (nonBreakingChangesPresent && nonBreakingChangesShown)
118153 (header 1 (anchor nonBreakingChangesId <> "🙆 Non-breaking changes")
119− <> builder (showErrs nonBreaking))
154+ <> builder (showErrs $ nonBreakingChanges inp))
120155 <> when'
121156 unsupportedChangesPresent
122157 (header 1 (anchor unsupportedChangesId <> "🤷 Unsupported feature changes")
123− <> builder (showErrs unsupported))
158+ <> builder (showErrs $ unsupportedChanges inp))
124159 status =
125160 if
126161 | breakingChangesPresent -> BreakingChanges
@@ -135,10 +170,11 @@ generateReport cfg inp =
135170 refOpt False _ i = i
136171 refOpt True a i = link ("#" <> a) "" i
137172
138− breakingChangesId, nonBreakingChangesId, unsupportedChangesId :: Text
173+ breakingChangesId, nonBreakingChangesId, unsupportedChangesId, schemaIssuesId :: Text
139174 breakingChangesId = "breaking-changes"
140175 unsupportedChangesId = "unsupported-changes"
141176 nonBreakingChangesId = "non-breaking-changes"
177+ schemaIssuesId = "schema-issues"
142178
143179 when' :: Monoid m => Bool -> m -> m
144180 when' True m = m
@@ -180,7 +216,7 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
180216 flip foldMap (M.toList m) $ \(bhv, subErrors) ->
181217 if P.null subErrors
182218 then mempty
183− else singletonHeader (describeBehaviour bhv) $ showErrs subErrors
219+ else singletonHeader (describeBehavior bhv) $ showErrs subErrors
184220 in issues <> paths <> rest
185221
186222unfoldFunctions :: forall m a. (Monoid m, Eq a) => a -> [a -> (m, a)] -> (a -> m) -> m
@@ -198,11 +234,11 @@ jets =
198234 unwrapReportJetResult
199235 <$> [ constructReportJet $
200236 curry $ \case
201− (OfType Object, p@(InPartition _)) -> Just $ describeBehaviour p :: Maybe Inlines
237+ (OfType Object, p@(InPartition _)) -> Just $ describeBehavior p :: Maybe Inlines
202238 _ -> Nothing
203239 , constructReportJet jsonPathJet
204240 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
205− strong (describeBehaviour op) <> " " <> describeBehaviour p :: Inlines
241+ strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
206242 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
207243 "📱⬅️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
208244 , constructReportJet $ \InRequest InPayload PayloadSchema -> "📱➡️ JSON Request" :: Inlines