Skip to content
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

Validation::is_valid #1279

Merged
merged 18 commits into from
Dec 19, 2024
Merged

Validation::is_valid #1279

merged 18 commits into from
Dec 19, 2024

Conversation

michaelkirk
Copy link
Member

@michaelkirk michaelkirk commented Dec 9, 2024

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

Fixes #123, #127, #1268

Much of the original code and test cases are courtesy of https://github.com/mthh/geo-validity-check (thanks @mthh!).

I also enabled the JTS IsValid test cases from our existing JTS test suite.

My approach:

First, I just copy/pasted the existing implementation from https://github.com/mthh/geo-validity-check in: 92d8bfc

And then wired it into our crate: 5dd1909

Most significantly, I did a lot of refactoring in pursuit of deduplicating code in b89e715, so any bugs are my own.

Note to merger: please don't squash these commits, they're somewhat meaningful.

@michaelkirk michaelkirk force-pushed the mkirk/mthh_is_valid_2 branch 3 times, most recently from 69757d9 to 7b5225f Compare December 10, 2024 00:22
@michaelkirk michaelkirk force-pushed the mkirk/mthh_is_valid_2 branch 2 times, most recently from 7f140cc to ed0e43a Compare December 10, 2024 03:55
geo/Cargo.toml Outdated Show resolved Hide resolved
#[cfg(debug_assertions)]
if right_position != current_position {
use crate::algorithm::Validation;
if geometry_graph.geometry().is_valid() {
Copy link
Member Author

Choose a reason for hiding this comment

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

This should address #1268

right_position != current_position should not happen normally, and this debug_assertion was originally intended to find bugs in geo code. However, it's also been shown to happen when given invalid geometry input.

We can now distinguish between a hypothetical bug in geo code from a bug in the caller's code, and just log in that latter case.

@michaelkirk michaelkirk marked this pull request as ready for review December 10, 2024 04:02
@michaelkirk michaelkirk requested a review from mthh December 10, 2024 04:02
@michaelkirk
Copy link
Member Author

@mthh - since this is largely based on your implementation, and you've done some work with validation, would you mind reviewing this?

Copy link
Member

@frewsxcv frewsxcv left a comment

Choose a reason for hiding this comment

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

Exciting! Can't wait for this to land

Definitely not done reviewing, just did a first pass and added a few comments

geo/CHANGES.md Outdated Show resolved Hide resolved
geo/src/algorithm/validation/mod.rs Show resolved Hide resolved
geo/src/algorithm/validation/mod.rs Outdated Show resolved Hide resolved
Copy link
Member

@frewsxcv frewsxcv left a comment

Choose a reason for hiding this comment

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

Another pass

geo/src/algorithm/validation/geometry.rs Outdated Show resolved Hide resolved
/// The closure `handle_validation_error` is called for each validation error.
fn visit_validation<T>(
&self,
handle_validation_error: Box<dyn FnMut(Self::Error) -> Result<(), T> + '_>,
Copy link
Member

Choose a reason for hiding this comment

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

Presumably something like this wasn't possible?

handle_validation_error: FnMut(Self::Error) -> Result<(), T>,

Copy link
Member Author

Choose a reason for hiding this comment

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

Because of the potentially infinite recursion of GeometryCollection, we cannot use a true generic impl FnMut - it must be boxed.

Is that what you were asking about?

Copy link
Member Author

Choose a reason for hiding this comment

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

Here's the error message when you switch to: handle_validation_error: impl FnMut(Self::Error) -> Result<(), T>,

error: reached the recursion limit while instantiating `validation::geometry_collection::<impl Validation for geo::GeometryCollection>::visit_validation::<geo::validation::InvalidGeometry, Box<{closure@validation::geometry::<impl Validation for geo::Geometry>::visit_validation<..., ...>::{closure#7}}>>`
  --> /Users/mkirk/src/georust/geo/geo/src/algorithm/validation/geometry.rs:91:48
   |
91 |               Geometry::GeometryCollection(g) => g.visit_validation(Box::new(|err| {
   |  ________________________________________________^
92 | |                 handle_validation_error(InvalidGeometry::InvalidGeometryCollection(err))
93 | |             }))?,
   | |_______________^
   |
note: `validation::geometry_collection::<impl Validation for geo::GeometryCollection<F>>::visit_validation` defined here
  --> /Users/mkirk/src/georust/geo/geo/src/algorithm/validation/geometry_collection.rs:28:5
   |
28 | /     fn visit_validation<T>(
29 | |         &self,
30 | |         mut handle_validation_error: impl FnMut(Self::Error) -> Result<(), T>,
31 | |     ) -> Result<(), T> {
   | |______________________^
   = note: the full type name has been written to '/Users/mkirk/src/georust/geo/target/debug/deps/jts_test_runner-201a42bc420bd6fb.long-type.txt'


Copy link
Member

Choose a reason for hiding this comment

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

I was thinking something like handle_validation_error: impl FnMut(Box<Self::Error>) -> Result<(), T>,

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think that addresses the potentially infinite recursion of the closure - without boxing the closure, the compiler must build a type which can store the state of a closure which validates a GeometryCollection inside a GeometryCollection inside a GeometryCollection inside a...

I confess I'm not that confident in my analysis, but I did have a go at your suggestion here, but ultimately hit the same compiler error:

Reached the recursion limit while instantiating validation::geometry_collection::<impl Validation for geo::GeometryCollection>::visit_validation::<geo::validation::InvalidGeometry, {closure@validation::geometry::<impl Validation for geo::Geometry>::visit_validation<geo::validation::InvalidGeometry, ...>::{closure#7}}>

geo/src/algorithm/validation/mod.rs Outdated Show resolved Hide resolved
Copy link
Member

@frewsxcv frewsxcv left a comment

Choose a reason for hiding this comment

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

I replied with one more comment but this looks good to me! Thanks for putting the effort into making this happen

/// The closure `handle_validation_error` is called for each validation error.
fn visit_validation<T>(
&self,
handle_validation_error: Box<dyn FnMut(Self::Error) -> Result<(), T> + '_>,
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking something like handle_validation_error: impl FnMut(Box<Self::Error>) -> Result<(), T>,

@michaelkirk
Copy link
Member Author

Thanks for the review @frewsxcv!

I'll give it another day before merging in case anyone else wants to look.

@mthh
Copy link
Member

mthh commented Dec 18, 2024

@michaelkirk thank you for inviting me to review it, unfortunately I won't have the time to do so.
Anyway I'm glad to see the Validation trait land in geo, thanks for the effort :)

@michaelkirk michaelkirk added this pull request to the merge queue Dec 18, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 18, 2024
@michaelkirk
Copy link
Member Author

See #1283 for details on the unrelated CI failure.

Note: some tests are failing
    failed TestValid2.xml case "Test 740" with error: expected true, actual: false
    failed TestValid2.xml case "Test 749" with error: expected true, actual: false
    failed TestValid2.xml case "Test 752" with error: expected true, actual: false
    failed TestValid2.xml case "Test 753" with error: expected true, actual: false
    failed TestValid2.xml case "Test 754" with error: expected true, actual: false

Note: this commit is empty, just a commit message.
Previously the `is_valid` vs `explain_invalidity` methods were mostly a
cut/paste job.  `explain_invalidity` gathered all the errors, while
`is_valid` short-circuited to return early as soon as it new a geometry
was invalid.

Now both methods share a code path, in the new `visit_validation` method.

And there's another new method: `check_validation` which, because it
returns a result can be used nicely in error handling:

```
polygon.check_validation()?;
```

This also leverages generics (associated types) to return specific error
cases per geometry, which replaces some runtime `unreachable` checks
with compile time type checks.
@michaelkirk michaelkirk force-pushed the mkirk/mthh_is_valid_2 branch from b4d6493 to 2b4ded7 Compare December 19, 2024 20:34
@michaelkirk michaelkirk added this pull request to the merge queue Dec 19, 2024
Merged via the queue into main with commit 785c1d8 Dec 19, 2024
18 checks passed
@michaelkirk michaelkirk deleted the mkirk/mthh_is_valid_2 branch December 19, 2024 20:41
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.

Dealing with Validity
4 participants