bee/lib.rs
1//! bee-rs — Rust client for the Swarm Bee API.
2//!
3//! Functional parity target with [bee-js] (the canonical TypeScript
4//! client) and [bee-go] (the typed Go port). bee-go is the primary
5//! reference for shape and behavior; bee-js is the source of truth for
6//! wire-format edge cases.
7//!
8//! # Quick start
9//!
10//! Connect to a local Bee node, buy a postage batch, upload a few
11//! bytes, and download them back:
12//!
13//! ```no_run
14//! use std::time::Duration;
15//!
16//! use bee::{Client, storage::StorageOptions};
17//! use bee::swarm::Size;
18//! use bytes::Bytes;
19//!
20//! # async fn run() -> Result<(), bee::Error> {
21//! let client = Client::new("http://localhost:1633")?;
22//!
23//! // Health check the node before we do anything else.
24//! let health = client.debug().health().await?;
25//! assert_eq!(health.status, "ok");
26//!
27//! // Buy 1 GB of storage for 30 days at the current chain price.
28//! let size = Size::from_gigabytes(1.0)?;
29//! let duration = Duration::from_secs(30 * 24 * 60 * 60);
30//! let batch_id = bee::storage::buy_storage(
31//! &client, size, duration, &StorageOptions::default(),
32//! ).await?;
33//!
34//! // Upload + download round-trip.
35//! let result = client
36//! .file()
37//! .upload_data(&batch_id, Bytes::from_static(b"Hello Swarm!"), None)
38//! .await?;
39//! let body = client.file().download_data(&result.reference, None).await?;
40//! assert_eq!(&body[..], b"Hello Swarm!");
41//! # Ok(()) }
42//! ```
43//!
44//! # Module map
45//!
46//! bee-rs is a sub-service client: the top-level [`Client`] yields one
47//! handle per Bee API domain. Sub-services are cheap to construct
48//! (`Arc<Inner>` + accessor) so it is fine to call them on every
49//! request.
50//!
51//! - [`api`] — generic `/api/*` endpoints (pin, tag, stewardship,
52//! grantee, envelope) plus the upload / download option structs
53//! and rich return types.
54//! - [`mod@file`] — bytes, files, chunks, SOC, feeds, and tar-packed
55//! collections; offline `hash_directory` / `hash_collection_entries`;
56//! chunk-by-chunk `stream_directory` with progress callback.
57//! - [`postage`] — postage batch CRUD plus pure stamp math
58//! (`get_stamp_cost`, `get_amount_for_duration`,
59//! `get_depth_for_size`, …) and an offline `Stamper`.
60//! - [`debug`] — operator endpoints: health, versions, peers,
61//! accounting, chequebook, stake, transactions.
62//! - [`pss`] — Postal Service: send + websocket subscribe / receive.
63//! - [`gsoc`] — Generic Single-Owner Chunk send / subscribe.
64//! - [`manifest`] — Mantaray trie, `ResourceLocator`, offline
65//! `resolve_path`.
66//! - [`swarm`] — typed bytes, BMT chunk addressing, SOC primitives,
67//! token math, [`BeeDuration`](swarm::BeeDuration),
68//! [`Size`](swarm::Size), and the crate-level [`Error`] enum.
69//! - [`storage`] — high-level `(size, duration)` helpers built on top
70//! of [`Client`] and [`postage`].
71//! - [`dev`] — [`DevClient`] wrapper for talking to `bee dev`.
72//!
73//! # Bee version compatibility
74//!
75//! This client targets Bee `2.7.2-rc1` / API `8.0.0` (the values pinned
76//! in [`debug::SUPPORTED_BEE_VERSION_EXACT`] and
77//! [`debug::SUPPORTED_API_VERSION`]). At startup, prefer
78//! [`debug::DebugApi::is_supported_api_version`] for a major-compatible
79//! check or [`debug::DebugApi::is_supported_exact_version`] for a
80//! strict pin. Older or newer Bee instances usually work — unknown
81//! response fields are ignored — but new endpoints will return 404 and
82//! breaking wire-format changes will surface as [`Error::Json`].
83//!
84//! # Authentication, timeouts, and proxies
85//!
86//! [`Client::new`] constructs a default [`reqwest::Client`] with no
87//! auth, no request timeout, and no custom TLS roots. For anything
88//! beyond a trusted local node, build a [`reqwest::Client`] with the
89//! settings you need and pass it via [`Client::with_http_client`]:
90//!
91//! ```no_run
92//! use std::time::Duration;
93//! use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
94//!
95//! # fn run() -> Result<(), bee::Error> {
96//! let mut headers = HeaderMap::new();
97//! headers.insert(AUTHORIZATION, HeaderValue::from_static("Bearer …"));
98//! let http = reqwest::Client::builder()
99//! .timeout(Duration::from_secs(30))
100//! .default_headers(headers)
101//! .build()
102//! .map_err(bee::Error::Transport)?;
103//! let client = bee::Client::with_http_client("https://bee.example.com", http)?;
104//! # let _ = client; Ok(()) }
105//! ```
106//!
107//! Bee gates `/stamps`, `/chequebook`, `/stake`, `/transactions`, and
108//! the operator endpoints behind tokens in production. Proxies are
109//! honored from the `HTTP_PROXY` / `HTTPS_PROXY` env vars unless you
110//! call `.no_proxy()` on the builder.
111//!
112//! No automatic retries are performed. Use a layered HTTP client (e.g.
113//! [`reqwest-middleware`] + [`reqwest-retry`]) if you need them.
114//!
115//! [`reqwest-middleware`]: https://docs.rs/reqwest-middleware
116//! [`reqwest-retry`]: https://docs.rs/reqwest-retry
117//!
118//! # Concurrency
119//!
120//! [`Client`] is `Send + Sync + Clone`. Cloning is `Arc`-cheap
121//! (`Arc<Inner>` under the hood), so pass a clone into spawned tasks
122//! rather than wrapping in `Arc<Mutex<…>>`. Sub-service handles
123//! ([`file::FileApi`], [`postage::PostageApi`], …) are likewise cheap
124//! and share the same underlying HTTP client + connection pool.
125//!
126//! # Cancellation
127//!
128//! Dropping the future returned by any endpoint cancels the in-flight
129//! HTTP request. For uploads, chunks already accepted by the local
130//! Bee node may remain in the local reserve but the upload is not
131//! committed (no manifest reference is returned).
132//! [`file::FileApi::stream_directory`] and
133//! [`file::FileApi::stream_collection_entries`] upload chunk-by-chunk
134//! and can leave orphan chunks if cancelled mid-stream.
135//!
136//! # Streaming vs. buffered transfers
137//!
138//! [`file::FileApi::download_data`] / [`file::FileApi::download_file`]
139//! buffer the full body into [`bytes::Bytes`] before returning — fine
140//! for ≤ a few hundred MB, but will OOM on multi-GB references. For
141//! larger downloads, use [`file::FileApi::download_data_response`] (or
142//! `download_file_response`) which returns the raw [`reqwest::Response`]
143//! so you can drive [`reqwest::Response::bytes_stream`] yourself.
144//!
145//! Uploads accept `impl Into<Bytes>` and stream the body to Bee. The
146//! chunk-by-chunk variants ([`file::FileApi::stream_directory`] and
147//! `stream_collection_entries`) bound peak memory at the BMT chunk
148//! size regardless of file size and report progress via a
149//! caller-supplied [`file::OnStreamProgressFn`].
150//!
151//! # Errors
152//!
153//! Every fallible call returns [`Result<T, Error>`](Result). [`Error`]
154//! is a single enum so callers can match on the variant of interest:
155//!
156//! ```no_run
157//! # async fn run(client: bee::Client) -> Result<(), bee::Error> {
158//! match client.debug().health().await {
159//! Ok(h) if h.status == "ok" => println!("ready: bee {}", h.version),
160//! Ok(h) => println!("not healthy: status={}", h.status),
161//! Err(bee::Error::Response { status, .. }) => println!("bee returned {status}"),
162//! Err(e) => return Err(e),
163//! }
164//! # Ok(()) }
165//! ```
166//!
167//! As a rule of thumb: 5xx responses and [`Error::Transport`] errors
168//! (DNS, connection refused, EOF) are retry candidates with backoff;
169//! 4xx [`Error::Response`] errors (invalid batch ID, depth out of
170//! range, immutable-flag mismatch) are caller bugs and re-issuing the
171//! same request will fail the same way. `POST /bytes` is idempotent
172//! for the same `(data, batch_id)` tuple — Bee returns the same
173//! content reference.
174//!
175//! # Observability
176//!
177//! bee-rs depends on [`tracing`] but does not currently emit spans or
178//! events. Wrap [`reqwest::Client`] with [`reqwest-tracing`] (or your
179//! own middleware) before passing it to [`Client::with_http_client`]
180//! to get request-level spans. Bee's own
181//! [`debug::DebugApi::set_logger_verbosity`] controls server-side
182//! verbosity at runtime.
183//!
184//! [`tracing`]: https://docs.rs/tracing
185//! [`reqwest-tracing`]: https://docs.rs/reqwest-tracing
186//!
187//! # Cargo features
188//!
189//! `default = []` — there are no opt-in features today. The crate
190//! always builds with `reqwest`'s `rustls-tls` backend; native-TLS
191//! (OS trust store) is not currently exposed. Open an issue if you
192//! need it for a corporate-CA deployment.
193//!
194//! # MSRV
195//!
196//! Minimum supported Rust version is **1.85** (Rust 2024 edition).
197//! See [`Cargo.toml`] `rust-version`.
198//!
199//! [`Cargo.toml`]: https://github.com/ethswarm-tools/bee-rs/blob/main/Cargo.toml
200//!
201//! # Testing
202//!
203//! Point [`Client::new`] at a [`wiremock`] mock server (a dev-dep of
204//! this crate, used in our own tests) to test code that calls bee-rs
205//! without a real Bee:
206//!
207//! [`wiremock`]: https://docs.rs/wiremock
208//!
209//! ```no_run
210//! # async fn run() -> Result<(), bee::Error> {
211//! # use wiremock::{MockServer, Mock, ResponseTemplate};
212//! # use wiremock::matchers::method;
213//! let server = MockServer::start().await;
214//! Mock::given(method("GET"))
215//! .respond_with(ResponseTemplate::new(200)
216//! .set_body_string(r#"{"status":"ok","version":"2.7.2","apiVersion":"8.0.0"}"#))
217//! .mount(&server)
218//! .await;
219//! let client = bee::Client::new(&server.uri())?;
220//! let h = client.debug().health().await?;
221//! assert_eq!(h.status, "ok");
222//! # Ok(()) }
223//! ```
224//!
225//! # Common pitfalls
226//!
227//! - A freshly-purchased postage batch is not usable for ~2-3 minutes
228//! on Sepolia (block time × N confirmations). Poll
229//! [`postage::PostageBatch::usable`] before uploading or you will
230//! get HTTP 422 "stamp not usable".
231//! - Dilute is one-way: depth can only grow, never shrink.
232//! - [`swarm::Reference`] values are 32 bytes (plain) or 64 bytes
233//! (encrypted). Passing an encrypted reference to a plain download
234//! silently returns garbage — match the reference type to how the
235//! data was uploaded.
236//! - Feed updates require the same `(topic, signer)` pair every time;
237//! a new signer creates a new feed, not an update.
238//! - On a `bee dev` node, all chain / chequebook / stake endpoints
239//! return 404 — see [`DevClient`].
240//!
241//! [bee-js]: https://github.com/ethersphere/bee-js
242//! [bee-go]: https://github.com/ethswarm-tools/bee-go
243
244#![forbid(unsafe_code)]
245#![warn(missing_docs)]
246
247pub mod api;
248pub(crate) mod client;
249pub mod debug;
250pub mod dev;
251pub mod file;
252pub mod gsoc;
253pub mod manifest;
254pub mod postage;
255pub mod pss;
256pub mod storage;
257pub mod swarm;
258
259pub use dev::DevClient;
260
261pub use client::Client;
262pub use swarm::{Error, Result};