Skip to main content

bes_canopy_api/
lib.rs

1//! Rust client for canopy's public API.
2//!
3//! The wire types and the per-endpoint methods in [`schema`] are generated from
4//! canopy's OpenAPI document, which lives in the same repository as this crate,
5//! so they are the types canopy declares rather than a separate description of
6//! them. The document and this crate carry the same version.
7//!
8//! # Calling canopy
9//!
10//! [`CanopyClient`] has one method per operation, taking and returning the
11//! generated types. The method name comes from the path (`/backup-credentials`
12//! becomes `backup_credentials`), with the verb prefixed where a path is served
13//! by more than one verb.
14//!
15//! How a request reaches canopy is the consumer's to decide: this crate depends
16//! on no HTTP client, and a consumer supplies a [`CanopyTransport`] which
17//! resolves the host, the scheme, and the authentication itself. Any status
18//! outside the success range surfaces as [`CanopyHttpError`], since endpoints
19//! give particular statuses a meaning only the caller can read.
20//!
21//! # What the generated types carry
22//!
23//! Timestamp fields are [`jiff::Timestamp`] rather than text, and credential
24//! secrets are wrapped in [`Redacted`] so they stay out of `Debug` output and
25//! logs; read them through the inner value. Schemas that carry arbitrary further
26//! keys alongside their declared fields generate a map field holding the rest, so
27//! those keys can be both sent and read.
28//!
29//! Generated structs are `#[non_exhaustive]` and carry a builder, so a schema
30//! gaining a field leaves construction working for a consumer that does not set
31//! it.
32
33use std::fmt;
34
35use serde::{Deserialize, Deserializer, Serialize, Serializer};
36
37mod client;
38mod error;
39mod transport;
40
41/// Wire types and per-endpoint methods generated from canopy's OpenAPI document.
42///
43/// Regenerate with `just gen-api` after changing the public API; the generated
44/// source is committed, so a change to this surface appears in the change that
45/// causes it.
46pub mod schema {
47	include!("generated.rs");
48}
49
50pub use async_trait::async_trait;
51pub use client::CanopyClient;
52pub use error::{CanopyHttpError, Error, Result};
53pub use transport::{CanopyRequest, CanopyResponse, CanopyTransport};
54pub use {bytes, http};
55
56/// Wraps a sensitive value so its `Debug` output doesn't leak the contents.
57///
58/// Serialises and deserialises as the inner value, so it is transparent on the
59/// wire; only `Debug` is withheld. Read the value through [`Deref`](std::ops::Deref)
60/// or the public field.
61#[derive(Clone)]
62pub struct Redacted<T>(pub T);
63
64impl<T> fmt::Debug for Redacted<T> {
65	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66		f.write_str("<redacted>")
67	}
68}
69
70impl<T> std::ops::Deref for Redacted<T> {
71	type Target = T;
72	fn deref(&self) -> &T {
73		&self.0
74	}
75}
76
77impl<T> From<T> for Redacted<T> {
78	fn from(value: T) -> Self {
79		Self(value)
80	}
81}
82
83impl<T: Serialize> Serialize for Redacted<T> {
84	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
85		self.0.serialize(serializer)
86	}
87}
88
89impl<'de, T: Deserialize<'de>> Deserialize<'de> for Redacted<T> {
90	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
91		T::deserialize(deserializer).map(Redacted)
92	}
93}