diff --git a/Cargo.toml b/Cargo.toml index 848999d..50dbca5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ supports-unicode = { version = "2.0.0", optional = true } backtrace = { version = "0.3.61", optional = true } terminal_size = { version = "0.2.6", optional = true } backtrace-ext = { version = "0.2.1", optional = true } -serde = { version = "1.0.162", optional = true } +serde = { version = "1.0.162", features = ["derive"], optional = true } [dev-dependencies] semver = "1.0.4" @@ -41,6 +41,8 @@ syn = { version = "2.0", features = ["full"] } regex = "1.5" lazy_static = "1.4" +serde_json = { version = "1.0.64" } + [features] default = [] no-format-args-capture = [] diff --git a/src/protocol.rs b/src/protocol.rs index 0f606f6..6fd5eb1 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -9,6 +9,9 @@ use std::{ panic::Location, }; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::MietteError; /// Adds rich metadata to your Error that can be used by @@ -163,6 +166,7 @@ impl From> for Box, span: SourceSpan, @@ -210,7 +240,7 @@ pub struct LabeledSpan { impl LabeledSpan { /// Makes a new labeled span. - pub fn new(label: Option, offset: ByteOffset, len: ByteOffset) -> Self { + pub fn new(label: Option, offset: ByteOffset, len: usize) -> Self { Self { label, span: (offset, len).into(), @@ -299,6 +329,48 @@ impl LabeledSpan { } } +#[cfg(feature = "serde")] +#[test] +fn test_serialize_labeled_span() { + use serde_json::json; + + assert_eq!( + json!(LabeledSpan::new(None, 0, 0)), + json!({ + "label": null, + "span": { "offset": 0, "length": 0 } + }) + ); + + assert_eq!( + json!(LabeledSpan::new(Some("label".to_string()), 0, 0)), + json!({ + "label": "label", + "span": { "offset": 0, "length": 0 } + }) + ) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_labeled_span() { + use serde_json::json; + + let span: LabeledSpan = serde_json::from_value(json!({ + "label": null, + "span": { "offset": 0, "length": 0 } + })) + .unwrap(); + assert_eq!(span, LabeledSpan::new(None, 0, 0)); + + let span: LabeledSpan = serde_json::from_value(json!({ + "label": "label", + "span": { "offset": 0, "length": 0 } + })) + .unwrap(); + assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0)) +} + /** Contents of a [`SourceCode`] covered by [`SourceSpan`]. @@ -402,15 +474,14 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> { } } -/** -Span within a [`SourceCode`] with an associated message. -*/ +/// Span within a [`SourceCode`] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SourceSpan { /// The start of the span. offset: SourceOffset, - /// The total length of the span. Think of this as an offset from `start`. - length: SourceOffset, + /// The total length of the span + length: usize, } impl SourceSpan { @@ -418,7 +489,7 @@ impl SourceSpan { pub fn new(start: SourceOffset, length: SourceOffset) -> Self { Self { offset: start, - length, + length: length.offset(), } } @@ -429,18 +500,18 @@ impl SourceSpan { /// Total length of the [`SourceSpan`], in bytes. pub fn len(&self) -> usize { - self.length.offset() + self.length } /// Whether this [`SourceSpan`] has a length of zero. It may still be useful /// to point to a specific point. pub fn is_empty(&self) -> bool { - self.length.offset() == 0 + self.length == 0 } } -impl From<(ByteOffset, ByteOffset)> for SourceSpan { - fn from((start, len): (ByteOffset, ByteOffset)) -> Self { +impl From<(ByteOffset, usize)> for SourceSpan { + fn from((start, len): (ByteOffset, usize)) -> Self { Self { offset: start.into(), length: len.into(), @@ -450,10 +521,7 @@ impl From<(ByteOffset, ByteOffset)> for SourceSpan { impl From<(SourceOffset, SourceOffset)> for SourceSpan { fn from((start, len): (SourceOffset, SourceOffset)) -> Self { - Self { - offset: start, - length: len, - } + Self::new(start, len) } } @@ -461,17 +529,14 @@ impl From> for SourceSpan { fn from(range: std::ops::Range) -> Self { Self { offset: range.start.into(), - length: range.len().into(), + length: range.len(), } } } impl From for SourceSpan { fn from(offset: SourceOffset) -> Self { - Self { - offset, - length: 0.into(), - } + Self { offset, length: 0 } } } @@ -479,11 +544,31 @@ impl From for SourceSpan { fn from(offset: ByteOffset) -> Self { Self { offset: offset.into(), - length: 0.into(), + length: 0, } } } +#[cfg(feature = "serde")] +#[test] +fn test_serialize_source_span() { + use serde_json::json; + + assert_eq!( + json!(SourceSpan::from(0)), + json!({ "offset": 0, "length": 0}) + ) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_source_span() { + use serde_json::json; + + let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap(); + assert_eq!(span, SourceSpan::from(0)) +} + /** "Raw" type for the byte offset from the beginning of a [`SourceCode`]. */ @@ -493,6 +578,7 @@ pub type ByteOffset = usize; Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`] */ #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SourceOffset(ByteOffset); impl SourceOffset { @@ -576,3 +662,18 @@ fn test_source_offset_from_location() { source.len() ); } + +#[cfg(feature = "serde")] +#[test] +fn test_serialize_source_offset() { + use serde_json::json; + + assert_eq!(json!(SourceOffset::from(0)), 0) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_source_offset() { + let offset: SourceOffset = serde_json::from_str("0").unwrap(); + assert_eq!(offset, SourceOffset::from(0)) +}