honk_me/lib.rs
1//! The official Rust client for [Honk](https://honk-me.app): send events from your apps, jobs,
2//! scripts and CI to your phone, as calm, grouped push notifications.
3//!
4//! - Async-first on Tokio and `reqwest` with rustls (no OpenSSL); a synchronous
5//! `blocking::Honk` with the feature `blocking`.
6//! - Every send carries an `Idempotency-Key` (yours, or a UUIDv7) that is reused on every
7//! retry, so a retry never creates a duplicate.
8//! - Retries only network errors, timeouts, 429 and 5xx, with exponential backoff and full
9//! jitter, never sooner than `Retry-After`, all under a total deadline (30 s). Redirects are
10//! reported, never followed.
11//! - Local validation of everything cheap (lengths, URLs, actions, metadata, the 16 KiB body),
12//! with every invalid field reported at once. [`HonkBuilder::validate`]`(false)` leaves it to
13//! the server.
14//!
15//! Keep the ingestion key (`honk_…`) on the server: never ship it inside a client app.
16//!
17//! # Quick start
18//!
19//! ```no_run
20//! use honk_me::{Honk, Message, Severity};
21//!
22//! #[tokio::main]
23//! async fn main() -> honk_me::Result<()> {
24//! let honk = Honk::from_env()?; // HONK_URL, HONK_KEY
25//!
26//! // The Honk scale: light, beep, loud, long, blast.
27//! honk.beep("Backup finished", "nightly pg_dump took 42 s").await?;
28//!
29//! // Every field, and your own idempotency key.
30//! let msg = Message::new("Ana asked for a quote: 3 rooms, 2 bathrooms")
31//! .title("New quote request")
32//! .severity(Severity::LOUD)
33//! .group_key("requests/4812")
34//! .url("https://shop.example.com/admin/requests/4812")
35//! .metadata("rooms", 3);
36//! let accepted = honk.send(&msg, "request-4812").await?;
37//! println!("stored as {}", accepted.id);
38//!
39//! // Incidents: a problem opens one for its group, a recovery closes it.
40//! honk.problem("db/backup", "Backup failed", "pg_dump exited with 1").await?;
41//! honk.recovery("db/backup", "Backup OK", "pg_dump finished").await?;
42//! Ok(())
43//! }
44//! ```
45//!
46//! # Errors
47//!
48//! Every failure is an [`Error`]: `Validation` (fix the message), `Auth`, `Quota`, `Conflict`,
49//! `Network`, `Timeout`, `Server` or `Http`, each with a [`Failure`] holding the details.
50//! [`Error::is_retryable`] tells whether sending again later (with the same idempotency key)
51//! may succeed.
52//!
53//! # Features
54//!
55//! - `blocking`: `blocking::Honk`, a synchronous client on a private Tokio runtime.
56
57#![forbid(unsafe_code)]
58#![warn(missing_docs, rust_2018_idioms, unreachable_pub)]
59#![cfg_attr(docsrs, feature(doc_cfg))]
60
61#[cfg(feature = "blocking")]
62#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
63pub mod blocking;
64mod client;
65mod error;
66mod message;
67mod time;
68mod uuid;
69mod validate;
70
71pub use client::{Honk, HonkBuilder, PendingSend, VERSION};
72pub use error::{Error, Failure, FieldError, Result};
73pub use message::{
74 Accepted, Action, Category, Defaults, EventType, Message, MetadataValue, ParseEnumError,
75 Priority, Severity,
76};
77pub use uuid::new_idempotency_key;
78pub use validate::{
79 MAX_ACTION_TITLE, MAX_ACTIONS, MAX_BODY_BYTES, MAX_CHANNEL, MAX_ENVIRONMENT, MAX_GROUP_KEY,
80 MAX_MESSAGE_BYTES, MAX_METADATA_KEYS, MAX_METADATA_STRING, MAX_SOURCE, MAX_TITLE,
81 MAX_TTL_SECONDS, MAX_URL_BYTES, MIN_TTL_SECONDS, encode_message,
82};