Skip to content

Commit

Permalink
refactor a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
chanced committed Nov 28, 2024
1 parent 1404c61 commit d844954
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 93 deletions.
7 changes: 4 additions & 3 deletions src/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
use crate::{
diagnostic::{impl_diagnostic_url, Diagnostic, Label},
index::{OutOfBoundsError, ParseIndexError},
Pointer,
Pointer, PointerBuf,
};
use alloc::borrow::Cow;
use core::{
Expand Down Expand Up @@ -228,8 +228,9 @@ impl fmt::Display for Error {
}
}

impl<'s> Diagnostic<'s> for Error {
type Subject = Cow<'s, Pointer>;
impl Diagnostic for Error {
type Subject = PointerBuf;
type Related = ();

fn url() -> &'static str {
impl_diagnostic_url!(enum assign::Error)
Expand Down
136 changes: 53 additions & 83 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
//! Error reporting data structures and miette integration.
use core::fmt;
use core::{fmt, ops::Deref};

/// Implemented by errors which can be converted into a [`Report`].
pub trait Diagnostic<'s>: Sized + private::Sealed {
pub trait Diagnostic: Sized {
/// The value which caused the error.
type Subject: private::IntoOwned;
type Subject: Deref;

// TODO: this is here to handle multiple errors, if we choose to support it. remove if not needed.
/// Optional type of related errors for miette reporting.
///
/// This is required as this trait is not object safe. Implementations which
/// do not have related errors should use `()`.
type Related;

/// Combine the error with its subject to generate a [`Report`].
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<Self, Self::Subject> {
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<Self> {
Report {
source: self,
subject: subject.into(),
Expand All @@ -20,6 +27,12 @@ pub trait Diagnostic<'s>: Sized + private::Sealed {

/// Returns the label for the given [`Subject`] if applicable.
fn labels(&self, subject: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>>;

// TODO: this is here to handle multiple errors, if we choose to support it. remove if not needed.
// The idea is that we will need
fn related(&self) -> Option<Box<dyn Iterator<Item = Self::Related>>> {

Check warning on line 33 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L33

warning: missing documentation for a method --> src/diagnostic.rs:33:5 | 33 | fn related(&self) -> Option<Box<dyn Iterator<Item = Self::Related>>> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Raw output
src/diagnostic.rs:33:5:w:warning: missing documentation for a method
  --> src/diagnostic.rs:33:5
   |
33 |     fn related(&self) -> Option<Box<dyn Iterator<Item = Self::Related>>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


__END__
None
}
}

/// A label for a span within a json pointer or malformed string.
Expand All @@ -46,87 +59,69 @@ impl From<Label> for miette::LabeledSpan {

/// An error wrapper which includes the subject of the failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report<SRC, SUB> {
source: SRC,
subject: SUB,
pub struct Report<D: Diagnostic> {
source: D,
subject: D::Subject,
}

impl<SRC, SUB> Report<SRC, SUB> {
impl<D: Diagnostic> Report<D> {
/// The value which caused the error.
pub fn subject(&self) -> &SUB {
pub fn subject(&self) -> &<D::Subject as Deref>::Target {
&self.subject
}

/// The error which occurred.
pub fn original(&self) -> &SRC {
pub fn original(&self) -> &D {
&self.source
}

/// The original parts of the [`Report`].
pub fn decompose(self) -> (SRC, SUB) {
pub fn decompose(self) -> (D, D::Subject) {
(self.source, self.subject)
}
}

impl<'s, S> Report<S, <S as Diagnostic<'s>>::Subject>
where
S: Diagnostic<'s>,
{
/// Converts the Report into an owned instance (generally by cloning the subject).
pub fn into_owned(self) -> Report<S, <S::Subject as private::IntoOwned>::Owned> {
use private::IntoOwned;

Report {
source: self.source,
subject: self.subject.into_owned(),
}
}
}

impl<SRC, SUB> core::ops::Deref for Report<SRC, SUB> {
type Target = SRC;
impl<D: Diagnostic> core::ops::Deref for Report<D> {
type Target = D;

fn deref(&self) -> &Self::Target {
&self.source
}
}

impl<SRC, SUB> fmt::Display for Report<SRC, SUB>
where
SRC: fmt::Display,
{
impl<D: Diagnostic + fmt::Display> fmt::Display for Report<D> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt::Display::fmt(&self.source, f)
}
}

#[cfg(feature = "std")]
impl<SRC, SUB> std::error::Error for Report<SRC, SUB>
impl<D> std::error::Error for Report<D>
where
SRC: fmt::Debug + std::error::Error + 'static,
SUB: fmt::Debug,
D: Diagnostic + fmt::Debug + std::error::Error + 'static,
D::Subject: fmt::Debug,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}

#[cfg(feature = "miette")]
impl<'s, SRC> miette::Diagnostic for Report<SRC, SRC::Subject>
impl<D> miette::Diagnostic for Report<D>
where
SRC: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
SRC::Subject: fmt::Debug + miette::SourceCode,
D: Diagnostic + fmt::Debug + std::error::Error + 'static,
D::Subject: fmt::Debug + miette::SourceCode,
{
fn url<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
Some(Box::new(SRC::url()))
Some(Box::new(D::url()))
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.subject)
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(SRC::labels(self, &self.subject)?.map(Into::into)))
Some(Box::new(D::labels(self, &self.subject)?.map(Into::into)))
}
}

Expand Down Expand Up @@ -160,66 +155,44 @@ macro_rules! impl_diagnostic_url {
pub(crate) use impl_diagnostic_url;

mod private {
use alloc::borrow::Cow;

pub trait Sealed {}
impl Sealed for crate::pointer::ParseError {}
impl Sealed for crate::assign::Error {}

pub trait IntoOwned {
type Owned;

fn into_owned(self) -> Self::Owned;
}

impl<'s, T: 'static + ToOwned + ?Sized> IntoOwned for Cow<'s, T> {
type Owned = Cow<'static, T>;

fn into_owned(self) -> Cow<'static, T> {
Cow::Owned(self.into_owned())
}
}
}

pub trait Diagnose<'s, T> {
type Error: Diagnostic<'s>;
type Error: Diagnostic;

Check warning on line 164 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L164

warning: missing documentation for an associated type --> src/diagnostic.rs:164:5 | 164 | type Error: Diagnostic; | ^^^^^^^^^^^^^^^^^^^^^^
Raw output
src/diagnostic.rs:164:5:w:warning: missing documentation for an associated type
   --> src/diagnostic.rs:164:5
    |
164 |     type Error: Diagnostic;
    |     ^^^^^^^^^^^^^^^^^^^^^^


__END__

#[allow(clippy::missing_errors_doc)]
fn diagnose(
self,
subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>;
subject: impl Into<<Self::Error as Diagnostic>::Subject>,
) -> Result<T, Report<Self::Error>>;

#[allow(clippy::missing_errors_doc)]
fn diagnose_with<F, S>(
self,
f: F,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>>

Check warning on line 173 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L173

warning: missing documentation for a method --> src/diagnostic.rs:173:5 | 173 | / fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>> 174 | | where 175 | | F: FnOnce() -> S, 176 | | S: Into<<Self::Error as Diagnostic>::Subject>; | |______________________________________________________^
Raw output
src/diagnostic.rs:173:5:w:warning: missing documentation for a method
   --> src/diagnostic.rs:173:5
    |
173 | /     fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>>
174 | |     where
175 | |         F: FnOnce() -> S,
176 | |         S: Into<<Self::Error as Diagnostic>::Subject>;
    | |______________________________________________________^


__END__
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic<'s>>::Subject>;
S: Into<<Self::Error as Diagnostic>::Subject>;
}

impl<'s, T, E> Diagnose<'s, T> for Result<T, E>
where
E: Diagnostic<'s>,
E: Diagnostic,
{
type Error = E;

fn diagnose(
self,
subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>> {
subject: impl Into<<Self::Error as Diagnostic>::Subject>,
) -> Result<T, Report<Self::Error>> {
self.map_err(|error| error.into_report(subject.into()))
}

fn diagnose_with<F, S>(
self,
f: F,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic<'s>>::Subject>,
S: Into<<Self::Error as Diagnostic>::Subject>,
{
self.diagnose(f())
}
Expand Down Expand Up @@ -264,16 +237,13 @@ mod tests {

#[test]
fn into_owned() {
let owned_report = {
// creating owned string to ensure its lifetime is local
// (could also coerce a static reference, but this is less brittle)
let invalid = "/foo/bar/invalid~3~encoding/cannot/reach".to_string();
let report = Pointer::parse(&invalid)
.diagnose(invalid.as_str())
.unwrap_err();
report.into_owned()
};

println!("{owned_report}");
// creating owned string to ensure its lifetime is local
// (could also coerce a static reference, but this is less brittle)
let invalid = "/foo/bar/invalid~3~encoding/cannot/reach".to_string();
let report = Pointer::parse(&invalid)
.diagnose(invalid.as_str())
.unwrap_err();

println!("{report}");
}
}
14 changes: 7 additions & 7 deletions src/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ macro_rules! impl_source_code {
};
}

impl_source_code!(Pointer, &Pointer);
impl_source_code!(Pointer, &Pointer, PointerBuf);

impl<'p> From<&'p Pointer> for Cow<'p, Pointer> {
fn from(value: &'p Pointer) -> Self {
Expand Down Expand Up @@ -942,10 +942,10 @@ impl PointerBuf {
///
/// ## Errors
/// Returns a [`RichParseError`] if the string is not a valid JSON Pointer.
pub fn parse<'s>(s: impl Into<Cow<'s, str>>) -> Result<Self, RichParseError<'s>> {
pub fn parse(s: impl Into<String>) -> Result<Self, RichParseError> {
let s = s.into();
match validate(&s) {
Ok(_) => Ok(Self(s.into_owned())),
Ok(_) => Ok(Self(s)),
Err(err) => Err(err.into_report(s)),
}
}
Expand Down Expand Up @@ -1277,15 +1277,15 @@ impl ParseError {
}
}

impl<'s> Diagnostic<'s> for ParseError {
type Subject = Cow<'s, str>;
impl Diagnostic for ParseError {
type Subject = String;
type Related = ();

fn url() -> &'static str {
impl_diagnostic_url!(struct ParseError)
}

fn labels(&self, subject: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>> {
// TODO: considering searching the pointer for all invalid encodings
let offset = self.complete_offset();
let len = self.invalid_encoding_len(subject);
let text = match self {
Expand Down Expand Up @@ -1387,7 +1387,7 @@ impl std::error::Error for ParseError {
}
}

pub type RichParseError<'s> = Report<ParseError, <ParseError as Diagnostic<'s>>::Subject>;
pub type RichParseError = Report<ParseError>;

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Expand Down

0 comments on commit d844954

Please sign in to comment.