Skip to main content

dig_rpc_types/
tier.rs

1//! RPC access tiers and the peer-surface allowlist.
2//!
3//! Every DIG-node RPC method belongs to exactly one [`Tier`], its PRIMARY tier.
4//! Three tiers, matching the dual-transport model:
5//!
6//! - [`Tier::PublicRead`] — anonymous, self-verified content + discovery reads
7//!   over plain HTTPS (browser) or mTLS. A browser resolves these without a
8//!   client cert.
9//! - [`Tier::Peer`] — the mTLS peer surface between DIG nodes (discovery,
10//!   availability, range serving). `peer_id = SHA-256(TLS SPKI DER)`.
11//! - [`Tier::Control`] — loopback / in-process only (cache + node lifecycle +
12//!   staging). NEVER reachable over the peer surface.
13//!
14//! # The peer allowlist is separate from the tier
15//!
16//! Being `PublicRead` does NOT make a method peer-reachable — the peer surface
17//! is an **allowlist**, not a denylist. Three chain-anchored public reads
18//! (`getAnchoredRoot`, `getCollection`, `listCollectionItems`) are PRIMARY
19//! `PublicRead` yet ALSO answered on the peer surface. The allowlist mirrors the
20//! canonical node's `is_peer_reachable_method` exactly; see
21//! [`Method::is_peer_reachable`](crate::method::Method::is_peer_reachable).
22
23use serde::{Deserialize, Serialize};
24
25/// A method's primary access tier.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
29pub enum Tier {
30    /// Anonymous, self-verified content + discovery reads (plain HTTPS / mTLS).
31    PublicRead,
32    /// The mTLS peer surface between DIG nodes.
33    Peer,
34    /// Loopback / in-process only (cache, lifecycle, staging).
35    Control,
36}
37
38impl Tier {
39    /// A stable, lower-case identifier for the tier (for OpenRPC tags / logs).
40    pub const fn as_str(self) -> &'static str {
41        match self {
42            Tier::PublicRead => "public-read",
43            Tier::Peer => "peer",
44            Tier::Control => "control",
45        }
46    }
47}
48
49impl std::fmt::Display for Tier {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.write_str(self.as_str())
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    /// **Proves:** each tier renders its stable lower-case identifier and
60    /// serializes camelCase, matching the OpenRPC `x-dig-tier` extension.
61    #[test]
62    fn tier_strings_and_serde() {
63        assert_eq!(Tier::PublicRead.as_str(), "public-read");
64        assert_eq!(Tier::Peer.to_string(), "peer");
65        assert_eq!(Tier::Control.as_str(), "control");
66        assert_eq!(
67            serde_json::to_string(&Tier::PublicRead).unwrap(),
68            "\"publicRead\""
69        );
70        assert_eq!(
71            serde_json::from_str::<Tier>("\"peer\"").unwrap(),
72            Tier::Peer
73        );
74    }
75}