bestool_canopy/lib.rs
1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5mod backup;
6mod client;
7pub mod registration;
8mod restore;
9
10/// Wire types generated at build time from canopy's OpenAPI document.
11///
12/// These are the canonical request and response types for canopy's API, and the
13/// ones to reach for first. The build script fetches the live spec and
14/// regenerates them, so they track canopy as it evolves and nothing here is
15/// hand-maintained or committed. Each type carries the schema's own description
16/// as rustdoc. (A failed fetch fails the build rather than silently using the
17/// committed snapshot, which is reserved for docs.rs and explicit offline
18/// builds — see the build script.)
19///
20/// Naming follows canopy's schema: request bodies are `…Args` (e.g.
21/// [`CredentialsArgs`], [`ReportArgs`], [`CapabilitiesArgs`]), and credentials
22/// come back as [`CredentialProcessOutput`].
23///
24/// The generated source is rewritten in two ways the raw JSON Schema can't
25/// express (see the build script): timestamp fields are [`jiff::Timestamp`]
26/// rather than strings, and credential secrets (`secret_access_key`,
27/// `session_token`, `repo_password`) are wrapped in [`Redacted`] so they never
28/// surface in `Debug` output or logs — read them through the inner value.
29///
30/// The typed endpoint methods on [`CanopyClient`] (e.g.
31/// [`CanopyClient::backup_credentials`], [`CanopyClient::backup_target`]) take
32/// and return these types. For an endpoint without a bespoke method, use the
33/// generic [`CanopyClient::request`]/[`CanopyClient::request_json`] with the
34/// matching schema type.
35///
36/// [`CredentialsArgs`]: schema::CredentialsArgs
37/// [`ReportArgs`]: schema::ReportArgs
38/// [`CapabilitiesArgs`]: schema::CapabilitiesArgs
39/// [`CredentialProcessOutput`]: schema::CredentialProcessOutput
40pub mod schema {
41 include!(concat!(env!("OUT_DIR"), "/canopy_schema.rs"));
42}
43
44pub use backup::{ContainerCreds, TargetOutcome};
45pub use client::{
46 CERT_RENEW_AFTER, CanopyClient, ClientBuilderFactory, DEFAULT_CANOPY_URL, TAILSCALE_URL,
47 client_builder, device_identity, tailscale_client, user_agent,
48};
49pub use reqwest;
50pub use restore::{RestoreCapabilitiesRequest, RestoreCredentialsRequest};
51
52/// Wraps a sensitive value so its `Debug` output doesn't leak the contents.
53#[derive(Clone)]
54pub struct Redacted<T>(pub T);
55
56impl<T> fmt::Debug for Redacted<T> {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str("<redacted>")
59 }
60}
61
62impl<T> std::ops::Deref for Redacted<T> {
63 type Target = T;
64 fn deref(&self) -> &T {
65 &self.0
66 }
67}
68
69impl<T: Serialize> Serialize for Redacted<T> {
70 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
71 self.0.serialize(serializer)
72 }
73}
74
75impl<'de, T: Deserialize<'de>> Deserialize<'de> for Redacted<T> {
76 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
77 T::deserialize(deserializer).map(Redacted)
78 }
79}