1use crate::tier::Tier;
16
17#[non_exhaustive]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum Method {
24 GetContent,
27 GetCapsule,
29 GetModule,
31 GetManifest,
33 GetMetadata,
35 ListCapsules,
37 GetProof,
39 GetProofStatus,
41 GetAnchoredRoot,
44 GetCollection,
47 ListCollectionItems,
50 Health,
52 Methods,
54
55 GetNetworkInfo,
58 GetPeers,
60 Announce,
62 GetAvailability,
64 ListInventory,
66 FetchRange,
68
69 Stage,
72 CacheGetConfig,
74 CacheSetCapBytes,
76 CacheClear,
78 CacheListCached,
80 CacheRemoveCached,
82 CacheFetchAndCache,
84 CacheStats,
87 ControlPeerStatus,
89 ControlSubscribe,
92 ControlUnsubscribe,
94 ControlListSubscriptions,
96 ControlPeersConnect,
99 ControlPeersDisconnect,
102 RpcDiscover,
104}
105
106impl Method {
107 pub const fn name(self) -> &'static str {
109 match self {
110 Method::GetContent => "dig.getContent",
111 Method::GetCapsule => "dig.getCapsule",
112 Method::GetModule => "dig.getModule",
113 Method::GetManifest => "dig.getManifest",
114 Method::GetMetadata => "dig.getMetadata",
115 Method::ListCapsules => "dig.listCapsules",
116 Method::GetProof => "dig.getProof",
117 Method::GetProofStatus => "dig.getProofStatus",
118 Method::GetAnchoredRoot => "dig.getAnchoredRoot",
119 Method::GetCollection => "dig.getCollection",
120 Method::ListCollectionItems => "dig.listCollectionItems",
121 Method::Health => "dig.health",
122 Method::Methods => "dig.methods",
123 Method::GetNetworkInfo => "dig.getNetworkInfo",
124 Method::GetPeers => "dig.getPeers",
125 Method::Announce => "dig.announce",
126 Method::GetAvailability => "dig.getAvailability",
127 Method::ListInventory => "dig.listInventory",
128 Method::FetchRange => "dig.fetchRange",
129 Method::Stage => "dig.stage",
130 Method::CacheGetConfig => "cache.getConfig",
131 Method::CacheSetCapBytes => "cache.setCapBytes",
132 Method::CacheClear => "cache.clear",
133 Method::CacheListCached => "cache.listCached",
134 Method::CacheRemoveCached => "cache.removeCached",
135 Method::CacheFetchAndCache => "cache.fetchAndCache",
136 Method::CacheStats => "cache.stats",
137 Method::ControlPeerStatus => "control.peerStatus",
138 Method::ControlSubscribe => "control.subscribe",
139 Method::ControlUnsubscribe => "control.unsubscribe",
140 Method::ControlListSubscriptions => "control.listSubscriptions",
141 Method::ControlPeersConnect => "control.peers.connect",
142 Method::ControlPeersDisconnect => "control.peers.disconnect",
143 Method::RpcDiscover => "rpc.discover",
144 }
145 }
146
147 pub fn from_name(name: &str) -> Option<Method> {
150 Method::ALL.iter().copied().find(|m| m.name() == name)
151 }
152
153 pub const fn tier(self) -> Tier {
159 match self {
160 Method::GetContent
161 | Method::GetCapsule
162 | Method::GetModule
163 | Method::GetManifest
164 | Method::GetMetadata
165 | Method::ListCapsules
166 | Method::GetProof
167 | Method::GetProofStatus
168 | Method::GetAnchoredRoot
169 | Method::GetCollection
170 | Method::ListCollectionItems
171 | Method::Health
172 | Method::Methods => Tier::PublicRead,
173
174 Method::GetNetworkInfo
175 | Method::GetPeers
176 | Method::Announce
177 | Method::GetAvailability
178 | Method::ListInventory
179 | Method::FetchRange => Tier::Peer,
180
181 Method::Stage
182 | Method::CacheGetConfig
183 | Method::CacheSetCapBytes
184 | Method::CacheClear
185 | Method::CacheListCached
186 | Method::CacheRemoveCached
187 | Method::CacheFetchAndCache
188 | Method::CacheStats
189 | Method::ControlPeerStatus
190 | Method::ControlSubscribe
191 | Method::ControlUnsubscribe
192 | Method::ControlListSubscriptions
193 | Method::ControlPeersConnect
194 | Method::ControlPeersDisconnect
195 | Method::RpcDiscover => Tier::Control,
196 }
197 }
198
199 pub const fn is_peer_reachable(self) -> bool {
208 matches!(
209 self,
210 Method::GetContent
211 | Method::GetNetworkInfo
212 | Method::GetPeers
213 | Method::Announce
214 | Method::GetAvailability
215 | Method::ListInventory
216 | Method::FetchRange
217 | Method::GetAnchoredRoot
218 | Method::GetCollection
219 | Method::ListCollectionItems
220 )
221 }
222
223 pub const fn summary(self) -> &'static str {
225 match self {
226 Method::GetContent => "Read a verified window of a resource's ciphertext.",
227 Method::GetCapsule => "Fetch the whole .dig module for (store, root).",
228 Method::GetModule => "Alias of dig.getCapsule.",
229 Method::GetManifest => "Fetch the public discovery manifest resource.",
230 Method::GetMetadata => "Fetch the plaintext metadata manifest.",
231 Method::ListCapsules => "List a store's confirmed capsules.",
232 Method::GetProof => "Get the real inclusion proof + execution-proof status.",
233 Method::GetProofStatus => "Poll a real execution-proof job by id.",
234 Method::GetAnchoredRoot => "Resolve a store's chain-anchored tip root.",
235 Method::GetCollection => "Get collection-level facts for NFT launcher ids.",
236 Method::ListCollectionItems => "List resolved NFT collection items (paginated).",
237 Method::Health => "Liveness and capability summary.",
238 Method::Methods => "List the method names this node implements.",
239 Method::GetNetworkInfo => "This node's peer-network posture.",
240 Method::GetPeers => "The peers this node currently knows.",
241 Method::Announce => "Accept a peer announcement (peer_id + addresses).",
242 Method::GetAvailability => "Batch-check whether this node holds items.",
243 Method::ListInventory => "Enumerate the stores / roots this node serves.",
244 Method::FetchRange => "Fetch a single verified range frame of a resource.",
245 Method::Stage => "Compile a local folder into a capsule .dig in-process.",
246 Method::CacheGetConfig => "Get the local-cache configuration.",
247 Method::CacheSetCapBytes => "Set the local-cache size cap.",
248 Method::CacheClear => "Clear the local cache.",
249 Method::CacheListCached => "List the durable cached modules.",
250 Method::CacheRemoveCached => "Remove one cached capsule.",
251 Method::CacheFetchAndCache => "Fetch and cache one capsule.",
252 Method::CacheStats => {
253 "Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss counters."
254 }
255 Method::ControlPeerStatus => "Snapshot the node's peer network.",
256 Method::ControlSubscribe => {
257 "Subscribe the node to a store (persisted watch + gap-fill)."
258 }
259 Method::ControlUnsubscribe => "Unsubscribe the node from a store.",
260 Method::ControlListSubscriptions => "List the node's persisted store subscriptions.",
261 Method::ControlPeersConnect => "Dial a peer into the connected pool.",
262 Method::ControlPeersDisconnect => "Drop a pooled peer by peer_id.",
263 Method::RpcDiscover => "Return the OpenRPC self-describe document.",
264 }
265 }
266
267 pub const ALL: &'static [Method] = &[
269 Method::GetContent,
270 Method::GetCapsule,
271 Method::GetModule,
272 Method::GetManifest,
273 Method::GetMetadata,
274 Method::ListCapsules,
275 Method::GetProof,
276 Method::GetProofStatus,
277 Method::GetAnchoredRoot,
278 Method::GetCollection,
279 Method::ListCollectionItems,
280 Method::Health,
281 Method::Methods,
282 Method::GetNetworkInfo,
283 Method::GetPeers,
284 Method::Announce,
285 Method::GetAvailability,
286 Method::ListInventory,
287 Method::FetchRange,
288 Method::Stage,
289 Method::CacheGetConfig,
290 Method::CacheSetCapBytes,
291 Method::CacheClear,
292 Method::CacheListCached,
293 Method::CacheRemoveCached,
294 Method::CacheFetchAndCache,
295 Method::CacheStats,
296 Method::ControlPeerStatus,
297 Method::ControlSubscribe,
298 Method::ControlUnsubscribe,
299 Method::ControlListSubscriptions,
300 Method::ControlPeersConnect,
301 Method::ControlPeersDisconnect,
302 Method::RpcDiscover,
303 ];
304
305 pub fn peer_reachable_names() -> Vec<&'static str> {
308 Method::ALL
309 .iter()
310 .copied()
311 .filter(|m| m.is_peer_reachable())
312 .map(Method::name)
313 .collect()
314 }
315}
316
317impl std::fmt::Display for Method {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 f.write_str(self.name())
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use std::collections::HashSet;
327
328 #[test]
331 fn names_round_trip() {
332 for m in Method::ALL {
333 assert_eq!(Method::from_name(m.name()), Some(*m), "{}", m.name());
334 }
335 assert!(Method::from_name("dig.nope").is_none());
336 }
337
338 #[test]
341 fn names_unique() {
342 let names: HashSet<&str> = Method::ALL.iter().map(|m| m.name()).collect();
343 assert_eq!(names.len(), Method::ALL.len());
344 }
345
346 #[test]
352 fn peer_allowlist_matches_canonical() {
353 let mut got = Method::peer_reachable_names();
354 got.sort_unstable();
355 let mut want = vec![
356 "dig.getContent",
357 "dig.getNetworkInfo",
358 "dig.getPeers",
359 "dig.announce",
360 "dig.getAvailability",
361 "dig.listInventory",
362 "dig.fetchRange",
363 "dig.getAnchoredRoot",
364 "dig.getCollection",
365 "dig.listCollectionItems",
366 ];
367 want.sort_unstable();
368 assert_eq!(got, want);
369
370 for m in [
372 Method::Stage,
373 Method::CacheGetConfig,
374 Method::CacheSetCapBytes,
375 Method::CacheClear,
376 Method::CacheListCached,
377 Method::CacheRemoveCached,
378 Method::CacheFetchAndCache,
379 Method::CacheStats,
380 Method::ControlPeerStatus,
381 Method::ControlSubscribe,
382 Method::ControlUnsubscribe,
383 Method::ControlListSubscriptions,
384 Method::ControlPeersConnect,
385 Method::ControlPeersDisconnect,
386 Method::RpcDiscover,
387 ] {
388 assert!(
389 !m.is_peer_reachable(),
390 "{} must NOT be peer-reachable",
391 m.name()
392 );
393 }
394 }
395
396 #[test]
399 fn anchored_reads_public_but_peer_reachable() {
400 for m in [
401 Method::GetAnchoredRoot,
402 Method::GetCollection,
403 Method::ListCollectionItems,
404 ] {
405 assert_eq!(m.tier(), Tier::PublicRead);
406 assert!(m.is_peer_reachable());
407 }
408 }
409
410 #[test]
416 fn completed_control_methods_present_and_gated() {
417 let expected = [
418 (Method::CacheStats, "cache.stats"),
419 (Method::ControlSubscribe, "control.subscribe"),
420 (Method::ControlUnsubscribe, "control.unsubscribe"),
421 (
422 Method::ControlListSubscriptions,
423 "control.listSubscriptions",
424 ),
425 (Method::ControlPeersConnect, "control.peers.connect"),
426 (Method::ControlPeersDisconnect, "control.peers.disconnect"),
427 ];
428 for (m, name) in expected {
429 assert_eq!(m.name(), name);
430 assert_eq!(Method::from_name(name), Some(m));
431 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
432 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
433 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
434 }
435 }
436
437 #[test]
440 fn control_tier_never_peer_reachable() {
441 for m in Method::ALL {
442 if m.tier() == Tier::Control {
443 assert!(
444 !m.is_peer_reachable(),
445 "{} is Control but peer-reachable",
446 m.name()
447 );
448 }
449 }
450 }
451}