hpr_net/lib.rs
1//! Optional online data sources with an on-disk cache and an explicit offline mode: weather,
2//! soundings, elevation, ThrustCurve and motor stock.
3//!
4//! **Guide:** [Online data and the cache][guide-page] says what works today and what is planned.
5//!
6//! [guide-page]: https://nrdptel.github.io/hpr-sim/online-data.html
7//! [weather]: https://nrdptel.github.io/hpr-sim/weather.html
8//! [soundings]: https://nrdptel.github.io/hpr-sim/soundings.html
9//! [nomads]: https://nrdptel.github.io/hpr-sim/nomads.html
10//! [elevation]: https://nrdptel.github.io/hpr-sim/elevation.html
11//! [motor-stock]: https://nrdptel.github.io/hpr-sim/motor-stock.html
12//! [roadmap]: https://github.com/nrdptel/hpr-sim/blob/main/docs/ROADMAP.md
13//!
14//! Status: pre-alpha, with six data sources: [`open_meteo`], a launch site's weather as a
15//! sounding ([M5.2a][roadmap], the first weather increment; the [weather page][weather] explains
16//! it); [`wyoming`], weather-balloon soundings from the University of Wyoming's archive
17//! ([M5.2b][roadmap]; the [soundings page][soundings]); [`nomads`], NOAA's GFS and RAP
18//! forecasts as GRIB2 cuts from NOMADS ([M5.2c][roadmap]; the [NOAA forecasts page][nomads]);
19//! [`elevation`], a site's ground height from Open-Meteo ([M5.3b][roadmap]; the
20//! [elevation page][elevation]); [`motor_finder`], motor stock and prices from
21//! motor.fusionspace.co ([M5.4a][roadmap]; the [motor stock page][motor-stock]); and
22//! [`thrustcurve`], motor records and thrust curves from ThrustCurve.org, matched to the motors in
23//! stock ([M5.4b][roadmap]; the same page), and [`on_demand`], one motor found there by name for
24//! a simulator that lacks its curve ([M4.5b][roadmap]). This
25//! crate does network and file I/O, so it is never a dependency of the pure core. Milestone
26//! [M5.1][roadmap] added the cache, the offline mode and HTTP: a [`Client`] asks a [`Transport`] for a URL's bytes only when it is [`Mode::Online`]
27//! and its [`Cache`] holds no fresh copy. In [`Mode::Offline`] it never calls the transport; it
28//! answers from the cache, stale or not, and says which. The `http` feature adds `Http`, the
29//! transport over HTTP and HTTPS (rustls, no OpenSSL), and [`Cache::platform_dir`] names the
30//! platform's usual cache folder.
31//!
32//! ```
33//! use hpr_net::{Cache, Client, Freshness, Mode, NetError, Source, Transport};
34//!
35//! /// A transport that answers every URL with the same bytes.
36//! struct Canned;
37//! impl Transport for Canned {
38//! fn get(&self, _url: &str) -> Result<Vec<u8>, String> {
39//! Ok(b"42".to_vec())
40//! }
41//! }
42//!
43//! let dir = std::env::temp_dir().join(format!("hpr-net-doc-{}", std::process::id()));
44//! let source = Source {
45//! name: "Example".into(),
46//! attribution: "Example data".into(),
47//! ttl_s: 3600,
48//! };
49//! let url = "https://example.test/x";
50//! let online = Client::new(Canned, Cache::new(&dir), Mode::Online);
51//! assert_eq!(online.fetch(&source, url, 1_000)?.freshness, Freshness::Fetched);
52//!
53//! let offline = Client::new(Canned, Cache::new(&dir), Mode::Offline);
54//! let later = offline.fetch(&source, url, 1_000 + 7_200)?;
55//! assert_eq!((later.freshness, later.body.as_slice()), (Freshness::Stale, &b"42"[..]));
56//! # std::fs::remove_dir_all(&dir).ok();
57//! # Ok::<(), NetError>(())
58//! ```
59
60#![allow(
61 clippy::disallowed_methods,
62 clippy::disallowed_types,
63 reason = "the online layer does network and cache I/O; it is not part of the pure core"
64)]
65
66mod cache;
67mod civil;
68mod client;
69mod coordinate;
70pub mod elevation;
71#[cfg(feature = "http")]
72mod http;
73pub mod motor_finder;
74pub mod nomads;
75pub mod on_demand;
76pub mod open_meteo;
77pub mod thrustcurve;
78pub mod wyoming;
79
80pub use cache::{Cache, CacheEntry};
81pub use client::{Client, Fetched, Freshness, Mode, Replay, Source, Transport};
82#[cfg(feature = "http")]
83pub use http::{Http, HttpConfig};
84
85/// Why a fetch failed.
86#[non_exhaustive]
87#[derive(Debug, thiserror::Error)]
88pub enum NetError {
89 /// Offline, and the cache holds no copy of the URL.
90 #[error("offline and {url} is not in the cache")]
91 NotCached {
92 /// The URL asked for.
93 url: String,
94 },
95 /// The transport failed (no connection, an HTTP error status, a timeout).
96 #[error("fetching {url} failed: {reason}")]
97 Transport {
98 /// The URL asked for.
99 url: String,
100 /// What the transport said.
101 reason: String,
102 },
103 /// A body was fetched or cached, but the data source's check refused it
104 /// ([`Client::fetch_checked`]); nothing was cached.
105 #[error("the answer for {url} was refused: {reason}")]
106 Refused {
107 /// The URL asked for.
108 url: String,
109 /// Why the check refused it.
110 reason: String,
111 },
112 /// Reading or writing the cache failed.
113 #[error("cache I/O at {path}: {source}")]
114 Cache {
115 /// The file involved.
116 path: std::path::PathBuf,
117 /// The underlying error.
118 source: std::io::Error,
119 },
120 /// A cache entry's metadata did not parse.
121 #[error("cache entry {path} is corrupt: {reason}")]
122 CorruptEntry {
123 /// The metadata file.
124 path: std::path::PathBuf,
125 /// What was wrong.
126 reason: String,
127 },
128}