Skip to content

thiserror finalize: drop anyhow from production, add satkit::Error façade#86

Merged
ssmichael1 merged 4 commits into
mainfrom
feat/thiserror-finalize
Apr 26, 2026
Merged

thiserror finalize: drop anyhow from production, add satkit::Error façade#86
ssmichael1 merged 4 commits into
mainfrom
feat/thiserror-finalize

Conversation

@ssmichael1
Copy link
Copy Markdown
Owner

Summary

Final PR in the anyhowthiserror migration tracked in #81. Closes #81.

Four commits in order:

  1. f3c36b5 — Phase 4: typed errors for utils::download and utils::update_data
  2. cf6a4c1 — Retype placeholder String/anyhow::Error variants now that all dependencies are migrated
  3. 56a7ca1 — Move anyhow to [dev-dependencies]
  4. 692efb2 — Add top-level satkit::Error façade

What changed

Phase 4 — utils::download::Error + utils::update_data::Error

  • Both modules migrated. Stubs now return typed download::Error::FeatureDisabled / FileNotFoundNoDownload { path }.
  • Five upstream modules (spaceweather, solar_cycle_forecast, earth_orientation_params, jplephem, earthgravity) now consume Download(#[from] download::Error) directly. The bridge From<anyhow::Error> impls are deleted.

Retyping

  • tle::Error::InvalidEpoch(String)#[from] InstantError
  • tle::Error::KeplerConversion(String)Kepler(#[from] kepler::Error)
  • orbitprop::Error::Precompute(String)Jplephem(#[from] jplephem::Error)
  • New sgp4::Error (with SatRecInit(i32) + Source(Box<dyn Error + Send + Sync>) slot) — wasn't strictly in the brief but was needed to fully eliminate anyhow from sgp4_impl.rs and the SGP4Source trait.
  • New frametransform::Error for to_gcrf/from_gcrf and IERSTable parsing — same reason.
  • Doctests updated to Ok::<(), satkit::omm::Error>(()) style.
  • Python bindings updated for the typed signatures.

anyhow move

  • anyhow is now in [dev-dependencies] only. Production code references it nowhere.
  • grep -rn "anyhow" src/ → 11 hits, all inside #[cfg(test)] mod tests blocks (test-only, kept for ergonomics).

satkit::Error façade

  • New src/error.rs with 19 variants (one per public module Error). All #[error(transparent)] + #[from].
  • pub use error::{Error, Result}; from lib.rs. Also re-exports InstantError for symmetry with Instant.
  • update_data::Error variant gated #[cfg(feature = "download")] (matches the module's gating).
fn do_thing() -> Result<(), satkit::Error> {
    let tle = satkit::TLE::from_url(url)?;            // tle::Error
    let states = satkit::orbitprop::propagate(...)?;   // orbitprop::Error
    Ok(())
}

Judgment calls worth a look

  1. tle::Error::Sgp4(String) left as Stringsgp4::Error does impl std::error::Error now, so retyping is mechanically possible. Kept as String to limit surface change in this PR; trivial follow-up if you want it.
  2. sgp4::Error::Source is Box<dyn Error + Send + Sync> rather than enum variants for OMM/TLE — adding From<omm::Error> for sgp4::Error would create an awkward inverse dependency (omm already depends on sgp4 for the trait). Boxed slot keeps the dependency arrow flowing one way.
  3. download::Error has two stub-related variants (FeatureDisabled and FileNotFoundNoDownload { path }) — download_if_not_exist stub needs to convey "file missing AND no download feature available", which is semantically distinct from the other stubs that are pure feature-gate refusals.
  4. Façade variant naming uses per-word capitalization for readability: JplEphem, SpaceWeather, EarthOrientationParams, SolarCycleForecast, EarthGravity, LpEphem, Frametransform.
  5. Test-module anyhow imports kept (per the brief). 11 use anyhow::* lines inside #[cfg(test)] blocks.

Test plan

  • cargo build — clean
  • cargo build --no-default-features — clean
  • cargo build --features download — clean
  • cargo test --release — 158 lib + 39 doc pass
  • cargo test --release --no-default-features — 157 lib + 38 doc pass
  • cargo test --release --features download — 159 lib + 41 doc pass
  • cargo check --manifest-path python/Cargo.toml — clean

Closes #81.

🤖 Generated with Claude Code

ssmichael1 and others added 4 commits April 26, 2026 18:35
Migrate the last anyhow holdouts in utils/download.rs and utils/update_data.rs
to thiserror enums, then update the five module Error types that wrapped them
(spaceweather, jplephem, earth_orientation_params, earthgravity, solar_cycle_forecast)
to consume Download(#[from] download::Error) directly instead of bridging
through anyhow::Error. The Download variant is now typed end-to-end and the
intermediate From<anyhow::Error> impls are gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… migrated

Replace the last stringified / boxed-anyhow placeholder variants with typed
#[from] sources, and migrate the two remaining anyhow-internal modules
(sgp4 and frametransform) to per-module thiserror enums. After this commit,
no production code references anyhow.

- tle::Error: InvalidEpoch(String) -> InvalidEpoch(#[from] InstantError);
  KeplerConversion(String) -> Kepler(#[from] kepler::Error). The .map_err
  shims at the call sites collapse to plain `?`.
- orbitprop::Error: Precompute(String) -> Jplephem(#[from] jplephem::Error)
  now that jplephem owns a typed Error.
- sgp4: new typed Error wrapping SatRecInit(i32) and a boxed Source slot
  for SGP4Source impls (TLE is infallible; OMM forwards omm::Error).
  Public sgp4 / sgp4_full / SGP4Source::sgp4_init_args all return
  sgp4::Result.
- frametransform: new typed Error covering UnsupportedFrame, IERS table
  parsing, and the propagating download / datadir / io / parse sources.
- Doctest snippets switch to `Ok::<(), satkit::omm::Error>(())` so the
  rendered docs match the actual API and don't rely on anyhow at all.
- Python bindings: explicit Result<SGP4State> closure annotation in the
  list path of pysgp4 and a one-line `?` cleanup in mod_utils after
  update_datafiles changed return type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production code no longer references anyhow; the only remaining uses are
inside `#[cfg(test)] mod tests` blocks, which are compiled with
[dev-dependencies]. Moving anyhow off the runtime dependency list keeps
downstream crates from inheriting a transitive anyhow dep just to use
satkit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-module thiserror enums give downstream Rust users a precise, matchable
error contract, but apps spanning many modules (e.g. a CLI that loads TLEs,
propagates orbits, and writes ephemerides) end up needing to define an
outer enum just to glue the various module errors together.

This commit adds a thin satkit::Error / satkit::Result façade that has
From impls for every public module-scoped Error in the crate. Apps that
prefer a single result type can return Result<T, satkit::Error> from the
top-level function and let `?` do the conversion at every module boundary
without giving up the typed-error guarantees underneath. The façade is
opt-in — module functions still return their narrow types.

InstantError is now also re-exported from the crate root for symmetry
with Instant.

Closes #81

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ssmichael1 ssmichael1 merged commit 92a2e0a into main Apr 26, 2026
4 checks passed
@ssmichael1 ssmichael1 deleted the feat/thiserror-finalize branch April 26, 2026 22: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.

Migrate from anyhow to per-module thiserror error enums

1 participant