Skip to main content

zammad_api/
lib.rs

1//! Async, idiomatic Rust client for the [Zammad](https://zammad.com) REST API.
2//!
3//! The entry point is [`Client`]; reach individual resources via the methods
4//! on it ([`Client::tickets`], [`Client::users`], ...). All operations are
5//! `async` and powered by [`reqwest`].
6//!
7//! # Quick start
8//!
9//! ```no_run
10//! use futures_util::TryStreamExt;
11//! use zammad_api::{Auth, Client, models::NewArticle};
12//!
13//! # async fn run() -> zammad_api::Result<()> {
14//! let zammad = Client::builder()
15//!     .url("https://help.example.com")
16//!     .auth(Auth::token("YOUR_TOKEN"))
17//!     .build()?;
18//!
19//! // Stream every open ticket.
20//! let mut stream = zammad.tickets().search("state.name:open").stream();
21//! while let Some(t) = stream.try_next().await? {
22//!     println!("#{} {}", t.id, t.title.as_deref().unwrap_or(""));
23//! }
24//!
25//! // Reply to a ticket.
26//! zammad
27//!     .articles()
28//!     .create(42)
29//!     .article(NewArticle::new("Thanks for the report.").article_type("note"))
30//!     .send()
31//!     .await?;
32//! # Ok(()) }
33//! ```
34//!
35//! # Authentication
36//!
37//! Zammad accepts three credential styles on every endpoint:
38//!
39//! ```
40//! use zammad_api::Auth;
41//!
42//! Auth::token("YOUR_PERSONAL_ACCESS_TOKEN"); // Authorization: Token token=...
43//! Auth::bearer("YOUR_OAUTH_TOKEN");          // Authorization: Bearer ...
44//! Auth::basic("agent@example.com", "pw");    // Authorization: Basic ...
45//! ```
46//!
47//! [`Auth`] values redact themselves in `Debug` output.
48//!
49//! # Custom fields
50//!
51//! Every typed model exposes an `extra: serde_json::Map<String, Value>` that
52//! captures any attribute Zammad returns that isn't part of the typed schema -
53//! perfect for Object Manager custom fields.
54//!
55//! ```no_run
56//! # use zammad_api::Client;
57//! # async fn run(z: &Client) -> zammad_api::Result<()> {
58//! let ticket = z.tickets().get(123).await?;
59//! let link = ticket.extra.get("espocrm_link").and_then(|v| v.as_str());
60//!
61//! // Set + verify that the value was actually persisted.
62//! z.tickets().set_field(123, "espocrm_link", "https://crm/Contact/abc").await?;
63//! # Ok(()) }
64//! ```
65//!
66//! # Pagination
67//!
68//! List and search builders return a [`futures_core::stream::BoxStream`] from
69//! `.stream()` - no `Box::pin` boilerplate needed at the call site. Each
70//! builder also exposes `.page(n)` for one-shot fetches.
71//!
72//! # Cargo features
73//!
74//! - `rustls-tls` *(default)* - TLS via rustls + webpki roots.
75//! - `native-tls` - TLS via the system's native TLS stack.
76//!
77//! # Expanded vs. compact tickets
78//!
79//! Passing `expand=true` to Zammad swaps several `*_id: u64` fields for their
80//! human-readable string equivalents (`group`, `state`, `priority`, ...).
81//! This crate models that as two distinct types - [`models::Ticket`] and
82//! [`models::TicketExpanded`] - selected at call time via
83//! [`Client::tickets`]`.list()` vs `.list_expanded()`.
84
85#![deny(missing_docs)]
86#![deny(unsafe_code)]
87#![warn(missing_debug_implementations)]
88#![warn(rust_2018_idioms)]
89#![cfg_attr(docsrs, feature(doc_cfg))]
90
91mod auth;
92mod client;
93mod error;
94
95pub mod api;
96pub mod models;
97
98pub use auth::Auth;
99pub use client::{Client, ClientBuilder};
100pub use error::{Error, Result};
101
102/// Re-export of [`bytes::Bytes`] (returned by attachment downloads).
103pub use bytes::Bytes;
104/// Re-export of [`futures_core::Stream`] for the streams returned by list/search builders.
105pub use futures_core::Stream;
106/// Re-export of [`serde_json::Value`] used for custom-field payloads.
107pub use serde_json::Value;