stac-client 0.2.0

A friendly, async client for the SpatioTemporal Asset Catalog (STAC) specification, written in Rust.
Documentation
# 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)