Skip to content

Commit

Permalink
rename diagnostic --> report, add into_owned
Browse files Browse the repository at this point in the history
  • Loading branch information
asmello committed Nov 18, 2024
1 parent cf3bcfa commit 3c9ff88
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//!
use crate::{
diagnostic::{impl_diagnostic_url, IntoDiagnostic, Label},
diagnostic::{impl_diagnostic_url, Diagnostic, Label},
index::{OutOfBoundsError, ParseIndexError},
Pointer,
};
Expand Down Expand Up @@ -228,7 +228,7 @@ impl fmt::Display for Error {
}
}

impl<'s> IntoDiagnostic<'s> for Error {
impl<'s> Diagnostic<'s> for Error {
type Subject = Cow<'s, Pointer>;

fn url() -> &'static str {
Expand Down
129 changes: 96 additions & 33 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
use core::fmt;

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

/// Enrich the error with its subject.
fn enrich(self, subject: impl Into<Self::Subject>) -> Diagnostic<'s, Self> {
Diagnostic::new(self, subject)
/// Combine the error with its subject to generate a [`Report`].
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<'s, Self> {
Report::new(self, subject)
}

/// The docs.rs URL for this error
Expand Down Expand Up @@ -41,15 +41,14 @@ impl From<Label> for miette::LabeledSpan {
}
}

/// An error wrapper which includes the [`String`] which failed to parse or the
/// [`PointerBuf`] being used.
/// An error wrapper which includes the subject of the failure.
#[derive(Clone, PartialEq, Eq)]
pub struct Diagnostic<'s, S: IntoDiagnostic<'s>> {
pub struct Report<'s, S: Diagnostic<'s>> {
source: S,
subject: S::Subject,
}

impl<'s, S: IntoDiagnostic<'s>> Diagnostic<'s, S> {
impl<'s, S: Diagnostic<'s>> Report<'s, S> {
/// Create a new `Report` with the given subject and error.
fn new(source: S, subject: impl Into<S::Subject>) -> Self {
Self {
Expand All @@ -68,15 +67,36 @@ impl<'s, S: IntoDiagnostic<'s>> Diagnostic<'s, S> {
&self.source
}

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

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

Report {
// TODO: is there a way to avoid the `Into` here? We really want `S == O`,
// but I'm strugggling to express that without a bound predicate cycle
source: self.source.into(),
subject: self.subject.into_owned(),
}
}
}

impl<'s, S> core::ops::Deref for Report<'s, S>
where
S: Diagnostic<'s>,
{
type Target = S;

Expand All @@ -85,18 +105,18 @@ where
}
}

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

impl<'s, S> fmt::Debug for Diagnostic<'s, S>
impl<'s, S> fmt::Debug for Report<'s, S>
where
S: IntoDiagnostic<'s> + fmt::Debug,
S: Diagnostic<'s> + fmt::Debug,
S::Subject: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand All @@ -108,9 +128,9 @@ where
}

#[cfg(feature = "std")]
impl<'s, S> std::error::Error for Diagnostic<'s, S>
impl<'s, S> std::error::Error for Report<'s, S>
where
S: IntoDiagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S::Subject: fmt::Debug,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Expand All @@ -119,9 +139,9 @@ where
}

#[cfg(feature = "miette")]
impl<'s, S> miette::Diagnostic for Diagnostic<'s, S>
impl<'s, S> miette::Diagnostic for Report<'s, S>
where
S: IntoDiagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S::Subject: fmt::Debug + miette::SourceCode,
{
fn url<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
Expand Down Expand Up @@ -167,44 +187,69 @@ macro_rules! impl_diagnostic_url {
pub(crate) use impl_diagnostic_url;

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

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

pub trait IntoOwned {
type Owned: 'static;

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

impl<'s> IntoOwned for Cow<'s, str> {

Check failure on line 203 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L203

error: the following explicit lifetimes could be elided: 's --> src/diagnostic.rs:203:10 | 203 | impl<'s> IntoOwned for Cow<'s, str> { | ^^ ^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes note: the lint level is defined here --> src/lib.rs:38:9 | 38 | #![deny(clippy::all, clippy::pedantic)] | ^^^^^^^^^^^ = note: `#[deny(clippy::needless_lifetimes)]` implied by `#[deny(clippy::all)]` help: elide the lifetimes | 203 - impl<'s> IntoOwned for Cow<'s, str> { 203 + impl IntoOwned for Cow<'_, str> { |
Raw output
src/diagnostic.rs:203:10:e:error: the following explicit lifetimes could be elided: 's
   --> src/diagnostic.rs:203:10
    |
203 |     impl<'s> IntoOwned for Cow<'s, str> {
    |          ^^                    ^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
note: the lint level is defined here
   --> src/lib.rs:38:9
    |
38  | #![deny(clippy::all, clippy::pedantic)]
    |         ^^^^^^^^^^^
    = note: `#[deny(clippy::needless_lifetimes)]` implied by `#[deny(clippy::all)]`
help: elide the lifetimes
    |
203 -     impl<'s> IntoOwned for Cow<'s, str> {
203 +     impl IntoOwned for Cow<'_, str> {
    |


__END__
type Owned = Cow<'static, str>;

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

impl<'s> IntoOwned for Cow<'s, Pointer> {

Check failure on line 211 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L211

error: the following explicit lifetimes could be elided: 's --> src/diagnostic.rs:211:10 | 211 | impl<'s> IntoOwned for Cow<'s, Pointer> { | ^^ ^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes help: elide the lifetimes | 211 - impl<'s> IntoOwned for Cow<'s, Pointer> { 211 + impl IntoOwned for Cow<'_, Pointer> { |
Raw output
src/diagnostic.rs:211:10:e:error: the following explicit lifetimes could be elided: 's
   --> src/diagnostic.rs:211:10
    |
211 |     impl<'s> IntoOwned for Cow<'s, Pointer> {
    |          ^^                    ^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
help: elide the lifetimes
    |
211 -     impl<'s> IntoOwned for Cow<'s, Pointer> {
211 +     impl IntoOwned for Cow<'_, Pointer> {
    |


__END__
type Owned = Cow<'static, Pointer>;

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

pub trait Diagnose<'s, T> {

Check warning on line 220 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L220

warning: missing documentation for a trait --> src/diagnostic.rs:220:1 | 220 | pub trait Diagnose<'s, T> { | ^^^^^^^^^^^^^^^^^^^^^^^^^
Raw output
src/diagnostic.rs:220:1:w:warning: missing documentation for a trait
   --> src/diagnostic.rs:220:1
    |
220 | pub trait Diagnose<'s, T> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^


__END__
type Error: IntoDiagnostic<'s>;
type Error: Diagnostic<'s>;

Check warning on line 221 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L221

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


__END__

#[allow(clippy::missing_errors_doc)]
fn diagnose(

Check warning on line 224 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L224

warning: missing documentation for a method --> src/diagnostic.rs:224:5 | 224 | / fn diagnose( 225 | | self, 226 | | subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>, 227 | | ) -> Result<T, Report<'s, Self::Error>>; | |____________________________________________^
Raw output
src/diagnostic.rs:224:5:w:warning: missing documentation for a method
   --> src/diagnostic.rs:224:5
    |
224 | /     fn diagnose(
225 | |         self,
226 | |         subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
227 | |     ) -> Result<T, Report<'s, Self::Error>>;
    | |____________________________________________^


__END__
self,
subject: impl Into<<Self::Error as IntoDiagnostic<'s>>::Subject>,
) -> Result<T, Diagnostic<'s, Self::Error>>;
subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
) -> Result<T, Report<'s, Self::Error>>;

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

Check warning on line 230 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L230

warning: missing documentation for a method --> src/diagnostic.rs:230:5 | 230 | / fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<'s, Self::Error>> 231 | | where 232 | | F: FnOnce() -> S, 233 | | S: Into<<Self::Error as Diagnostic<'s>>::Subject>; | |__________________________________________________________^
Raw output
src/diagnostic.rs:230:5:w:warning: missing documentation for a method
   --> src/diagnostic.rs:230:5
    |
230 | /     fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<'s, Self::Error>>
231 | |     where
232 | |         F: FnOnce() -> S,
233 | |         S: Into<<Self::Error as Diagnostic<'s>>::Subject>;
    | |__________________________________________________________^


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

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

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

fn diagnose_with<F, S>(self, f: F) -> Result<T, Diagnostic<'s, Self::Error>>
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<'s, Self::Error>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as IntoDiagnostic<'s>>::Subject>,
S: Into<<Self::Error as Diagnostic<'s>>::Subject>,
{
self.diagnose(f())
}
Expand All @@ -213,7 +258,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::{Pointer, PointerBuf};
use crate::{ParseError, Pointer, PointerBuf};

#[test]
#[cfg(all(
Expand Down Expand Up @@ -246,4 +291,22 @@ mod tests {
let report = miette::Report::from(report);
println!("{report:?}");
}

#[test]
fn into_owned() {
// NOTE: the type has to be fully specified here because of the `S: Into<O>` bound
// in `Report::into_owned`. Ideally we'd express `S == O` so that the output can
// be automatically inferred.
let owned_report: Report<'static, ParseError> = {
// 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}");
}
}
10 changes: 6 additions & 4 deletions src/pointer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
diagnostic::{impl_diagnostic_url, Diagnostic, IntoDiagnostic, Label},
diagnostic::{impl_diagnostic_url, Diagnostic, Label, Report},
token::InvalidEncodingError,
Components, Token, Tokens,
};
Expand Down Expand Up @@ -942,11 +942,11 @@ 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, Diagnostic<'s, ParseError>> {
pub fn parse<'s>(s: impl Into<Cow<'s, str>>) -> Result<Self, RichParseError<'s>> {
let s = s.into();
match validate(&s) {
Ok(_) => Ok(Self(s.into_owned())),
Err(err) => Err(err.enrich(s)),
Err(err) => Err(err.into_report(s)),
}
}

Expand Down Expand Up @@ -1277,7 +1277,7 @@ impl ParseError {
}
}

impl<'s> IntoDiagnostic<'s> for ParseError {
impl<'s> Diagnostic<'s> for ParseError {
type Subject = Cow<'s, str>;

fn url() -> &'static str {
Expand Down Expand Up @@ -1387,6 +1387,8 @@ impl std::error::Error for ParseError {
}
}

pub type RichParseError<'s> = Report<'s, ParseError>;

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
Expand Down

0 comments on commit 3c9ff88

Please sign in to comment.