# STAC Client for Rust
A friendly, async client for the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) specification, written in Rust. This library helps you interact with STAC APIs to find and retrieve geospatial data.
It is inspired by Python's [pystac-client](https://github.com/stac-utils/pystac-client) but designed with Rust's principles of type safety, performance, and idiomatic API design in mind.
> For architectural decisions and design rationale, see the `docs/` directory.
## Project Goals
- **Idiomatic Rust API**: Provide an API that feels natural to Rust developers, with explicit error handling and proper use of ownership.
- **Minimal Dependencies**: Keep the dependency tree small to ensure fast builds and a lean footprint.
- **Well-Documented**: Offer clear and comprehensive documentation for all public APIs.
- **Thoroughly Tested**: Maintain a high standard of testing to guarantee stability and correctness.
## Features
- **Async First**: Built on `tokio` for non-blocking I/O.
- **Strongly-Typed Models**: Rust structs for all major STAC objects (`Catalog`, `Collection`, `Item`, etc.).
- **Powerful Search**: A fluent builder pattern for constructing complex search queries.
- **Robust Error Handling**: Distinct error types for network, parsing, and API-specific issues.
- **Customizable Client**: Bring your own `reqwest::Client` to configure timeouts, proxies, or custom headers.
## Installation
Add `stac-client` to your `Cargo.toml`:
```toml
[dependencies]
stac-client = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
```
## Quick Start
This example shows how to connect to a STAC API, retrieve the root catalog, and run a simple search.
```rust,no_run
use stac_client::{Client, SearchBuilder};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// The base URL of the STAC API
let api_url = "https://planetarycomputer.microsoft.com/api/stac/v1";
// Create a new client
let client = Client::new(api_url)?;
println!("Connected to: {}", client.base_url());
// Get the root catalog to verify the connection
let catalog = client.get_catalog().await?;
println!("Root catalog ID: {}", catalog.id);
// Build a search query for Sentinel-2 data over a specific location and time
let search = SearchBuilder::new()
.collections(vec!["sentinel-2-l2a".to_string()])
.bbox(vec![-74.0, 40.7, -73.9, 40.8]) // West, South, East, North
.datetime("2023-01-01T00:00:00Z/2023-01-31T23:59:59Z")
.limit(10)
.build();
// Execute the search
let results = client.search(&search).await?;
println!("Found {} items in the search.", results.features.len());
// Process the resulting items
for item in results.features {
println!(
" - Item '{}' from collection '{}'",
item.id,
item.collection.as_deref().unwrap_or("N/A")
);
for (asset_key, asset) in item.assets {
println!(" - Asset '{}': {}", asset_key, asset.href);
}
}
Ok(())
}
```
## API Reference
### Basic Operations
The `Client` provides methods for fetching core STAC objects:
```rust,no_run
# use stac_client::Client;
# use std::error::Error;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn Error>> {
# let client = Client::new("https://example.com")?;
// Get the root catalog
let catalog = client.get_catalog().await?;
// Get a list of all collections
let collections = client.get_collections().await?;
// Get a single collection by its ID
let collection = client.get_collection("my-collection-id").await?;
// Get items from a collection
let items = client.get_collection_items("my-collection-id", Some(50)).await?;
// Get a single item by its collection and item ID
let item = client.get_item("my-collection-id", "my-item-id").await?;
// Get the API's conformance classes
let conformance = client.get_conformance().await?;
# Ok(())
# }
```
### Advanced Search
The `SearchBuilder` allows you to construct detailed queries. The client supports both `POST /search` (default) and `GET /search`.
```rust,no_run
# use stac_client::{Client, SearchBuilder, SortDirection};
# use serde_json::json;
# use std::error::Error;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn Error>> {
# let client = Client::new("https://example.com")?;
let search = SearchBuilder::new()
.limit(100)
.bbox(vec![-180.0, -90.0, 180.0, 90.0])
.datetime("2024-01-01T00:00:00Z/..") // Open-ended interval
.collections(vec!["collection-a".to_string(), "collection-b".to_string()])
.ids(vec!["item-1".to_string(), "item-2".to_string()])
.intersects(json!({
"type": "Point",
"coordinates": [-122.4, 37.8]
}))
.query("eo:cloud_cover", json!({ "lt": 10 })) // STAC Query Extension
.sort_by("datetime", SortDirection::Desc)
.include_fields(vec!["id".to_string(), "properties.datetime".to_string()])
.build();
// Execute using POST /search
let results_post = client.search(&search).await?;
// Execute using GET /search
let results_get = client.search_get(&search).await?;
# Ok(())
# }
```
## Error Handling
The library returns a `stac_client::Error` for failed operations, allowing you to handle different failure modes.
```rust,no_run
# use stac_client::{Client, Error};
# #[tokio::main]
# async fn main() {
# let client = Client::new("https://example.com").unwrap();
match client.get_catalog().await {
Ok(catalog) => println!("Success: {}", catalog.id),
Err(Error::Http(e)) => println!("HTTP request failed: {}", e),
Err(Error::Json(e)) => println!("Failed to parse JSON response: {}", e),
Err(Error::Api { status, message }) => {
println!("API returned an error (status {}): {}", status, message)
},
Err(e) => println!("An unexpected error occurred: {}", e),
}
# }
```
## Testing
This project maintains a high level of test coverage. To run the test suite:
```bash
cargo test -- --nocapture
```
## Contributing
Contributions are welcome! We have a strict set of development standards to ensure the quality and stability of the crate. Please review:
- **`CONTRIBUTING.md`** - Contribution guidelines and release process
- **`AGENTS.md`** - Detailed policies and best practices
All pull requests must pass formatting, linting, and test coverage checks.
## License
This project is licensed under either of the following, at your option:
- Apache License, Version 2.0 (`LICENSE-APACHE` or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (`LICENSE-MIT` or http://opensource.org/licenses/MIT)