-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor errors to include position, span (offset + len), and optional miette integration #95
base: main
Are you sure you want to change the base?
Conversation
* Adds type alias `crate::resolve::ResolveError` for `crate::resolve::Error` * Renames `crate::assign::AssignError` to `crate::assign::Error` * Adds type alias `crate::assign::AssignError` for crate::assign::Error` * Adds `position` (token index) to variants of `assign::Error` & `resolve::Error`
@asmello incase you happen to have a minute - can you please spot this and give me a bit of feedback on how you feel about both the degree of breaking change this is going to introduce as well as the general design? I only spot checked one crate which uses Here's a short summary, told through types: pub struct Span {
pub offset: usize,
pub len: usize,
}
pub struct Spanned<E> {
span: Span,
source: E,
}
#[cfg(feature = "miette")]
impl<E> miette::Diagnostic for Spanned<E>
where
E: 'static + miette::Diagnostic,
{
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(once(miette::LabeledSpan::new(
None, // no label - this would require an allocation (String) + doesnt seem like we can add much
self.offset(), // self.span.offset
self.len(), // self.span.len
))))
}
// all other methods are passthrough to `self::source`
}
// implements miette::Diagnostic as passthrough to Spanned<E>
pub struct Positioned<E> {
position: usize,
source: Spanned<E>,
} usage is: pub enum Error {
FailedToParseIndex(Positioned<ParseIndexError>),
OutOfBounds(Positioned<OutOfBoundsError>),
} |
I haven't used |
I will need to use the miette's |
Oh I think I understand, each I think it'll be worth playing around with a few errors and seeing how it goes. Biggest risk I see is this ends up being equivalent work to just implementing these directly for each error type. |
Oh, right! I forgot about source! So the reason I opted for the wrappers is that most of the errors themselves do not have knowledge of their surroundings. I can change that though with wrappers specific to each. /// Indicates that an `Index` is not within the given bounds.
#[derive(Debug, PartialEq, Eq)]
pub struct OutOfBoundsError {
pub length: usize,
pub index: usize,
} to #[derive(Debug, PartialEq, Eq)]
pub struct OutOfBoundsErrorWrapper {
pub source: OutOfBoundsError,
pub position: usize,
pub span: Span,
} or something like it |
Or I guess I could go the other route and refactor the error into a // assign::Error
pub struct Error {
kind: Kind,
position: usize,
span: Span,
}
pub enum Kind {
FailedToParseIndex(ParseIndexError),
OutOfBounds(OutOfBoundsError),
} |
I'm going to flush this design out to see how it works - I haven't figured out what to do about source yet (the convoluted nature of |
I have an idea for how to do this - just running into borrowck issues. I'm hoping the ergonomics are going to shake out but trait resolution may thwart my plans. The way it is anticipated to work is: let result = PointerBuf::parse_with_report("/example");
let err_report: Report<'a, ReplaceError, Pointer> = buf
.report_err()
.replace(1, "oops")
.unwrap_err(); src/error.rs// crate::error::Reporter
pub trait Reporter<T> {
type Reporter<'e>
where
Self: 'e;
// TODO: naming, alts: report, include_err_report, with_err_report, ???
fn report_err(&'_ self) -> Self::Reporter<'_>;
}
// crate::error::MutReporter
pub trait MutReporter<T> {
type Reporter<'e>
where
Self: 'e;
// TODO: naming, alts: report, include_err_report, with_err_report, ???
fn report_err(&'_ mut self) -> Self::Reporter<'_>;
} src/pointer.rspub struct MutReporter<'p> {
ptr: &'p mut PointerBuf,
}
impl<'p> MutReporter<'p> {
pub fn replace<'t>(
mut self,
index: usize,
token: impl Into<Token<'t>>,
) -> Result<Option<Token<'t>>, Report<'p, ReplaceError, Pointer>>
where
'p: 't,
{
// borrowck: can't reborrow - need to see if i can use polonius-the-crab
match self.ptr.replace(index, token) {
Ok(res) => Ok(res),
Err(source) => Err(Report {
source,
source_code: Cow::Borrowed(&*self.ptr),
}),
}
}
// rest of methods which can mutate a pointer
}
pub struct Reporter<'p> {
ptr: &'p Pointer,
}
// impl methods such as `assign`, `resolve`, etc
impl error::MutReporter<PointerBuf> for PointerBuf {
fn report_err(&'_ mut self) -> Self::Reporter<'_> {
MutReporter { ptr: self }
}
} For parsing, there would need to be a I'm trying to avoid having the user re-supply the We don't need all of the refactoring to errors - this can be done keeping the errors with struct variants. We'd just need to add edit: not sure why I had ohmyfish on my mind |
I don't follow what role |
Yea, I should have explained that better. Sorry. The reason Each pub struct Report<'a, E, T: ToOwned + ?Sized> {
pub source: E, // the error
pub source_code: Cow<'a, T>, // str or Pointer
} re: allocation - do you mean all errors could/should allocate or that |
Oh hell no, that would be an awful constraint. But they likely should have static lifetimes. So, for example, an error containing a static string is fine. In other cases the input/source can be owned. Allocate is a sort of last resort, but what I'm saying is that borrowing probably shouldn't even be attempted. This doesn't preclude the usage of |
As for the rest, thanks for the clarification. This sort of wrapping: pub struct Report<'a, E, T: ToOwned + ?Sized> {
pub source: E, // the error
pub source_code: Cow<'a, T>, // str or Pointer
} makes sense to me, although not sure how generalisable it is. Another thing to experiment with, I suppose. |
I'm game for dumping the |
@asmello i dont think this is going to work :( I have a very little playground here where 2 flavors ( I was really hoping resolution wouldn't matter - that calling I'll toy around with it a bit more but I'm out of ideas if this doesn't end up working. I guess we just let them create the report by passing the error and use jsonptr::{error::Report, resolve, PointerBuf, ReplaceError};
fn mutable() {
use jsonptr::error::{ReportErr, ReportErrMut};
let mut ptr = PointerBuf::parse("/example").unwrap();
let err: Report<ReplaceError> = ptr.report_err().replace(2, "invalid").unwrap_err();
// 👍
}
fn immutable() {
use jsonptr::error::{ReportErr, ReportErrMut};
let ptr = PointerBuf::parse("/example").unwrap();
let err: Report<resolve::Error> = ptr
.report_err()
.resolve(&serde_json::Value::Null)
.unwrap_err();
// 👍
}
fn both() {
use jsonptr::error::{ReportErr, ReportErrMut};
let mut ptr = PointerBuf::parse("/example").unwrap();
let err: Report<resolve::Error> = ptr
.report_err()
.resolve(&serde_json::Value::Null)
.unwrap_err();
// 👍
let err: Report<ReplaceError> = ptr.report_err().replace(2, "invalid").unwrap_err();
// ❌ no method named `replace` found for struct `jsonptr::reporter::Immutable` in the current scope
} https://github.com/chanced/spike-jsonptr-reports
|
I don't think this is worth it. I think we just provide a |
@asmello I refactored the errors, although I haven't gotten to the This includes breaking changes that I have commented out for I can write up some top-level methods that we can provide to emulate them though. It'll just require the source string & the error. I hate to break things like that though - I'm kinda banking on people having not had a need to use them, which is pretty weak. |
I should mention that I've got a lot cleaning up to do on it - I just let it sit idle for too long so I figured I'd push what I have. I plan on working on it over the weekend. The more I think about just dropping the offset methods, the more reluctant I become to potentially break code without a clean transitional solution. I'll see if I can come up with something. Perhaps I should make the fields private and just keep it around for now, with the methods deprecated. If you'd rather keep them the way they are - as enum structs, I totally get that. This may be just too much. |
This pull request:
Span
structure which containslen
&offset
Spanned
error wrapper struct that contains aSpan
and a source error. It implementsmiette::Diagnostic
by acting as a pass-through for all methods exceptlabels
(used to mark spans). Forlabels
, it provides a blank (None
) label with the spanPositioned
error wrapper struct that contains aSpanned
error and a position. It also implementsmiette::Diagnostic
by deferring toSpanned
Positioned
orSpanned
, depending on context.I'm just getting started with it so it will not compile right now. I should hopefully have it done today.