use crate::tier::Tier;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Method {
GetContent,
GetCapsule,
GetModule,
GetManifest,
GetMetadata,
ListCapsules,
GetProof,
GetProofStatus,
GetAnchoredRoot,
GetCollection,
ListCollectionItems,
Health,
Methods,
GetNetworkInfo,
GetPeers,
Announce,
GetAvailability,
ListInventory,
FetchRange,
Stage,
CacheGetConfig,
CacheSetCapBytes,
CacheClear,
CacheListCached,
CacheRemoveCached,
CacheFetchAndCache,
ControlPeerStatus,
RpcDiscover,
}
impl Method {
pub const fn name(self) -> &'static str {
match self {
Method::GetContent => "dig.getContent",
Method::GetCapsule => "dig.getCapsule",
Method::GetModule => "dig.getModule",
Method::GetManifest => "dig.getManifest",
Method::GetMetadata => "dig.getMetadata",
Method::ListCapsules => "dig.listCapsules",
Method::GetProof => "dig.getProof",
Method::GetProofStatus => "dig.getProofStatus",
Method::GetAnchoredRoot => "dig.getAnchoredRoot",
Method::GetCollection => "dig.getCollection",
Method::ListCollectionItems => "dig.listCollectionItems",
Method::Health => "dig.health",
Method::Methods => "dig.methods",
Method::GetNetworkInfo => "dig.getNetworkInfo",
Method::GetPeers => "dig.getPeers",
Method::Announce => "dig.announce",
Method::GetAvailability => "dig.getAvailability",
Method::ListInventory => "dig.listInventory",
Method::FetchRange => "dig.fetchRange",
Method::Stage => "dig.stage",
Method::CacheGetConfig => "cache.getConfig",
Method::CacheSetCapBytes => "cache.setCapBytes",
Method::CacheClear => "cache.clear",
Method::CacheListCached => "cache.listCached",
Method::CacheRemoveCached => "cache.removeCached",
Method::CacheFetchAndCache => "cache.fetchAndCache",
Method::ControlPeerStatus => "control.peerStatus",
Method::RpcDiscover => "rpc.discover",
}
}
pub fn from_name(name: &str) -> Option<Method> {
Method::ALL.iter().copied().find(|m| m.name() == name)
}
pub const fn tier(self) -> Tier {
match self {
Method::GetContent
| Method::GetCapsule
| Method::GetModule
| Method::GetManifest
| Method::GetMetadata
| Method::ListCapsules
| Method::GetProof
| Method::GetProofStatus
| Method::GetAnchoredRoot
| Method::GetCollection
| Method::ListCollectionItems
| Method::Health
| Method::Methods => Tier::PublicRead,
Method::GetNetworkInfo
| Method::GetPeers
| Method::Announce
| Method::GetAvailability
| Method::ListInventory
| Method::FetchRange => Tier::Peer,
Method::Stage
| Method::CacheGetConfig
| Method::CacheSetCapBytes
| Method::CacheClear
| Method::CacheListCached
| Method::CacheRemoveCached
| Method::CacheFetchAndCache
| Method::ControlPeerStatus
| Method::RpcDiscover => Tier::Control,
}
}
pub const fn is_peer_reachable(self) -> bool {
matches!(
self,
Method::GetContent
| Method::GetNetworkInfo
| Method::GetPeers
| Method::Announce
| Method::GetAvailability
| Method::ListInventory
| Method::FetchRange
| Method::GetAnchoredRoot
| Method::GetCollection
| Method::ListCollectionItems
)
}
pub const fn summary(self) -> &'static str {
match self {
Method::GetContent => "Read a verified window of a resource's ciphertext.",
Method::GetCapsule => "Fetch the whole .dig module for (store, root).",
Method::GetModule => "Alias of dig.getCapsule.",
Method::GetManifest => "Fetch the public discovery manifest resource.",
Method::GetMetadata => "Fetch the plaintext metadata manifest.",
Method::ListCapsules => "List a store's confirmed capsules.",
Method::GetProof => "Get the real inclusion proof + execution-proof status.",
Method::GetProofStatus => "Poll a real execution-proof job by id.",
Method::GetAnchoredRoot => "Resolve a store's chain-anchored tip root.",
Method::GetCollection => "Get collection-level facts for NFT launcher ids.",
Method::ListCollectionItems => "List resolved NFT collection items (paginated).",
Method::Health => "Liveness and capability summary.",
Method::Methods => "List the method names this node implements.",
Method::GetNetworkInfo => "This node's peer-network posture.",
Method::GetPeers => "The peers this node currently knows.",
Method::Announce => "Accept a peer announcement (peer_id + addresses).",
Method::GetAvailability => "Batch-check whether this node holds items.",
Method::ListInventory => "Enumerate the stores / roots this node serves.",
Method::FetchRange => "Fetch a single verified range frame of a resource.",
Method::Stage => "Compile a local folder into a capsule .dig in-process.",
Method::CacheGetConfig => "Get the local-cache configuration.",
Method::CacheSetCapBytes => "Set the local-cache size cap.",
Method::CacheClear => "Clear the local cache.",
Method::CacheListCached => "List the durable cached modules.",
Method::CacheRemoveCached => "Remove one cached capsule.",
Method::CacheFetchAndCache => "Fetch and cache one capsule.",
Method::ControlPeerStatus => "Snapshot the node's peer network.",
Method::RpcDiscover => "Return the OpenRPC self-describe document.",
}
}
pub const ALL: &'static [Method] = &[
Method::GetContent,
Method::GetCapsule,
Method::GetModule,
Method::GetManifest,
Method::GetMetadata,
Method::ListCapsules,
Method::GetProof,
Method::GetProofStatus,
Method::GetAnchoredRoot,
Method::GetCollection,
Method::ListCollectionItems,
Method::Health,
Method::Methods,
Method::GetNetworkInfo,
Method::GetPeers,
Method::Announce,
Method::GetAvailability,
Method::ListInventory,
Method::FetchRange,
Method::Stage,
Method::CacheGetConfig,
Method::CacheSetCapBytes,
Method::CacheClear,
Method::CacheListCached,
Method::CacheRemoveCached,
Method::CacheFetchAndCache,
Method::ControlPeerStatus,
Method::RpcDiscover,
];
pub fn peer_reachable_names() -> Vec<&'static str> {
Method::ALL
.iter()
.copied()
.filter(|m| m.is_peer_reachable())
.map(Method::name)
.collect()
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn names_round_trip() {
for m in Method::ALL {
assert_eq!(Method::from_name(m.name()), Some(*m), "{}", m.name());
}
assert!(Method::from_name("dig.nope").is_none());
}
#[test]
fn names_unique() {
let names: HashSet<&str> = Method::ALL.iter().map(|m| m.name()).collect();
assert_eq!(names.len(), Method::ALL.len());
}
#[test]
fn peer_allowlist_matches_canonical() {
let mut got = Method::peer_reachable_names();
got.sort_unstable();
let mut want = vec![
"dig.getContent",
"dig.getNetworkInfo",
"dig.getPeers",
"dig.announce",
"dig.getAvailability",
"dig.listInventory",
"dig.fetchRange",
"dig.getAnchoredRoot",
"dig.getCollection",
"dig.listCollectionItems",
];
want.sort_unstable();
assert_eq!(got, want);
for m in [
Method::Stage,
Method::CacheGetConfig,
Method::CacheSetCapBytes,
Method::CacheClear,
Method::CacheListCached,
Method::CacheRemoveCached,
Method::CacheFetchAndCache,
Method::ControlPeerStatus,
Method::RpcDiscover,
] {
assert!(
!m.is_peer_reachable(),
"{} must NOT be peer-reachable",
m.name()
);
}
}
#[test]
fn anchored_reads_public_but_peer_reachable() {
for m in [
Method::GetAnchoredRoot,
Method::GetCollection,
Method::ListCollectionItems,
] {
assert_eq!(m.tier(), Tier::PublicRead);
assert!(m.is_peer_reachable());
}
}
#[test]
fn control_tier_never_peer_reachable() {
for m in Method::ALL {
if m.tier() == Tier::Control {
assert!(
!m.is_peer_reachable(),
"{} is Control but peer-reachable",
m.name()
);
}
}
}
}