@@ -1,13 +1,11 @@
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+ , ReportInput (..)
4+ , ReportStatus (..)
5+ , Pandoc
6+ , ReportConfig (..)
7+ , ReportTreeStyle (..)
8+ , ReportMode (..)
119 )
1210where
1311
@@ -23,15 +21,6 @@ import qualified Data.Map as M
2321import Data.Map.Ordered (OMap)
2422import qualified Data.Map.Ordered as OM
2523import 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
3524import Data.OpenUnion
3625import Data.OpenUnion.Extra
3726import Data.Set
@@ -41,47 +30,27 @@ import qualified Data.Text as T
4130import Data.TypeRepMap hiding (empty)
4231import Data.Typeable
4332import Generic.Data
33+import OpenAPI.Checker.Behavior
34+import OpenAPI.Checker.Paths
35+import OpenAPI.Checker.PathsPrefixTree hiding (empty)
36+import qualified OpenAPI.Checker.PathsPrefixTree as P hiding (empty)
37+import OpenAPI.Checker.Report.Jet
38+import OpenAPI.Checker.Subtree (invertIssueOrientationP)
39+import OpenAPI.Checker.Validate.OpenApi
40+import OpenAPI.Checker.Validate.Schema.TypedJson
41+import OpenAPI.Checker.Validate.Schema.Issues
4442import Text.Pandoc.Builder
4543
4644type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
4745
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−
5646data 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
47+ { breakingChanges :: Changes
48+ , nonBreakingChanges :: Changes
6549 }
6650 deriving stock (Generic)
6751 deriving (Semigroup, Monoid) via (Generically ReportInput)
6852 deriving anyclass (ToJSON)
6953
70−segregateIssues :: 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− }
78− where
79− isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
80− isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
81− isNonBreaking xs i = isBreaking i && all (\j -> not $ relatedAnIssues i j) (P.lookup xs fwd)
82− isUnsupported i = anIssueKind i == Unsupported
83− isSchemaIssue i = anIssueKind i == SchemaInvalid
84−
8554data ReportStatus
8655 = BreakingChanges
8756 | NoBreakingChanges
@@ -109,66 +78,49 @@ data ReportTreeStyle = HeadersTreeStyle | FoldingBlockquotesTreeStyle
10978twoRowTable :: [(Inlines, Inlines)] -> Blocks
11079twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
11180
112−generateReport :: ReportConfig -> ReportInput -> (Blocks, ReportStatus)
81+generateReport :: ReportConfig -> ReportInput -> (Pandoc, ReportStatus)
11382generateReport cfg inp =
114− let schemaIssuesPresent = not $ P.null $ schemaIssues inp
115− breakingChangesPresent = not $ P.null $ breakingChanges inp
116− nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
117− unsupportedChangesPresent = not $ P.null $ unsupportedChanges 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
11890 nonBreakingChangesShown = case reportMode cfg of
11991 All -> True
12092 OnlyErrors -> False
12193 builder = buildReport cfg
12294 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"
134− , show' $ P.size $ breakingChanges inp
95+ doc $
96+ header 1 "Summary"
97+ <> twoRowTable
98+ ([ ( refOpt breakingChangesPresent breakingChangesId "⚠️ Breaking changes"
99+ , show' $ P.size breaking
100+ )
101+ ]
102+ ++ when'
103+ nonBreakingChangesShown
104+ [ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "🙆 Non-breaking changes"
105+ , show' $ P.size nonBreaking
135106 )
136107 ]
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− )
108+ ++ [ ( refOpt unsupportedChangesPresent unsupportedChangesId "🤷 Unsupported feature changes"
109+ , show' $ P.size unsupported
110+ )
111+ ])
112+ <> when'
113+ breakingChangesPresent
114+ (header 1 (anchor breakingChangesId <> "⚠️ Breaking changes")
115+ <> builder (showErrs breaking))
116+ <> when'
117+ (nonBreakingChangesPresent && nonBreakingChangesShown)
118+ (header 1 (anchor nonBreakingChangesId <> "🙆 Non-breaking changes")
119+ <> builder (showErrs nonBreaking))
120+ <> when'
121+ unsupportedChangesPresent
122+ (header 1 (anchor unsupportedChangesId <> "🤷 Unsupported feature changes")
123+ <> builder (showErrs unsupported))
172124 status =
173125 if
174126 | breakingChangesPresent -> BreakingChanges
@@ -183,11 +135,10 @@ generateReport cfg inp =
183135 refOpt False _ i = i
184136 refOpt True a i = link ("#" <> a) "" i
185137
186− breakingChangesId, nonBreakingChangesId, unsupportedChangesId, schemaIssuesId :: Text
138+ breakingChangesId, nonBreakingChangesId, unsupportedChangesId :: Text
187139 breakingChangesId = "breaking-changes"
188140 unsupportedChangesId = "unsupported-changes"
189141 nonBreakingChangesId = "non-breaking-changes"
190− schemaIssuesId = "schema-issues"
191142
192143 when' :: Monoid m => Bool -> m -> m
193144 when' True m = m
@@ -202,10 +153,9 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
202153 Just Refl
203154 | (S.toList -> p@((AnIssue ori _) : _), o) <-
204155 S.partition
205− ( \((AnIssue _ u)) -> case u of
206− NoPathsMatched {} -> True
207− AllPathsFailed {} -> True
208− )
156+ (\((AnIssue _ u)) -> case u of
157+ NoPathsMatched {} -> True
158+ AllPathsFailed {} -> True)
209159 currentIssues ->
210160 let p' = p <&> (\(AnIssue _ i) -> i)
211161 in (Just (ori, p'), o)
@@ -216,10 +166,9 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
216166 paths = case removedPaths of
217167 Just (ori, ps) -> do
218168 singletonHeader
219− ( case ori of
220− Forward -> "Removed paths"
221− Backward -> "Added paths"
222− )
169+ (case ori of
170+ Forward -> "Removed paths"
171+ Backward -> "Added paths")
223172 $ singletonBody $
224173 bulletList $
225174 ps <&> \case
@@ -231,7 +180,7 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
231180 flip foldMap (M.toList m) $ \(bhv, subErrors) ->
232181 if P.null subErrors
233182 then mempty
234− else singletonHeader (describeBehavior bhv) $ showErrs subErrors
183+ else singletonHeader (describeBehaviour bhv) $ showErrs subErrors
235184 in issues <> paths <> rest
236185
237186unfoldFunctions :: forall m a. (Monoid m, Eq a) => a -> [a -> (m, a)] -> (a -> m) -> m
@@ -249,68 +198,66 @@ jets =
249198 unwrapReportJetResult
250199 <$> [ constructReportJet $
251200 curry $ \case
252− (OfType Object, p@(InPartition _)) -> Just $ describeBehavior p :: Maybe Inlines
201+ (OfType Object, p@(InPartition _)) -> Just $ describeBehaviour p :: Maybe Inlines
253202 _ -> Nothing
254203 , constructReportJet jsonPathJet
255204 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
256− strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
205+ strong (describeBehaviour op) <> " " <> describeBehaviour p :: Inlines
257206 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
258− "⬅️☁️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
259− , constructReportJet $ \InRequest InPayload PayloadSchema -> "➡️☁️ JSON Request" :: Inlines
207+ "📱⬅️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
208+ , constructReportJet $ \InRequest InPayload PayloadSchema -> "📱➡️ JSON Request" :: Inlines
260209 ]
261210 where
262211 unwrapReportJetResult :: ReportJetResult Behave x -> ReportJet' Behave x
263212 unwrapReportJetResult (Pure _) = error "There really shouldn't be any results here."
264213 unwrapReportJetResult (Free f) = f
265214
266− jsonPathJet ::
267− NonEmpty
268− ( Union
269− '[ Behave 'SchemaLevel 'TypedSchemaLevel
270− , Behave 'TypedSchemaLevel 'SchemaLevel
271− ]
272− ) ->
273− Inlines
215+ jsonPathJet
216+ :: NonEmpty
217+ ( Union
218+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
219+ , Behave 'TypedSchemaLevel 'SchemaLevel
220+ ]
221+ )
222+ -> Inlines
274223 jsonPathJet x = code $ "$" <> showParts (NE.toList x)
275224 where
276− showParts ::
277− [ Union
278− '[ Behave 'SchemaLevel 'TypedSchemaLevel
279− , Behave 'TypedSchemaLevel 'SchemaLevel
280− ]
281− ] ->
282− Text
225+ showParts
226+ :: [ Union
227+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
228+ , Behave 'TypedSchemaLevel 'SchemaLevel
229+ ]
230+ ]
231+ -> Text
283232 showParts [] = mempty
284233 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion (InProperty _)) : _)) = showParts xs
285234 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion InAdditionalProperty) : _)) = showParts xs
286235 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion InItems : _)) = showParts xs
287236 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion (InItem _) : _)) = showParts xs
288237 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− )
238+ ((\(OfType t) -> "(" <> describeJSONType t <> ")")
239+ @@> (\case
240+ InItems -> "[*]"
241+ InItem i -> "[" <> T.pack (show i) <> "]"
242+ InProperty p -> "." <> p
243+ InAdditionalProperty -> ".*")
244+ @@> typesExhausted)
298245 y
299246 <> showParts ys
300247
301−observeJetShowErrs ::
302− ReportJet' Behave (Maybe Inlines) ->
303− P.PathsPrefixTree Behave AnIssue a ->
304− (Report, P.PathsPrefixTree Behave AnIssue a)
248+observeJetShowErrs
249+ :: ReportJet' Behave (Maybe Inlines)
250+ -> P.PathsPrefixTree Behave AnIssue a
251+ -> (Report, P.PathsPrefixTree Behave AnIssue a)
305252observeJetShowErrs jet p = case observeJetShowErrs' jet p of
306253 Just m -> m
307254 Nothing -> (mempty, p)
308255
309−observeJetShowErrs' ::
310− forall a.
311− ReportJet' Behave (Maybe Inlines) ->
312− P.PathsPrefixTree Behave AnIssue a ->
313− Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
256+observeJetShowErrs'
257+ :: forall a.
258+ ReportJet' Behave (Maybe Inlines)
259+ -> P.PathsPrefixTree Behave AnIssue a
260+ -> Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
314261observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues) =
315262 let results =
316263 subIssues >>= \(WrapTypeable (AStep m)) ->
@@ -318,23 +265,21 @@ observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues)
318265 maybe (Left $ embed (step bhv) subErrs) Right . listToMaybe $
319266 jet @_ @_ @[] bhv
320267 & 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− )
268+ (\case
269+ Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
270+ Pure (Just h) ->
271+ if P.null subErrs
272+ then Just mempty
273+ else Just (singletonHeader h (showErrs subErrs), mempty)
274+ Pure Nothing -> Nothing)
329275 in (fmap . fmap) (PathsPrefixNode currentIssues mempty <>) $
330276 if any isRight results
331277 then
332278 Just $
333279 foldMap
334− ( \case
335− Left e -> (mempty, e)
336− Right m -> m
337− )
280+ (\case
281+ Left e -> (mempty, e)
282+ Right m -> m)
338283 results
339284 else Nothing
340285
@@ -357,9 +302,8 @@ buildReport cfg = case treeStyle cfg of
357302 body rprt
358303 <> foldOMapWithKey
359304 (headers rprt)
360− ( \k v ->
361− header level k <> subBuilder v
362− )
305+ (\k v ->
306+ header level k <> subBuilder v)
363307 where
364308 subBuilder = headerStyleBuilder (level + 1)
365309
@@ -368,17 +312,16 @@ buildReport cfg = case treeStyle cfg of
368312 body rprt
369313 <> foldOMapWithKey
370314 (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− )
315+ (\k v ->
316+ if (OM.size . headers $ rprt) < 2
317+ then para k <> blockQuote (subBuilder v)
318+ else
319+ rawHtml "<details>"
320+ <> rawHtml "<summary>"
321+ <> plain k
322+ <> rawHtml "</summary>"
323+ <> blockQuote (subBuilder v)
324+ <> rawHtml "</details>")
382325 where
383326 subBuilder = foldingStyleBuilder
384327