ecmwf_opendata/lib.rs
1#![forbid(unsafe_code)]
2
3//! Rust client for ECMWF Open Data.
4//!
5//! This crate is a Rust re-implementation of the core ideas from the upstream
6//! `ecmwf-opendata` Python package: you express a MARS-like request (keyword/value
7//! pairs), URLs are derived from that request, and downloads can either fetch whole
8//! files or use the `.index` sidecar to download only matching fields via HTTP
9//! range requests.
10//!
11//! **Quick start**
12//! ```no_run
13//! use ecmwf_opendata::{Client, ClientOptions, Request};
14//!
15//! let opts = ClientOptions {
16//! // Python: Client(source="ecmwf", model="ifs", resol="0p25", ...)
17//! source: "ecmwf".to_string(),
18//! model: "ifs".to_string(),
19//! resol: "0p25".to_string(),
20//! preserve_request_order: false,
21//! infer_stream_keyword: true,
22//! ..ClientOptions::default()
23//! };
24//! let client = Client::new(opts)?;
25//!
26//! // Builder style
27//! let req = Request::new().r#type("fc").param("msl").step(240).target("data.grib2");
28//! let result = client.retrieve_request(req)?;
29//! println!("{} bytes", result.size_bytes);
30//! # Ok::<(), ecmwf_opendata::Error>(())
31//! ```
32//!
33//! **Pairs (kwargs-like) style**
34//! ```no_run
35//! use ecmwf_opendata::{Client, ClientOptions, RequestValue};
36//!
37//! let client = Client::new(ClientOptions::default())?;
38//! let result = client.retrieve_pairs([
39//! ("type", RequestValue::from("fc")),
40//! ("param", RequestValue::from("msl")),
41//! ("step", 240.into()),
42//! ("target", "data.grib2".into()),
43//! ])?;
44//! println!("{}", result.datetime);
45//! # Ok::<(), ecmwf_opendata::Error>(())
46//! ```
47//!
48//! **Macro (closest to Python `client.retrieve(time=..., ...)`)**
49//! ```no_run
50//! use ecmwf_opendata::{retrieve, Client, ClientOptions};
51//!
52//! let client = Client::new(ClientOptions::default())?;
53//! let steps: Vec<i32> = (12..=360).step_by(12).collect();
54//!
55//! let result = retrieve!(
56//! client,
57//! time = 0,
58//! stream = "enfo",
59//! type = "ep",
60//! step = steps,
61//! param = ["tpg1", "tpg5", "10fgg10"],
62//! target = "data.grib2",
63//! )?;
64//! println!("{}", result.datetime);
65//! # Ok::<(), ecmwf_opendata::Error>(())
66//! ```
67//!
68//! **GUI/config style (string key/value pairs)**
69//! ```no_run
70//! use ecmwf_opendata::{Client, ClientOptions, Request};
71//!
72//! let client = Client::new(ClientOptions::default())?;
73//! let req = Request::from_str_pairs([
74//! ("time", "0"),
75//! ("stream", "enfo"),
76//! ("type", "ep"),
77//! ("step", "12,24,36"),
78//! ("param", "tpg1,tpg5,10fgg10"),
79//! ("target", "data.grib2"),
80//! ]);
81//! let _ = client.retrieve_request(req)?;
82//! # Ok::<(), ecmwf_opendata::Error>(())
83//! ```
84//!
85//! Notes:
86//! - Downloads are governed by ECMWF Open Data terms (e.g. attribution requirements).
87//! - Network conditions vary by mirror/source; if `latest()` cannot be established,
88//! specify `date`/`time` explicitly in your request.
89//! - In line with the upstream Python client, omitting `step` means “retrieve all available steps”.
90
91mod client;
92mod date;
93mod error;
94mod request;
95mod sources;
96mod url_builder;
97
98pub use crate::client::{Client, ClientOptions, Result};
99pub use crate::error::{Error, Result as EResult};
100pub use crate::request::{Request, RequestValue};
101
102/// Build a [`Request`] using a kwargs-like syntax.
103///
104/// Example:
105/// ```no_run
106/// use ecmwf_opendata::{request, Client, ClientOptions};
107///
108/// let client = Client::new(ClientOptions::default())?;
109/// let steps: Vec<i32> = (12..=360).step_by(12).collect();
110///
111/// let req = request!(
112/// time = 0,
113/// stream = "enfo",
114/// type = "ep",
115/// step = steps,
116/// levelist = 850,
117/// param = [
118/// "ptsa_gt_1stdev",
119/// "ptsa_gt_1p5stdev",
120/// "ptsa_gt_2stdev",
121/// ],
122/// target = "data.grib2",
123/// );
124///
125/// let _ = client.retrieve_request(req)?;
126/// # Ok::<(), ecmwf_opendata::Error>(())
127/// ```
128#[macro_export]
129macro_rules! request {
130 ($($key:tt = $value:expr),+ $(,)?) => {{
131 let mut r = $crate::Request::new();
132 $(
133 let k0: &str = stringify!($key);
134 let k: &str = k0.strip_prefix("r#").unwrap_or(k0);
135 r = r.kw(k, $crate::RequestValue::from($value));
136 )+
137 r
138 }};
139}
140
141/// Python-like `client.retrieve(...)` convenience using kwargs-like syntax.
142///
143/// Example:
144/// ```no_run
145/// use ecmwf_opendata::{retrieve, Client, ClientOptions};
146///
147/// let client = Client::new(ClientOptions::default())?;
148/// let steps: Vec<i32> = (12..=360).step_by(12).collect();
149///
150/// let result = retrieve!(
151/// client,
152/// time = 0,
153/// stream = "enfo",
154/// type = "ep",
155/// step = steps,
156/// param = ["tpg1", "tpg5", "10fgg10"],
157/// target = "data.grib2",
158/// )?;
159/// println!("{}", result.datetime);
160/// # Ok::<(), ecmwf_opendata::Error>(())
161/// ```
162#[macro_export]
163macro_rules! retrieve {
164 ($client:expr, $($key:tt = $value:expr),+ $(,)?) => {{
165 let req = $crate::request!($($key = $value),+);
166 $client.retrieve_request(req)
167 }};
168}