Skip to content

Refactor InvalidSchemaError to support multiple errors #246

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

Merged
merged 2 commits into from
Apr 16, 2025

Conversation

popematt
Copy link
Contributor

@popematt popematt commented Apr 14, 2025

Issue #, if available:

#228

Description of changes:

  • Refactors the crate's result module to...
    • Support multiple errors in InvalidSchemaError
    • Support error location in InvalidSchemaError
    • More cleanly wrap IoError
    • Eliminate the need for a format! when calling invalid_schema_error.

This change looks big, but a lot of the diff is swapping various invalid_schema_error for the new invalid_schema! macro.

See comments in the individual files to guide your attention to the important parts. If a file does not have a "❗" comment, then the changes are just switching from invalid_schema_error(format!(..., invalid_schema_error_raw(format!(..., etc. to the new invalid_schema! macro (and consequent cargo fmt changes).


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Comment on lines -52 to -54
///
/// Technically, this is a smart pointer that (rather than managing the ownership or memory of the
/// wrapped value) encodes Ion Schema version information along with the wrapped value.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ This was an out of date comment. Versioned is no longer a "smart pointer".

Comment on lines +45 to 51
source_location: IslSourceLocation,
}
impl PartialEq for Import {
fn eq(&self, other: &Self) -> bool {
self.schema_id == other.schema_id && self.type_import == other.type_import
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_location is extra metadata, not part of the semantic meaning of an Import, so we need a custom implementation of PartialEq to exclude it.

Comment on lines +24 to +29
source_location: IslSourceLocation,
}
impl PartialEq for TypeDefinition {
fn eq(&self, other: &Self) -> bool {
self.constraints == other.constraints && self.open_content == other.open_content
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ As with Import, source_location is extra metadata in TypeDefinition, not actually part of the equality, so we need a custom implementation of PartialEq.

Comment on lines +16 to +24
source_location: IslSourceLocation,
// Not exposed in public API. Initially `None`, but changed to `Some(_)` when resolving the schemas
resolved_type_coordinates: Option<TypeCoordinates>,
}
impl PartialEq for TypeReference {
fn eq(&self, other: &Self) -> bool {
self.schema_id == other.schema_id && self.type_name == other.type_name
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ Again, source_location is extra metadata, not actually part of the equality, so we need a custom implementation of PartialEq. In this case, it also exposed that the resolved_type_coordinates are not part of the "equality" definition either—two type references should be equivalent if they refer to the same type, regardless of whether one or both have been resolved.

One(InvalidSchemaDetails),
Many(BTreeSet<InvalidSchemaDetails>),
}
impl Display for InvalidSchemaErrorQuantity {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ When there are multiple errors in an InvalidSchemaError, they are displayed like this:

foo/bar.isl:24:13 > range start must not be greater than range end
foo/bar.isl:105:22 > top level type missing name
foo/baz.isl:1:1 > unknown schema version 3.14

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this new Display for error reporting!


/// Collects multiple errors to report them together.
///
/// ```ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ This is ignore because doc tests can only use things that are visible outside of the crate, and InvalidSchemaErrorCollector is only pub(crate).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ When reviewing this file, you should not view changes from all commits. Exclude the first commit of the PR (the commit that moves the file from result.rs to mod/result.rs) to see how the content of the result mod has actually changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ If you look at only the first commit of this PR, you will see that this file was moved to result/mod.rs.

use crate::violation::Violation;
use ion_rs::IonError;
use std::convert::Infallible;
use std::fmt::{Debug, Write};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix this before merging.

Suggested change
use std::fmt::{Debug, Write};
use std::fmt::Debug;

/// A macro that checks some condition required to be valid ISL.
///
/// If invalid, returns an InvalidSchemaErr with the given error message.
#[macro_export]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ This internal-only macro was inappropriately being exported by the crate. I've replaced the #[macro_export] with a pub(crate) use instead.

/// ```
#[derive(Debug, Default)]
pub(crate) struct InvalidSchemaErrorCollector {
errors: BTreeSet<InvalidSchemaDetails>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question) Why are we using a BTreeSet here. I don't think the errors collected has to be sorted unless I am missing something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two different parts to this question:

I don't think the errors collected has to be sorted

In order to have a better user experience, they are sorted by schema_id, row, and column. Even though reading the ISL may occur in order and errors would already be sorted, resolving the imports and type references is a separate step (and itself includes multiple steps) so errors are not sorted by default.

Why are we using a BTreeSet here

Because I didn't think it was appropriate to allocate a vec to hold a sorted copy of the error details in the Display impl. The other option is to sort them as they are aggregated, for which the BTreeSet is the natural choice. This, however, is an implementation detail that can change if necessary.

One(InvalidSchemaDetails),
Many(BTreeSet<InvalidSchemaDetails>),
}
impl Display for InvalidSchemaErrorQuantity {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this new Display for error reporting!

@popematt popematt merged commit e5f742a into amazon-ion:dev-1.0.0 Apr 16, 2025
3 checks passed
@popematt popematt deleted the multi-error-2 branch April 18, 2025 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants