zammad-api 0.4.0

Async, idiomatic Rust client for the Zammad Service Platform
Documentation
//! Async, idiomatic Rust client for the [Zammad](https://zammad.com) REST API.
//!
//! The entry point is [`Client`]; reach individual resources via the methods
//! on it ([`Client::tickets`], [`Client::users`], ...). All operations are
//! `async` and powered by [`reqwest`].
//!
//! # Quick start
//!
//! ```no_run
//! use futures_util::TryStreamExt;
//! use zammad_api::{Auth, Client, models::NewArticle};
//!
//! # async fn run() -> zammad_api::Result<()> {
//! let zammad = Client::builder()
//!     .url("https://help.example.com")
//!     .auth(Auth::token("YOUR_TOKEN"))
//!     .build()?;
//!
//! // Stream every open ticket.
//! let mut stream = zammad.tickets().search("state.name:open").stream();
//! while let Some(t) = stream.try_next().await? {
//!     println!("#{} {}", t.id, t.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?;
//! # Ok(()) }
//! ```
//!
//! # Authentication
//!
//! Zammad accepts three credential styles on every endpoint:
//!
//! ```
//! use zammad_api::Auth;
//!
//! Auth::token("YOUR_PERSONAL_ACCESS_TOKEN"); // Authorization: Token token=...
//! Auth::bearer("YOUR_OAUTH_TOKEN");          // Authorization: Bearer ...
//! Auth::basic("agent@example.com", "pw");    // Authorization: Basic ...
//! ```
//!
//! [`Auth`] values redact themselves in `Debug` output.
//!
//! # Custom fields
//!
//! Every typed model exposes an `extra: serde_json::Map<String, Value>` that
//! captures any attribute Zammad returns that isn't part of the typed schema -
//! perfect for Object Manager custom fields.
//!
//! ```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 that the value was actually persisted.
//! z.tickets().set_field(123, "espocrm_link", "https://crm/Contact/abc").await?;
//! # Ok(()) }
//! ```
//!
//! # Pagination
//!
//! List and search builders return a [`futures_core::stream::BoxStream`] from
//! `.stream()` - no `Box::pin` boilerplate needed at the call site. Each
//! builder also exposes `.page(n)` for one-shot fetches.
//!
//! # Cargo features
//!
//! - `rustls-tls` *(default)* - TLS via rustls + webpki roots.
//! - `native-tls` - TLS via the system's native TLS stack.
//!
//! # Expanded vs. compact tickets
//!
//! Passing `expand=true` to Zammad swaps several `*_id: u64` fields for their
//! human-readable string equivalents (`group`, `state`, `priority`, ...).
//! This crate models that as two distinct types - [`models::Ticket`] and
//! [`models::TicketExpanded`] - selected at call time via
//! [`Client::tickets`]`.list()` vs `.list_expanded()`.

#![deny(missing_docs)]
#![deny(unsafe_code)]
#![warn(missing_debug_implementations)]
#![warn(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]

mod auth;
mod client;
mod error;

pub mod api;
pub mod models;

pub use auth::Auth;
pub use client::{Client, ClientBuilder};
pub use error::{Error, Result};

/// Re-export of [`bytes::Bytes`] (returned by attachment downloads).
pub use bytes::Bytes;
/// Re-export of [`futures_core::Stream`] for the streams returned by list/search builders.
pub use futures_core::Stream;
/// Re-export of [`serde_json::Value`] used for custom-field payloads.
pub use serde_json::Value;