zammad-api 0.4.0

Async, idiomatic Rust client for the Zammad Service Platform
Documentation
# zammad-api

Async, idiomatic Rust client for the [Zammad](https://zammad.com) REST API, written by [SiSol PRO](https://www.sisol-pro.fr), a French company that has its expertise in managing "communautés d'autoconsommation".

- Builder-style `Client` with token, bearer or basic auth
- Strongly typed models for tickets, articles, users, organizations, groups, tags, states and priorities
- Streaming pagination via `futures::Stream`
- Custom-field passthrough via `#[serde(flatten)] extra`
- Write-verification helper for setting custom fields (Zammad silently returns 200 when a field doesn't exist on the object - `set_field` turns that into a loud error)
- TLS opt-in: `rustls-tls` (default) or `native-tls`

## Quick start

```toml
[dependencies]
zammad-api = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures-util = "0.3"
```

```rust,no_run
use futures_util::TryStreamExt;
use zammad_api::{Auth, Client, models::NewArticle};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let zammad = Client::builder()
        .url("https://help.example.com")
        .auth(Auth::token(std::env::var("ZAMMAD_TOKEN")?))
        .build()?;

    // Stream every open ticket
    let mut stream = zammad.tickets().search("state.name:open").stream();
    while let Some(ticket) = stream.try_next().await? {
        println!("#{} {}", ticket.id, ticket.title.as_deref().unwrap_or(""));
    }

    // Reply to a ticket
    zammad
        .articles()
        .create(42)
        .article(NewArticle::new("Thanks for the report.").article_type("note"))
        .send()
        .await?;

    // Tag a ticket
    zammad.tags().add(42, "important").await?;

    Ok(())
}
```

## Authentication

```rust,no_run
use zammad_api::Auth;

Auth::token("YOUR_TOKEN");                // -> Authorization: Token token=...
Auth::bearer("YOUR_OAUTH_TOKEN");         // -> Authorization: Bearer ...
Auth::basic("agent@example.com", "pw");   // -> Authorization: Basic ...
```

## Custom fields

Every model exposes an `extra: serde_json::Map<String, Value>` field that captures any attribute Zammad returns that isn't part of the typed schema - perfect for the custom fields you've added in Zammad's Object Manager.

```rust,no_run
# use zammad_api::Client;
# async fn run(z: &Client) -> zammad_api::Result<()> {
let ticket = z.tickets().get(123).await?;
let link = ticket.extra.get("espocrm_link").and_then(|v| v.as_str());

// Set + verify the value was persisted (Zammad silently swallows unknown fields)
z.tickets().set_field(123, "espocrm_link", "https://crm/Contact/abc").await?;
# Ok(()) }
```

## License

MIT