Skip to content

Commit c25676c

Browse files
authoredMay 14, 2023
feat(serde): Add serde support (#264)
Fixes: #260
1 parent 024145d commit c25676c

File tree

4 files changed

+251
-23
lines changed

4 files changed

+251
-23
lines changed
 

‎Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ supports-unicode = { version = "2.0.0", optional = true }
2727
backtrace = { version = "0.3.61", optional = true }
2828
terminal_size = { version = "0.1.17", optional = true }
2929
backtrace-ext = { version = "0.2.1", optional = true }
30+
serde = { version = "1.0.162", features = ["derive"], optional = true }
3031

3132
[dev-dependencies]
3233
semver = "1.0.4"
@@ -40,6 +41,8 @@ syn = { version = "2.0", features = ["full"] }
4041
regex = "1.5"
4142
lazy_static = "1.4"
4243

44+
serde_json = "1.0.64"
45+
4346
[features]
4447
default = []
4548
no-format-args-capture = []

‎src/eyreish/macros.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ macro_rules! miette {
284284
/// ```
285285
#[macro_export]
286286
macro_rules! diagnostic {
287-
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {{
287+
($fmt:literal $($arg:tt)*) => {{
288+
$crate::MietteDiagnostic::new(format!($fmt $($arg)*))
289+
}};
290+
($($key:ident = $value:expr,)+ $fmt:literal $($arg:tt)*) => {{
288291
let mut diag = $crate::MietteDiagnostic::new(format!($fmt $($arg)*));
289292
$(diag.$key = Some($value.into());)*
290293
diag

‎src/miette_diagnostic.rs

+115
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ use std::{
33
fmt::{Debug, Display},
44
};
55

6+
#[cfg(feature = "serde")]
7+
use serde::{Deserialize, Serialize};
8+
69
use crate::{Diagnostic, LabeledSpan, Severity};
710

811
/// Diagnostic that can be created at runtime.
912
#[derive(Debug, Clone, PartialEq, Eq)]
13+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1014
pub struct MietteDiagnostic {
1115
/// Displayed diagnostic message
1216
pub message: String,
@@ -15,17 +19,22 @@ pub struct MietteDiagnostic {
1519
/// in the toplevel crate's documentation for easy searching.
1620
/// Rust path format (`foo::bar::baz`) is recommended, but more classic
1721
/// codes like `E0123` will work just fine
22+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1823
pub code: Option<String>,
1924
/// [`Diagnostic`] severity. Intended to be used by
2025
/// [`ReportHandler`](crate::ReportHandler)s to change the way different
2126
/// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`]
27+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2228
pub severity: Option<Severity>,
2329
/// Additional help text related to this Diagnostic
30+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2431
pub help: Option<String>,
2532
/// URL to visit for a more detailed explanation/help about this
2633
/// [`Diagnostic`].
34+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2735
pub url: Option<String>,
2836
/// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
37+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2938
pub labels: Option<Vec<LabeledSpan>>,
3039
}
3140

@@ -248,3 +257,109 @@ impl MietteDiagnostic {
248257
self
249258
}
250259
}
260+
261+
#[cfg(feature = "serde")]
262+
#[test]
263+
fn test_serialize_miette_diagnostic() {
264+
use serde_json::json;
265+
266+
use crate::diagnostic;
267+
268+
let diag = diagnostic!("message");
269+
let json = json!({ "message": "message" });
270+
assert_eq!(json!(diag), json);
271+
272+
let diag = diagnostic!(
273+
code = "code",
274+
help = "help",
275+
url = "url",
276+
labels = [
277+
LabeledSpan::at_offset(0, "label1"),
278+
LabeledSpan::at(1..3, "label2")
279+
],
280+
severity = Severity::Warning,
281+
"message"
282+
);
283+
let json = json!({
284+
"message": "message",
285+
"code": "code",
286+
"help": "help",
287+
"url": "url",
288+
"severity": "Warning",
289+
"labels": [
290+
{
291+
"span": {
292+
"offset": 0,
293+
"length": 0
294+
},
295+
"label": "label1"
296+
},
297+
{
298+
"span": {
299+
"offset": 1,
300+
"length": 2
301+
},
302+
"label": "label2"
303+
}
304+
]
305+
});
306+
assert_eq!(json!(diag), json);
307+
}
308+
309+
#[cfg(feature = "serde")]
310+
#[test]
311+
fn test_deserialize_miette_diagnostic() {
312+
use serde_json::json;
313+
314+
use crate::diagnostic;
315+
316+
let json = json!({ "message": "message" });
317+
let diag = diagnostic!("message");
318+
assert_eq!(diag, serde_json::from_value(json).unwrap());
319+
320+
let json = json!({
321+
"message": "message",
322+
"help": null,
323+
"code": null,
324+
"severity": null,
325+
"url": null,
326+
"labels": null
327+
});
328+
assert_eq!(diag, serde_json::from_value(json).unwrap());
329+
330+
let diag = diagnostic!(
331+
code = "code",
332+
help = "help",
333+
url = "url",
334+
labels = [
335+
LabeledSpan::at_offset(0, "label1"),
336+
LabeledSpan::at(1..3, "label2")
337+
],
338+
severity = Severity::Warning,
339+
"message"
340+
);
341+
let json = json!({
342+
"message": "message",
343+
"code": "code",
344+
"help": "help",
345+
"url": "url",
346+
"severity": "Warning",
347+
"labels": [
348+
{
349+
"span": {
350+
"offset": 0,
351+
"length": 0
352+
},
353+
"label": "label1"
354+
},
355+
{
356+
"span": {
357+
"offset": 1,
358+
"length": 2
359+
},
360+
"label": "label2"
361+
}
362+
]
363+
});
364+
assert_eq!(diag, serde_json::from_value(json).unwrap());
365+
}

‎src/protocol.rs

+129-22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use std::{
99
panic::Location,
1010
};
1111

12+
#[cfg(feature = "serde")]
13+
use serde::{Deserialize, Serialize};
14+
1215
use crate::MietteError;
1316

1417
/// Adds rich metadata to your Error that can be used by
@@ -163,6 +166,7 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Sen
163166
[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
164167
*/
165168
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
169+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
166170
pub enum Severity {
167171
/// Just some help. Here's how you could be doing it better.
168172
Advice,
@@ -179,6 +183,31 @@ impl Default for Severity {
179183
}
180184
}
181185

186+
#[cfg(feature = "serde")]
187+
#[test]
188+
fn test_serialize_severity() {
189+
use serde_json::json;
190+
191+
assert_eq!(json!(Severity::Advice), json!("Advice"));
192+
assert_eq!(json!(Severity::Warning), json!("Warning"));
193+
assert_eq!(json!(Severity::Error), json!("Error"));
194+
}
195+
196+
#[cfg(feature = "serde")]
197+
#[test]
198+
fn test_deserialize_severity() {
199+
use serde_json::json;
200+
201+
let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
202+
assert_eq!(severity, Severity::Advice);
203+
204+
let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
205+
assert_eq!(severity, Severity::Warning);
206+
207+
let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
208+
assert_eq!(severity, Severity::Error);
209+
}
210+
182211
/**
183212
Represents readable source code of some sort.
184213
@@ -203,14 +232,16 @@ pub trait SourceCode: Send + Sync {
203232

204233
/// A labeled [`SourceSpan`].
205234
#[derive(Debug, Clone, PartialEq, Eq)]
235+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
206236
pub struct LabeledSpan {
237+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
207238
label: Option<String>,
208239
span: SourceSpan,
209240
}
210241

211242
impl LabeledSpan {
212243
/// Makes a new labeled span.
213-
pub fn new(label: Option<String>, offset: ByteOffset, len: ByteOffset) -> Self {
244+
pub fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
214245
Self {
215246
label,
216247
span: (offset, len).into(),
@@ -299,6 +330,53 @@ impl LabeledSpan {
299330
}
300331
}
301332

333+
#[cfg(feature = "serde")]
334+
#[test]
335+
fn test_serialize_labeled_span() {
336+
use serde_json::json;
337+
338+
assert_eq!(
339+
json!(LabeledSpan::new(None, 0, 0)),
340+
json!({
341+
"span": { "offset": 0, "length": 0 }
342+
})
343+
);
344+
345+
assert_eq!(
346+
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
347+
json!({
348+
"label": "label",
349+
"span": { "offset": 0, "length": 0 }
350+
})
351+
)
352+
}
353+
354+
#[cfg(feature = "serde")]
355+
#[test]
356+
fn test_deserialize_labeled_span() {
357+
use serde_json::json;
358+
359+
let span: LabeledSpan = serde_json::from_value(json!({
360+
"label": null,
361+
"span": { "offset": 0, "length": 0 }
362+
}))
363+
.unwrap();
364+
assert_eq!(span, LabeledSpan::new(None, 0, 0));
365+
366+
let span: LabeledSpan = serde_json::from_value(json!({
367+
"span": { "offset": 0, "length": 0 }
368+
}))
369+
.unwrap();
370+
assert_eq!(span, LabeledSpan::new(None, 0, 0));
371+
372+
let span: LabeledSpan = serde_json::from_value(json!({
373+
"label": "label",
374+
"span": { "offset": 0, "length": 0 }
375+
}))
376+
.unwrap();
377+
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
378+
}
379+
302380
/**
303381
Contents of a [`SourceCode`] covered by [`SourceSpan`].
304382
@@ -402,23 +480,22 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
402480
}
403481
}
404482

405-
/**
406-
Span within a [`SourceCode`] with an associated message.
407-
*/
483+
/// Span within a [`SourceCode`]
408484
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
485+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
409486
pub struct SourceSpan {
410487
/// The start of the span.
411488
offset: SourceOffset,
412-
/// The total length of the span. Think of this as an offset from `start`.
413-
length: SourceOffset,
489+
/// The total length of the span
490+
length: usize,
414491
}
415492

416493
impl SourceSpan {
417494
/// Create a new [`SourceSpan`].
418495
pub fn new(start: SourceOffset, length: SourceOffset) -> Self {
419496
Self {
420497
offset: start,
421-
length,
498+
length: length.offset(),
422499
}
423500
}
424501

@@ -429,61 +506,75 @@ impl SourceSpan {
429506

430507
/// Total length of the [`SourceSpan`], in bytes.
431508
pub fn len(&self) -> usize {
432-
self.length.offset()
509+
self.length
433510
}
434511

435512
/// Whether this [`SourceSpan`] has a length of zero. It may still be useful
436513
/// to point to a specific point.
437514
pub fn is_empty(&self) -> bool {
438-
self.length.offset() == 0
515+
self.length == 0
439516
}
440517
}
441518

442-
impl From<(ByteOffset, ByteOffset)> for SourceSpan {
443-
fn from((start, len): (ByteOffset, ByteOffset)) -> Self {
519+
impl From<(ByteOffset, usize)> for SourceSpan {
520+
fn from((start, len): (ByteOffset, usize)) -> Self {
444521
Self {
445522
offset: start.into(),
446-
length: len.into(),
523+
length: len,
447524
}
448525
}
449526
}
450527

451528
impl From<(SourceOffset, SourceOffset)> for SourceSpan {
452529
fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
453-
Self {
454-
offset: start,
455-
length: len,
456-
}
530+
Self::new(start, len)
457531
}
458532
}
459533

460534
impl From<std::ops::Range<ByteOffset>> for SourceSpan {
461535
fn from(range: std::ops::Range<ByteOffset>) -> Self {
462536
Self {
463537
offset: range.start.into(),
464-
length: range.len().into(),
538+
length: range.len(),
465539
}
466540
}
467541
}
468542

469543
impl From<SourceOffset> for SourceSpan {
470544
fn from(offset: SourceOffset) -> Self {
471-
Self {
472-
offset,
473-
length: 0.into(),
474-
}
545+
Self { offset, length: 0 }
475546
}
476547
}
477548

478549
impl From<ByteOffset> for SourceSpan {
479550
fn from(offset: ByteOffset) -> Self {
480551
Self {
481552
offset: offset.into(),
482-
length: 0.into(),
553+
length: 0,
483554
}
484555
}
485556
}
486557

558+
#[cfg(feature = "serde")]
559+
#[test]
560+
fn test_serialize_source_span() {
561+
use serde_json::json;
562+
563+
assert_eq!(
564+
json!(SourceSpan::from(0)),
565+
json!({ "offset": 0, "length": 0})
566+
)
567+
}
568+
569+
#[cfg(feature = "serde")]
570+
#[test]
571+
fn test_deserialize_source_span() {
572+
use serde_json::json;
573+
574+
let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
575+
assert_eq!(span, SourceSpan::from(0))
576+
}
577+
487578
/**
488579
"Raw" type for the byte offset from the beginning of a [`SourceCode`].
489580
*/
@@ -493,6 +584,7 @@ pub type ByteOffset = usize;
493584
Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
494585
*/
495586
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
587+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
496588
pub struct SourceOffset(ByteOffset);
497589

498590
impl SourceOffset {
@@ -576,3 +668,18 @@ fn test_source_offset_from_location() {
576668
source.len()
577669
);
578670
}
671+
672+
#[cfg(feature = "serde")]
673+
#[test]
674+
fn test_serialize_source_offset() {
675+
use serde_json::json;
676+
677+
assert_eq!(json!(SourceOffset::from(0)), 0)
678+
}
679+
680+
#[cfg(feature = "serde")]
681+
#[test]
682+
fn test_deserialize_source_offset() {
683+
let offset: SourceOffset = serde_json::from_str("0").unwrap();
684+
assert_eq!(offset, SourceOffset::from(0))
685+
}

0 commit comments

Comments
 (0)
Please sign in to comment.