Repository: zkat/miette (rust)

Issue:
JSON renderer does not render causes
Hello there,

It looks like rendering causes was not implemented for the JSON renderer.

```plaintext
Error: up::error::sql

  × SQL query failed
  ├─▶ error returned from database: operator does not exist: uuid = text
  ╰─▶ operator does not exist: uuid = text
```

Example JSON output:

```json
{"message": "SQL query failed","code": "up::error::sql","severity": "error","labels": [],"related": []}
```

I am not sure if this was an intentional omission, but it would be helpful to provide additional context if it were there, especially in log aggregators.

Cheers!



Pull request description:
Support causes in JSON renderer
Fixes: https://github.com/zkat/miette/issues/187



Patch:
diff --git a/src/handlers/json.rs b/src/handlers/json.rs
index cab38ba..ec214ca 100644
--- a/src/handlers/json.rs
+++ b/src/handlers/json.rs
@@ -1,6 +1,8 @@
 use std::fmt::{self, Write};
 
-use crate::{protocol::Diagnostic, ReportHandler, Severity, SourceCode};
+use crate::{
+    diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode,
+};
 
 /**
 [`ReportHandler`] that renders JSON output. It's a machine-readable output.
@@ -79,6 +81,25 @@ impl JSONReportHandler {
             Some(Severity::Advice) => "advice",
         };
         write!(f, r#""severity": "{:}","#, severity)?;
+        if let Some(cause_iter) = diagnostic
+            .diagnostic_source()
+            .map(DiagnosticChain::from_diagnostic)
+            .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror))
+        {
+            write!(f, r#""causes": ["#)?;
+            let mut add_comma = false;
+            for error in cause_iter {
+                if add_comma {
+                    write!(f, ",")?;
+                } else {
+                    add_comma = true;
+                }
+                write!(f, r#""{}""#, escape(&error.to_string()))?;
+            }
+            write!(f, "],")?
+        } else {
+            write!(f, r#""causes": [],"#)?;
+        }
         if let Some(url) = diagnostic.url() {
             write!(f, r#""url": "{}","#, &url.to_string())?;
         }


Test patch:
diff --git a/tests/test_json.rs b/tests/test_json.rs
index acdfb73..5bd14cb 100644
--- a/tests/test_json.rs
+++ b/tests/test_json.rs
@@ -1,5 +1,4 @@
 mod json_report_handler {
-
     use miette::{Diagnostic, MietteError, NamedSource, Report, SourceSpan};
 
     use miette::JSONReportHandler;
@@ -38,6 +37,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -83,6 +83,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -128,6 +129,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -173,6 +175,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -218,6 +221,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -262,6 +266,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -313,6 +318,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -372,6 +378,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -427,6 +434,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -500,6 +508,10 @@ mod json_report_handler {
             "message": "wtf?!\nit broke :(",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [
+                "something went wrong\n\nHere's a more detailed explanation of everything that actually went wrong because it's actually important.\n",
+                "very much went wrong"
+            ],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -554,6 +566,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -609,6 +622,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -664,6 +678,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -706,6 +721,7 @@ mod json_report_handler {
         {
             "message": "oops!",
             "severity": "error",
+            "causes": [],
             "url": "https://example.com",
             "help": "try doing it better next time?",
             "labels": [],
@@ -757,6 +773,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -772,6 +789,7 @@ mod json_report_handler {
                 "message": "oops!",
                 "code": "oops::my::bad",
                 "severity": "error",
+                "causes": [],
                 "help": "try doing it better next time?",
                 "filename": "bad_file2.rs",
                 "labels": [
@@ -788,6 +806,7 @@ mod json_report_handler {
                 "message": "oops!",
                 "code": "oops::my::bad",
                 "severity": "error",
+                "causes": [],
                 "help": "try doing it better next time?",
                 "filename": "bad_file3.rs",
                 "labels": [
@@ -852,6 +871,7 @@ mod json_report_handler {
             "message": "oops!",
             "code": "oops::my::bad",
             "severity": "error",
+            "causes": [],
             "help": "try doing it better next time?",
             "filename": "bad_file.rs",
             "labels": [
@@ -867,6 +887,7 @@ mod json_report_handler {
                 "message": "oops!",
                 "code": "oops::my::bad",
                 "severity": "error",
+                "causes": [],
                 "help": "try doing it better next time?",
                 "filename": "bad_file.rs",
                 "labels": [
@@ -883,6 +904,7 @@ mod json_report_handler {
                 "message": "oops!",
                 "code": "oops::my::bad",
                 "severity": "error",
+                "causes": [],
                 "help": "try doing it better next time?",
                 "filename": "bad_file.rs",
                 "labels": [