fix(library): surface failed sub-part resolutions in ResolutionResult#70
Open
bkfunk wants to merge 1 commit into
Open
fix(library): surface failed sub-part resolutions in ResolutionResult#70bkfunk wants to merge 1 commit into
bkfunk wants to merge 1 commit into
Conversation
`resolve_dependencies_multipart` (and its single-document sibling) used
to filter the resolver's internal state down to successfully-resolved
entries and silently drop everything else. The only signal a caller had
for a failed sub-part was the `on_update` callback — and because that
callback is `Fn`, accumulating errors from it required interior
mutability. The easiest path (passing `&|_, _| {}`) made resolution
failures vanish entirely, and the resulting `ResolutionResult` would
then bake into a part with missing geometry, with no error and no
panic.
This patch:
1. Adds `failures: HashMap<PartAlias, ResolutionError>` to
`ResolutionResult`, populated from previously-discarded `Missing`
states. Exposed via `failures()` and `has_failures()`.
2. Changes `ResolutionState::Missing` to carry the original
`ResolutionError` (`ResolutionError` is `!Clone` because of `IoError`
and `ReqwestError`, so the error lives in the state and the
`on_update` callback now receives `Result<(), &ResolutionError>`
instead of by value — a small breaking change in the callback
signature, mitigated by the fact that all in-tree callers either
already only `Display` the error or pass a no-op closure).
3. Adds a regression test exercising the silent-failure path with a
no-op callback.
Incidentally also adds `tokio` macros/rt to ldraw's dev-dependencies so
the existing `#[tokio::test]` tests in parser.rs can compile (they
weren't building before — `cargo test -p ldraw` failed on a stock
checkout). 22 tests pass after this change.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR improves dependency resolution reporting in the ldraw library by preserving failed sub-part resolutions and exposing them to callers via ResolutionResult, preventing silent baking of incomplete geometry when callers use a no-op on_update callback.
Changes:
- Extend
ResolutionResultwith afailures: HashMap<PartAlias, ResolutionError>plusfailures()/has_failures()accessors, populated from resolverMissingstates. - Update resolution tracking so
ResolutionState::Missingcarries the underlyingResolutionError, and change theon_updatecallback to passResult<(), &ResolutionError>. - Add a regression test for the “no-op callback hides missing sub-part” scenario and add a minimal
tokiodev-dependency feature set so existing#[tokio::test]tests compile.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tools/viewer/common/src/lib.rs | Propagates the updated on_update callback signature (&ResolutionError). |
| ldraw/src/library.rs | Stores resolution failures in state and surfaces them via ResolutionResult::failures; adds partition helper + regression test. |
| ldraw/Cargo.toml | Adds non-wasm dev-dependency tokio features needed for #[tokio::test] compilation. |
Comments suppressed due to low confidence (1)
ldraw/src/library.rs:144
ResolutionStateis apubenum, and this change both makesMissingcarry data and removes theClonederive. That’s a breaking public-API change in addition to theon_updatesignature update. IfResolutionStateisn’t intended to be part of the public API, consider reducing its visibility (e.g.pub(crate)), otherwise consider keeping itCloneby storing the error behindArc/Box(e.g.Missing(Arc<ResolutionError>)) so downstream code can still clone states.
#[derive(Debug)]
pub enum ResolutionState {
Missing(ResolutionError),
Pending,
Subpart,
Associated(Arc<MultipartDocument>),
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
resolve_dependencies_multipart(andresolve_dependencies) used to filter the resolver's internal state down to successfully-resolved entries and silently drop everything else, including failed resolutions. The only signal a caller had for a failed sub-part was theon_updatecallback — and because that callback isFn, accumulating errors from it required interior mutability (RefCell/Mutex/ channel).In practice, that meant the easiest, most obvious callback (
&|_, _| {}) caused resolution failures to vanish entirely. The resultingResolutionResultwould then feed intobake_part_from_*and produce a part with missing geometry, with no error and no panic.Repro
Change
Add
failures: HashMap<PartAlias, ResolutionError>toResolutionResult, populated from previously-discardedMissingstates. Exposed via:pub fn failures(&self) -> &HashMap<PartAlias, ResolutionError>pub fn has_failures(&self) -> boolResolutionState::Missingnow carries the originalResolutionError.ResolutionErroris notClone(it containsIoErrorandReqwestError), so the error has to live somewhere — and the previously-dropped state map is the natural place.on_updatecallback signature changes fromFn(PartAlias, Result<(), ResolutionError>)toFn(PartAlias, Result<(), &ResolutionError>). Small breaking change, but justified: the error now lives in the state map, and callers that want to inspect it via the callback receive a borrow instead of moving ownership. All in-tree callers either passed a no-op closure (source-compatible) or usedDisplay::fmt, which works the same against&ResolutionError.tools/viewer/common::set_documentpropagates the callback type and is updated accordingly.Regression test exercising the silent-failure path with a no-op callback, using a stub
LibraryLoaderthat always returnsFileNotFound.Incidental fix
cargo test -p ldrawdid not build on a stock checkout —parser.rsuses#[tokio::test]but the workspace'stokiodep doesn't enable themacros/rtfeatures. Added the minimal dev-dep needed (tokio = { workspace = true, features = ["macros", "rt"] }) so the existing tests compile alongside the new one. 22 tests pass after this change.Test plan
cargo check --workspacepassescargo test -p ldraw --libpasses (22 tests, including the new one)resolution_result_surfaces_missing_subpart_with_noop_callback) confirmshas_failures()andfailures()populate when a sub-part is unresolvable, even with a no-opon_update