dig_rpc_protocol/types.rs
1//! Request/response wire types for every DIG-node RPC method.
2//!
3//! Each type is `serde`-derived and models a method's params or result
4//! field-for-field with the canonical implementation (the digstore `dig-node`
5//! crate). Fields that appear only in one profile or only on the first window of
6//! a paged stream are `Option` and doc-flagged.
7//!
8//! Hex-encoded identifiers (`store_id`, `root`, `retrieval_key`, `peer_id`) are
9//! carried as `String` on the wire — lower-case 64-hex — because the interface
10//! crate does no crypto and imposes no byte-array dependency. Callers validate
11//! length/charset at their boundary.
12//!
13//! # Two content profiles, one chunk type
14//!
15//! [`ContentChunk`] models both the node profile (`dig.getContent` on the local
16//! dig-node) and the network profile (`rpc.dig.net`). The network-profile-only
17//! fields — [`total_length`](ContentChunk::total_length),
18//! [`length`](ContentChunk::length), [`program_hash`](ContentChunk::program_hash),
19//! [`offset`](ContentChunk::offset) — are `Option` so one type serves both
20//! surfaces with no silent split.
21
22use serde::{Deserialize, Serialize};
23
24/// A lower-case 64-hex identifier on the wire (e.g. a `store_id`, `root`,
25/// `retrieval_key`, or `peer_id`). A type alias for documentation; validation is
26/// the boundary's job.
27pub type HexId = String;
28
29// ===========================================================================
30// Shared value objects
31// ===========================================================================
32
33/// A peer's dialable network endpoint.
34///
35/// IPv6-first per the ecosystem networking rule: an address list orders
36/// global-unicast IPv6 ahead of IPv4 fallback, and a wildcard bind
37/// (`[::]`/`0.0.0.0`) is never advertised.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
40pub struct PeerAddress {
41 /// The host — an IPv6 or IPv4 literal (never a wildcard).
42 pub host: String,
43 /// The TCP port.
44 pub port: u16,
45 /// How the address was discovered: `direct`, `reflexive`, `mapped`, or
46 /// `relay`.
47 pub kind: String,
48}
49
50/// A content provider: a holder's stable `peer_id` plus its candidate addresses.
51///
52/// The address list is byte-compatible with [`dig.getPeers`](crate::method::Method::GetPeers)
53/// and the DHT provider shape.
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
56pub struct Provider {
57 /// The holder's stable `peer_id` = `SHA-256(TLS SPKI DER)`, 64-hex.
58 pub peer_id: HexId,
59 /// The holder's candidate addresses (IPv6-first).
60 pub addresses: Vec<PeerAddress>,
61}
62
63/// The content item a redirect points at: `store_id` [+ `root` [+
64/// `retrieval_key`]], each lower-case 64-hex — the exact item to re-request.
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
66#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
67pub struct ContentRef {
68 /// The store launcher id (always present).
69 pub store_id: HexId,
70 /// The generation root (present for capsule/resource granularity).
71 #[serde(skip_serializing_if = "Option::is_none", default)]
72 pub root: Option<HexId>,
73 /// The resource retrieval key (present for resource granularity).
74 #[serde(skip_serializing_if = "Option::is_none", default)]
75 pub retrieval_key: Option<HexId>,
76}
77
78/// The `error.data.redirect` payload of a
79/// [`ContentRedirect`](crate::error::ErrorCode::ContentRedirect) (`-32008`).
80///
81/// The node does not hold the content but located peers that do; the caller
82/// re-requests against one of `providers`, echoing `redirect_depth` in its
83/// params so the hop budget stays bounded (stop at `max_redirects`).
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
86pub struct RedirectInfo {
87 /// The content the caller should re-request.
88 pub content: ContentRef,
89 /// The holders (peer_id + candidate addresses) to re-request against.
90 pub providers: Vec<Provider>,
91 /// The hop count the caller must echo on its re-request.
92 pub redirect_depth: u64,
93 /// The redirect budget — stop redirecting when `redirect_depth` reaches this.
94 pub max_redirects: u64,
95}
96
97// ===========================================================================
98// dig.getContent (PUBLIC-READ, also peer-reachable)
99// ===========================================================================
100
101/// Params for [`dig.getContent`](crate::method::Method::GetContent) — a verified
102/// resource-window read.
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
105pub struct GetContentParams {
106 /// The CHIP-0035 singleton launcher id (64-hex).
107 pub store_id: HexId,
108 /// `SHA-256(urn)` — the only URN-derived value sent to a node (64-hex).
109 pub retrieval_key: HexId,
110 /// The generation root (64-hex). Empty / `"latest"` / absent ⇒ resolve the
111 /// chain tip.
112 #[serde(skip_serializing_if = "Option::is_none", default)]
113 pub root: Option<HexId>,
114 /// The window start offset (default 0).
115 #[serde(skip_serializing_if = "Option::is_none", default)]
116 pub offset: Option<u64>,
117 /// Retrieval mode: `"speed"` (default) or `"privacy"` (onion — target).
118 #[serde(skip_serializing_if = "Option::is_none", default)]
119 pub mode: Option<String>,
120 /// The redirect budget already consumed (echoed from a `-32008` redirect).
121 #[serde(skip_serializing_if = "Option::is_none", default)]
122 pub redirect_depth: Option<u64>,
123}
124
125/// One window of a resource's ciphertext — the chunk wire object.
126///
127/// Serves BOTH the node profile (`dig.getContent` on the local dig-node) and the
128/// network profile (`rpc.dig.net`). Node-profile responses omit the
129/// network-profile-only fields; the doc on each field says which profile
130/// populates it.
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
133pub struct ContentChunk {
134 /// This window's bytes, base64. Both profiles.
135 pub ciphertext: String,
136 /// The resolved generation root (64-hex). Both profiles.
137 pub root: HexId,
138 /// Whether this window ends the resource. Both profiles.
139 pub complete: bool,
140 /// The next offset; present iff not complete. Both profiles.
141 #[serde(skip_serializing_if = "Option::is_none", default)]
142 pub next_offset: Option<u64>,
143 /// Whole-resource merkle proof, base64. First window only (`offset == 0`).
144 /// Both profiles.
145 #[serde(skip_serializing_if = "Option::is_none", default)]
146 pub inclusion_proof: Option<String>,
147 /// Per-chunk ciphertext lengths of the full resource. First window only;
148 /// empty ⇒ single chunk. Both profiles.
149 #[serde(skip_serializing_if = "Option::is_none", default)]
150 pub chunk_lens: Option<Vec<u64>>,
151 /// Where the window was served from: `"local"` (this device's cache) or
152 /// `"remote"` (freshly fetched). **Node profile only** — additive tag the
153 /// in-process node sets; absent on the network profile.
154 #[serde(skip_serializing_if = "Option::is_none", default)]
155 pub source: Option<String>,
156 /// The full resource ciphertext length (pre-windowing). **Network profile
157 /// only.**
158 #[serde(skip_serializing_if = "Option::is_none", default)]
159 pub total_length: Option<u64>,
160 /// This window's byte length. **Network profile only** (the node profile's
161 /// length is implicit in `ciphertext`).
162 #[serde(skip_serializing_if = "Option::is_none", default)]
163 pub length: Option<u64>,
164 /// The window start offset (echoed). **Network profile only.**
165 #[serde(skip_serializing_if = "Option::is_none", default)]
166 pub offset: Option<u64>,
167 /// `SHA-256(.dig bytes)` — the on-chain program identity (64-hex).
168 /// **Network profile only.**
169 #[serde(skip_serializing_if = "Option::is_none", default)]
170 pub program_hash: Option<HexId>,
171}
172
173// ===========================================================================
174// dig.getAnchoredRoot (PUBLIC-READ, also peer-reachable)
175// ===========================================================================
176
177/// Params for [`dig.getAnchoredRoot`](crate::method::Method::GetAnchoredRoot).
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
180pub struct GetAnchoredRootParams {
181 /// The store launcher id (64-hex).
182 pub store_id: HexId,
183}
184
185/// Result for [`dig.getAnchoredRoot`](crate::method::Method::GetAnchoredRoot) —
186/// the store's current chain-anchored tip root.
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
189pub struct AnchoredRoot {
190 /// The store launcher id (echoed, 64-hex).
191 pub store_id: HexId,
192 /// The chain-anchored tip root (64-hex).
193 pub root: HexId,
194}
195
196// ===========================================================================
197// dig.getCollection / dig.listCollectionItems (PUBLIC-READ, also peer)
198// ===========================================================================
199
200/// Params for [`dig.getCollection`](crate::method::Method::GetCollection).
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
203pub struct GetCollectionParams {
204 /// The NFT launcher ids to resolve. Capped at 10,000 (over-cap ⇒ `-32602`).
205 pub launcher_ids: Vec<HexId>,
206 /// The optional collection creator DID (64-hex).
207 #[serde(skip_serializing_if = "Option::is_none", default)]
208 pub did: Option<HexId>,
209}
210
211/// Result for [`dig.getCollection`](crate::method::Method::GetCollection) —
212/// collection-level facts computed from DIG's own coinset data.
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
214#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
215pub struct Collection {
216 /// The resolved creator DID (64-hex), if any.
217 #[serde(skip_serializing_if = "Option::is_none", default)]
218 pub did: Option<HexId>,
219 /// The DID declared by the caller / metadata (64-hex), if any.
220 #[serde(skip_serializing_if = "Option::is_none", default)]
221 pub declared_did: Option<HexId>,
222 /// The number of launcher ids requested.
223 pub item_count: u64,
224 /// How many resolved to live NFTs.
225 pub resolved_count: u64,
226 /// The uniform royalty in basis points, if resolvable.
227 #[serde(skip_serializing_if = "Option::is_none", default)]
228 pub royalty_basis_points: Option<u64>,
229}
230
231/// Params for
232/// [`dig.listCollectionItems`](crate::method::Method::ListCollectionItems).
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
235pub struct ListCollectionItemsParams {
236 /// The NFT launcher ids. Capped at 10,000 (over-cap ⇒ `-32602`).
237 pub launcher_ids: Vec<HexId>,
238 /// Page start (default 0).
239 #[serde(skip_serializing_if = "Option::is_none", default)]
240 pub offset: Option<u64>,
241 /// Page size (default 50, capped at 200).
242 #[serde(skip_serializing_if = "Option::is_none", default)]
243 pub limit: Option<u64>,
244}
245
246/// CHIP-0007 NFT metadata for one collection item.
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
248#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
249pub struct NftMetadata {
250 /// Edition ordinal, if any.
251 #[serde(skip_serializing_if = "Option::is_none", default)]
252 pub edition_number: Option<u64>,
253 /// Edition total, if any.
254 #[serde(skip_serializing_if = "Option::is_none", default)]
255 pub edition_total: Option<u64>,
256 /// Data URIs.
257 #[serde(default)]
258 pub data_uris: Vec<String>,
259 /// `SHA-256` of the data (64-hex), if any.
260 #[serde(skip_serializing_if = "Option::is_none", default)]
261 pub data_hash: Option<HexId>,
262 /// Metadata URIs.
263 #[serde(default)]
264 pub metadata_uris: Vec<String>,
265 /// `SHA-256` of the metadata document (64-hex), if any.
266 #[serde(skip_serializing_if = "Option::is_none", default)]
267 pub metadata_hash: Option<HexId>,
268 /// License URIs.
269 #[serde(default)]
270 pub license_uris: Vec<String>,
271 /// `SHA-256` of the license (64-hex), if any.
272 #[serde(skip_serializing_if = "Option::is_none", default)]
273 pub license_hash: Option<HexId>,
274}
275
276/// One resolved collection item — its current on-chain owner, royalty, and
277/// CHIP-0007 metadata.
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
280pub struct CollectionItem {
281 /// The NFT launcher id (64-hex).
282 pub launcher_id: HexId,
283 /// The current coin id (64-hex).
284 pub coin_id: HexId,
285 /// The current owner DID (64-hex), if any.
286 #[serde(skip_serializing_if = "Option::is_none", default)]
287 pub owner_did: Option<HexId>,
288 /// The royalty puzzle hash (64-hex).
289 pub royalty_puzzle_hash: HexId,
290 /// The royalty in basis points.
291 pub royalty_basis_points: u64,
292 /// The current owner puzzle hash (64-hex).
293 pub owner_puzzle_hash: HexId,
294 /// The CHIP-0007 metadata, if resolvable.
295 #[serde(skip_serializing_if = "Option::is_none", default)]
296 pub metadata: Option<NftMetadata>,
297}
298
299/// Result for
300/// [`dig.listCollectionItems`](crate::method::Method::ListCollectionItems) — a
301/// page of resolved items.
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
304pub struct CollectionItemsPage {
305 /// This page's items.
306 pub items: Vec<CollectionItem>,
307 /// The page start (echoed).
308 pub offset: u64,
309 /// The page size (echoed).
310 pub limit: u64,
311 /// The total item count across the whole (capped) launcher set.
312 pub total: u64,
313 /// The next page's offset, or `null` when exhausted.
314 #[serde(skip_serializing_if = "Option::is_none", default)]
315 pub next_offset: Option<u64>,
316}
317
318// ===========================================================================
319// dig.getNetworkInfo (PEER)
320// ===========================================================================
321
322/// The node's relay reservation posture.
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
324#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
325pub struct RelayStatus {
326 /// The relay endpoint URL (e.g. `wss://relay.dig.net:9450`).
327 pub url: String,
328 /// Whether a relay reservation is currently held.
329 pub reserved: bool,
330}
331
332/// Result for [`dig.getNetworkInfo`](crate::method::Method::GetNetworkInfo) —
333/// this node's own peer-network posture.
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
335#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
336pub struct NetworkInfo {
337 /// This node's stable `peer_id` = `SHA-256(TLS SPKI DER)` (64-hex), or
338 /// `null` when no identity is configured.
339 #[serde(skip_serializing_if = "Option::is_none", default)]
340 pub peer_id: Option<HexId>,
341 /// The DIG network id (e.g. `DIG_MAINNET`).
342 pub network_id: String,
343 /// The first advertised (dialable) candidate address, `host:port`.
344 pub listen_addr: String,
345 /// The STUN-discovered reflexive address, if known.
346 #[serde(skip_serializing_if = "Option::is_none", default)]
347 pub reflexive_addr: Option<String>,
348 /// All advertised candidate addresses (IPv6-first).
349 pub candidate_addresses: Vec<String>,
350 /// Reachability posture: `"direct"` or `"relayed"`.
351 pub reachability: String,
352 /// The relay reservation posture.
353 pub relay: RelayStatus,
354}
355
356// ===========================================================================
357// dig.getPeers (PEER)
358// ===========================================================================
359
360/// Result for [`dig.getPeers`](crate::method::Method::GetPeers) — the peers this
361/// node currently knows (peer exchange over RPC).
362#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
363#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
364pub struct PeersList {
365 /// The known peers (peer_id + candidate addresses).
366 pub peers: Vec<Provider>,
367}
368
369// ===========================================================================
370// dig.announce (PEER)
371// ===========================================================================
372
373/// Params for [`dig.announce`](crate::method::Method::Announce).
374#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
376pub struct AnnounceParams {
377 /// The announcing peer's `peer_id` (64-hex).
378 pub peer_id: HexId,
379 /// The announcing peer's candidate addresses.
380 pub addresses: Vec<PeerAddress>,
381}
382
383/// Result for [`dig.announce`](crate::method::Method::Announce).
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
385#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
386pub struct AnnounceAck {
387 /// Whether the announcement was accepted.
388 pub accepted: bool,
389 /// How many peers this node now knows.
390 pub known_peers: u64,
391}
392
393// ===========================================================================
394// dig.getAvailability (PEER)
395// ===========================================================================
396
397/// One availability query item. Granularity is inferred from which fields are
398/// present: `store_id` only ⇒ which roots are held; `+root` ⇒ a capsule; `+root
399/// +retrieval_key` ⇒ a resource.
400#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
401#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
402pub struct AvailabilityQuery {
403 /// The store launcher id (64-hex, required).
404 pub store_id: HexId,
405 /// The generation root (64-hex), for capsule/resource granularity.
406 #[serde(skip_serializing_if = "Option::is_none", default)]
407 pub root: Option<HexId>,
408 /// The resource retrieval key (64-hex), for resource granularity.
409 #[serde(skip_serializing_if = "Option::is_none", default)]
410 pub retrieval_key: Option<HexId>,
411}
412
413/// Params for [`dig.getAvailability`](crate::method::Method::GetAvailability).
414#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
415#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
416pub struct GetAvailabilityParams {
417 /// The items to check. Capped at 512 per batch (past-cap items are dropped).
418 pub items: Vec<AvailabilityQuery>,
419}
420
421/// One availability answer. Only the fields relevant to the query's granularity
422/// are populated.
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
424#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
425pub struct AvailabilityAnswer {
426 /// Whether this node holds the queried item.
427 pub available: bool,
428 /// The roots held (store-granularity queries only).
429 #[serde(skip_serializing_if = "Option::is_none", default)]
430 pub roots: Option<Vec<HexId>>,
431 /// The full resource ciphertext length (resource-granularity only).
432 #[serde(skip_serializing_if = "Option::is_none", default)]
433 pub total_length: Option<u64>,
434 /// The chunk count (resource-granularity only).
435 #[serde(skip_serializing_if = "Option::is_none", default)]
436 pub chunk_count: Option<u64>,
437 /// Whether the whole item is held (root/resource-granularity only).
438 #[serde(skip_serializing_if = "Option::is_none", default)]
439 pub complete: Option<bool>,
440 /// Providers that hold the item — present on a miss when holders were
441 /// located (enriched answer).
442 #[serde(skip_serializing_if = "Option::is_none", default)]
443 pub providers: Option<Vec<Provider>>,
444}
445
446/// Result for [`dig.getAvailability`](crate::method::Method::GetAvailability) —
447/// one answer per query item, in order.
448#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
449#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
450pub struct AvailabilityBatch {
451 /// The per-item answers (index-aligned to the query items served).
452 pub items: Vec<AvailabilityAnswer>,
453}
454
455// ===========================================================================
456// dig.listInventory (PEER)
457// ===========================================================================
458
459/// Params for [`dig.listInventory`](crate::method::Method::ListInventory).
460#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
461#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
462pub struct ListInventoryParams {
463 /// The store to list roots for (64-hex). Absent ⇒ list all stores served.
464 #[serde(skip_serializing_if = "Option::is_none", default)]
465 pub store_id: Option<HexId>,
466 /// The maximum number of entries to return.
467 #[serde(skip_serializing_if = "Option::is_none", default)]
468 pub limit: Option<u64>,
469}
470
471/// Result for [`dig.listInventory`](crate::method::Method::ListInventory).
472///
473/// With a `store_id` the node returns the roots it holds for that store; without
474/// one it returns the stores it serves. `#[serde(untagged)]` keeps the wire flat
475/// (`{"roots": …}` or `{"stores": …}`).
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(untagged)]
478#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
479pub enum Inventory {
480 /// The roots held for a specific store.
481 ForStore {
482 /// The store launcher id (echoed, 64-hex).
483 store_id: HexId,
484 /// The roots this node holds for the store.
485 roots: Vec<HexId>,
486 },
487 /// The stores this node serves (no `store_id` given).
488 AllStores {
489 /// The store launcher ids served.
490 stores: Vec<HexId>,
491 },
492}
493
494// ===========================================================================
495// dig.fetchRange (PEER)
496// ===========================================================================
497
498/// Params for [`dig.fetchRange`](crate::method::Method::FetchRange) — a single
499/// range frame of a resource this node holds.
500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
501#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
502pub struct FetchRangeParams {
503 /// The store launcher id (64-hex, required).
504 pub store_id: HexId,
505 /// The generation root (64-hex, required for a resource fetch).
506 pub root: HexId,
507 /// `SHA-256(urn)` (64-hex, required for a resource fetch).
508 pub retrieval_key: HexId,
509 /// The range start (default 0).
510 #[serde(skip_serializing_if = "Option::is_none", default)]
511 pub offset: Option<u64>,
512 /// The range length in bytes (> 0; clamped to the window cap).
513 pub length: u64,
514 /// Whole-capsule mode (default false). Capsule range fetch is not yet
515 /// served; a `true` here yields `-32004`.
516 #[serde(skip_serializing_if = "Option::is_none", default)]
517 pub capsule: Option<bool>,
518 /// The redirect budget already consumed (echoed from a `-32008` redirect).
519 #[serde(skip_serializing_if = "Option::is_none", default)]
520 pub redirect_depth: Option<u64>,
521}
522
523/// One range frame of a resource. The first frame (`offset == 0`) carries the
524/// per-resource verification metadata; later frames carry only the window.
525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
526#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
527pub struct RangeFrame {
528 /// The window start offset (echoed).
529 pub offset: u64,
530 /// This window's byte length.
531 pub length: u64,
532 /// This window's ciphertext, base64.
533 pub bytes: String,
534 /// Whether this frame ends the resource.
535 pub complete: bool,
536 /// The full resource ciphertext length. First frame only.
537 #[serde(skip_serializing_if = "Option::is_none", default)]
538 pub total_length: Option<u64>,
539 /// Per-chunk ciphertext lengths of the full resource. First frame only.
540 #[serde(skip_serializing_if = "Option::is_none", default)]
541 pub chunk_lens: Option<Vec<u64>>,
542 /// This frame's chunk index. First frame only (`0`).
543 #[serde(skip_serializing_if = "Option::is_none", default)]
544 pub chunk_index: Option<u64>,
545 /// Whole-resource merkle proof, base64. First frame only.
546 #[serde(skip_serializing_if = "Option::is_none", default)]
547 pub inclusion_proof: Option<String>,
548 /// The chain-anchored root (64-hex). First frame only.
549 ///
550 /// Advisory ECHO only. A verifier MUST NOT trust this value: it substitutes
551 /// its OWN URN-pinned root when folding [`range_proof`](Self::range_proof)
552 /// and rejects any frame whose proof does not reach that pinned root
553 /// (NC-9 fail-closed).
554 #[serde(skip_serializing_if = "Option::is_none", default)]
555 pub root: Option<HexId>,
556 /// Per-chunk merkle inclusion proofs for the chunks this frame covers, in
557 /// ascending chunk order — present on ANY frame, unlike the first-frame-only
558 /// whole-resource [`inclusion_proof`](Self::inclusion_proof).
559 ///
560 /// Each entry is base64 of an opaque per-chunk merkle proof blob (the
561 /// canonical node emits `dig_capsule::MerkleProof::encode`). This crate is a
562 /// pure level-00 wire type and MUST NOT depend on the merkle primitive, so
563 /// the proof stays an opaque string here; the consumer decodes each blob and
564 /// folds its leaf → the client's PINNED root (never the peer's echoed
565 /// [`root`](Self::root)) to verify the chunk.
566 ///
567 /// The server expands a requested byte range to the covering whole-chunk
568 /// span, so a frame's chunks align with [`chunk_lens`](Self::chunk_lens);
569 /// `range_proof.len()` equals the number of chunks in this frame and pairs
570 /// index-for-index with [`first_chunk_index`](Self::first_chunk_index)`..`.
571 #[serde(skip_serializing_if = "Option::is_none", default)]
572 pub range_proof: Option<Vec<String>>,
573 /// The chunk index of the first chunk in this frame (0-based, into the
574 /// resource's chunk sequence described by [`chunk_lens`](Self::chunk_lens)).
575 ///
576 /// With `range_proof.len()` this pins each entry of
577 /// [`range_proof`](Self::range_proof) to an absolute chunk index, giving the
578 /// full byte↔chunk↔proof mapping.
579 #[serde(skip_serializing_if = "Option::is_none", default)]
580 pub first_chunk_index: Option<u64>,
581}
582
583// ===========================================================================
584// dig.stage (CONTROL — loopback / in-process only)
585// ===========================================================================
586
587/// Params for [`dig.stage`](crate::method::Method::Stage) — compile a local
588/// folder into a capsule `.dig` module in-process.
589#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
590#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
591pub struct StageParams {
592 /// The absolute path to the folder to compile.
593 pub dir: String,
594 /// The target store launcher id (64-hex). Absent ⇒ an ephemeral,
595 /// content-derived id (a preview).
596 #[serde(skip_serializing_if = "Option::is_none", default)]
597 pub store_id: Option<HexId>,
598 /// The store salt (64-hex). Present ⇒ a private store.
599 #[serde(skip_serializing_if = "Option::is_none", default)]
600 pub salt: Option<HexId>,
601 /// Optional DIGHub-style manifest metadata to embed.
602 #[serde(skip_serializing_if = "Option::is_none", default)]
603 pub metadata: Option<serde_json::Value>,
604}
605
606/// Result for [`dig.stage`](crate::method::Method::Stage) — the compiled capsule.
607#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
608#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
609pub struct StageResult {
610 /// The canonical capsule identity, `storeId:rootHash`.
611 pub capsule: String,
612 /// The store launcher id (64-hex).
613 pub store_id: HexId,
614 /// The compiled generation root (64-hex).
615 pub root: HexId,
616 /// The filesystem path to the compiled `.dig` module.
617 pub module_path: String,
618 /// The module size in bytes.
619 pub size: u64,
620 /// The `chia://storeId:rootHash/` content address.
621 #[serde(skip_serializing_if = "Option::is_none", default)]
622 pub content_address: Option<String>,
623 /// The relative paths compiled into the capsule.
624 #[serde(default)]
625 pub files: Vec<String>,
626 /// Whether this is an ephemeral preview (not advancing a real store).
627 #[serde(skip_serializing_if = "Option::is_none", default)]
628 pub ephemeral: Option<bool>,
629}
630
631// ===========================================================================
632// cache.* (CONTROL — loopback / in-process only)
633// ===========================================================================
634
635/// Result for [`cache.getConfig`](crate::method::Method::CacheGetConfig).
636///
637/// The canonical field name for the cache path is `cache_dir` everywhere (the
638/// shell's historical `dir` is unified onto this name).
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
641pub struct CacheConfig {
642 /// The on-disk cache size cap in bytes (floored at 64 MiB).
643 pub cap_bytes: u64,
644 /// The bytes currently used.
645 pub used_bytes: u64,
646 /// The effective resolved cache directory.
647 pub cache_dir: String,
648 /// Whether that directory is the canonical shared location (vs a
649 /// process-private fallback).
650 pub shared: bool,
651}
652
653/// Params for [`cache.setCapBytes`](crate::method::Method::CacheSetCapBytes).
654#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
655#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
656pub struct SetCapBytesParams {
657 /// The requested cap in bytes (floored at 64 MiB by the node).
658 pub cap_bytes: u64,
659}
660
661/// Result for [`cache.setCapBytes`](crate::method::Method::CacheSetCapBytes).
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
663#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
664pub struct SetCapBytesResult {
665 /// The effective cap after flooring.
666 pub cap_bytes: u64,
667}
668
669/// One durable cached-module entry.
670#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
671#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
672pub struct CachedCapsule {
673 /// The canonical capsule identity, `storeId:rootHash`.
674 pub capsule: String,
675 /// The store launcher id (64-hex).
676 pub store_id: HexId,
677 /// The generation root (64-hex).
678 pub root: HexId,
679 /// The module size in bytes.
680 pub size_bytes: u64,
681 /// When the module was last used (unix ms).
682 pub last_used_unix_ms: u64,
683}
684
685/// Result for [`cache.listCached`](crate::method::Method::CacheListCached).
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
687#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
688pub struct CachedList {
689 /// The cached capsules.
690 pub cached: Vec<CachedCapsule>,
691}
692
693/// Params for a capsule-keyed cache op
694/// ([`cache.removeCached`](crate::method::Method::CacheRemoveCached),
695/// [`cache.fetchAndCache`](crate::method::Method::CacheFetchAndCache)).
696#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
697#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
698pub struct CapsuleKey {
699 /// The store launcher id (64-hex).
700 pub store_id: HexId,
701 /// The generation root (64-hex).
702 pub root: HexId,
703}
704
705/// Result for [`cache.removeCached`](crate::method::Method::CacheRemoveCached).
706#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
707#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
708pub struct RemoveCachedResult {
709 /// Whether an entry was removed.
710 pub removed: bool,
711}
712
713/// Result for [`cache.fetchAndCache`](crate::method::Method::CacheFetchAndCache).
714///
715/// A failed fetch is reported in-band (`status = "failed"` + `message`) so the
716/// caller can show it without treating it as a transport error.
717#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
718#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
719pub struct FetchAndCacheResult {
720 /// `"cached"`, `"already_cached"`, or `"failed"`.
721 pub status: String,
722 /// The fetched module size in bytes (on success).
723 #[serde(skip_serializing_if = "Option::is_none", default)]
724 pub size_bytes: Option<u64>,
725 /// The served generation root (64-hex, on success).
726 #[serde(skip_serializing_if = "Option::is_none", default)]
727 pub served_root: Option<HexId>,
728 /// The failure message (on `status = "failed"`).
729 #[serde(skip_serializing_if = "Option::is_none", default)]
730 pub message: Option<String>,
731}
732
733// ===========================================================================
734// control.peerStatus (CONTROL — loopback / in-process only)
735// ===========================================================================
736
737/// Result for [`control.peerStatus`](crate::method::Method::ControlPeerStatus) —
738/// a snapshot of the node's L7 peer network. Always safe to call; reports
739/// `running: false` on the FFI path.
740#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
741#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
742pub struct PeerStatusSnapshot {
743 /// Whether a peer network is currently active.
744 pub running: bool,
745 /// This node's `peer_id` (64-hex), if a peer network is running.
746 #[serde(skip_serializing_if = "Option::is_none", default)]
747 pub peer_id: Option<HexId>,
748 /// The DIG network id.
749 pub network_id: String,
750 /// The relay reservation posture.
751 pub relay: RelayStatus,
752 /// The number of currently connected peers.
753 pub connected_peers: u64,
754 /// The last peer-network error, if any.
755 #[serde(skip_serializing_if = "Option::is_none", default)]
756 pub last_error: Option<String>,
757}
758
759// ===========================================================================
760// cache.stats (CONTROL — loopback / in-process only)
761// ===========================================================================
762
763/// The decoded-content cache hit/miss counters carried in
764/// [`CacheStats`](CacheStats::content_cache).
765#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
766#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
767pub struct ContentCacheCounters {
768 /// Session decoded-content cache hits.
769 pub hits: u64,
770 /// Session decoded-content cache misses.
771 pub misses: u64,
772}
773
774/// Result for [`cache.stats`](crate::method::Method::CacheStats) — cache
775/// telemetry beside [`cache.getConfig`](crate::method::Method::CacheGetConfig):
776/// the reserved cap + live usage, the cached-capsule count + total on-disk
777/// bytes, and the session eviction + content-cache counters.
778#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
779#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
780pub struct CacheStats {
781 /// The on-disk cache size cap in bytes.
782 pub cap_bytes: u64,
783 /// The bytes currently used on disk.
784 pub used_bytes: u64,
785 /// The number of durable cached capsules.
786 pub entry_count: u64,
787 /// The total on-disk bytes across the cached capsules.
788 pub total_bytes: u64,
789 /// Capsules evicted this session.
790 pub evicted_count: u64,
791 /// Bytes evicted this session.
792 pub evicted_bytes: u64,
793 /// The decoded-content cache hit/miss counters.
794 pub content_cache: ContentCacheCounters,
795}
796
797// ===========================================================================
798// control.subscribe / control.unsubscribe / control.listSubscriptions
799// (CONTROL — loopback / in-process only)
800// ===========================================================================
801
802/// Params for [`control.subscribe`](crate::method::Method::ControlSubscribe) and
803/// [`control.unsubscribe`](crate::method::Method::ControlUnsubscribe).
804#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
805#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
806pub struct SubscribeParams {
807 /// The store launcher id to (un)subscribe (64-hex).
808 pub store_id: HexId,
809}
810
811/// Result for [`control.subscribe`](crate::method::Method::ControlSubscribe).
812#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
813#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
814pub struct SubscribeResult {
815 /// Always `true` — the store is subscribed after this call.
816 pub subscribed: bool,
817 /// Whether this call ADDED the subscription (`false` ⇒ already subscribed).
818 pub added: bool,
819 /// The canonical persisted store id (trimmed + lower-cased, 64-hex).
820 pub store_id: HexId,
821}
822
823/// Result for [`control.unsubscribe`](crate::method::Method::ControlUnsubscribe).
824#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
825#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
826pub struct UnsubscribeResult {
827 /// Always `false` — the store is not subscribed after this call.
828 pub subscribed: bool,
829 /// Whether this call REMOVED a subscription (`false` ⇒ was not subscribed).
830 pub removed: bool,
831 /// The canonical persisted store id (trimmed + lower-cased, 64-hex).
832 pub store_id: HexId,
833}
834
835/// Result for
836/// [`control.listSubscriptions`](crate::method::Method::ControlListSubscriptions).
837#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
838#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
839pub struct SubscriptionsList {
840 /// The persisted subscribed store ids (64-hex each).
841 pub subscriptions: Vec<HexId>,
842 /// The subscription count (`subscriptions.len()`).
843 pub count: u64,
844}
845
846// ===========================================================================
847// control.peers.connect / control.peers.disconnect
848// (CONTROL — loopback / in-process only)
849// ===========================================================================
850
851/// Params for [`control.peers.connect`](crate::method::Method::ControlPeersConnect)
852/// and [`control.peers.disconnect`](crate::method::Method::ControlPeersDisconnect).
853#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
854#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
855pub struct PeerConnectParams {
856 /// The peer to dial/drop — a dialable address, or a known peer's `peer_id`
857 /// (64-hex) to resolve an already-connected peer.
858 pub peer: String,
859}
860
861/// Result for
862/// [`control.peers.connect`](crate::method::Method::ControlPeersConnect).
863#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
864#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
865pub struct PeerConnectResult {
866 /// Always `true` on success — the peer is a counted, connected pool member.
867 pub connected: bool,
868 /// The connected peer's stable `peer_id` (64-hex).
869 pub peer_id: HexId,
870}
871
872/// Result for
873/// [`control.peers.disconnect`](crate::method::Method::ControlPeersDisconnect).
874///
875/// Idempotent: disconnecting a peer that is not connected succeeds as a no-op.
876#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
877#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
878pub struct PeerDisconnectResult {
879 /// Always `true` — the peer is not in the pool after this call.
880 pub disconnected: bool,
881 /// The dropped peer's `peer_id` (trimmed + lower-cased, 64-hex).
882 pub peer_id: HexId,
883}
884
885// ===========================================================================
886// dig.health / dig.methods / rpc.discover (discovery)
887// ===========================================================================
888
889/// Result for [`dig.health`](crate::method::Method::Health) — liveness + a
890/// capability summary.
891#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
892#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
893pub struct Health {
894 /// Liveness — `"ok"` when the node can serve.
895 pub status: String,
896 /// The node's software version.
897 #[serde(skip_serializing_if = "Option::is_none", default)]
898 pub version: Option<String>,
899 /// The DIG network id the node serves.
900 #[serde(skip_serializing_if = "Option::is_none", default)]
901 pub network_id: Option<String>,
902 /// The method names this node implements (its profile).
903 #[serde(default)]
904 pub methods: Vec<String>,
905}
906
907/// Result for [`dig.methods`](crate::method::Method::Methods) — the method names
908/// this node implements (agent self-describe).
909#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
910#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
911pub struct Methods {
912 /// The implemented method names.
913 pub methods: Vec<String>,
914}
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919
920 /// **Proves:** `ContentChunk` round-trips a node-profile window (no
921 /// network-profile fields) without inventing keys.
922 /// **Catches:** a missing `skip_serializing_if` that would leak `null`
923 /// network-profile fields onto the node profile.
924 #[test]
925 fn content_chunk_node_profile_is_lean() {
926 let c = ContentChunk {
927 ciphertext: "AAA=".into(),
928 root: "ab".repeat(32),
929 complete: false,
930 next_offset: Some(3_145_728),
931 inclusion_proof: Some("cHJvb2Y=".into()),
932 chunk_lens: Some(vec![10, 20]),
933 source: Some("local".into()),
934 total_length: None,
935 length: None,
936 offset: None,
937 program_hash: None,
938 };
939 let v = serde_json::to_value(&c).unwrap();
940 assert_eq!(v["source"], "local");
941 assert!(
942 v.get("total_length").is_none(),
943 "node profile must omit total_length"
944 );
945 assert!(v.get("program_hash").is_none());
946 assert_eq!(serde_json::from_value::<ContentChunk>(v).unwrap(), c);
947 }
948
949 /// **Proves:** the network-profile fields serialize when present.
950 #[test]
951 fn content_chunk_network_profile_carries_extras() {
952 let c = ContentChunk {
953 ciphertext: "AAA=".into(),
954 root: "cd".repeat(32),
955 complete: true,
956 next_offset: None,
957 inclusion_proof: None,
958 chunk_lens: None,
959 source: None,
960 total_length: Some(100),
961 length: Some(100),
962 offset: Some(0),
963 program_hash: Some("ef".repeat(32)),
964 };
965 let v = serde_json::to_value(&c).unwrap();
966 assert_eq!(v["total_length"], 100);
967 assert_eq!(v["length"], 100);
968 assert!(v.get("source").is_none());
969 }
970
971 /// **Proves:** the untagged `Inventory` picks `ForStore` vs `AllStores` by
972 /// shape.
973 /// **Catches:** a lost `#[serde(untagged)]` that would tag the variant.
974 #[test]
975 fn inventory_untagged_by_shape() {
976 let for_store = Inventory::ForStore {
977 store_id: "ab".repeat(32),
978 roots: vec!["cd".repeat(32)],
979 };
980 let s = serde_json::to_string(&for_store).unwrap();
981 assert!(s.contains("\"roots\""));
982 assert!(!s.contains("ForStore"));
983 assert_eq!(serde_json::from_str::<Inventory>(&s).unwrap(), for_store);
984
985 let all = Inventory::AllStores {
986 stores: vec!["ef".repeat(32)],
987 };
988 let s = serde_json::to_string(&all).unwrap();
989 assert!(s.contains("\"stores\""));
990 assert_eq!(serde_json::from_str::<Inventory>(&s).unwrap(), all);
991 }
992
993 /// **Proves:** `RedirectInfo` serializes the full redirect payload the
994 /// `-32008` envelope carries.
995 #[test]
996 fn redirect_info_shape() {
997 let r = RedirectInfo {
998 content: ContentRef {
999 store_id: "ab".repeat(32),
1000 root: Some("cd".repeat(32)),
1001 retrieval_key: Some("ef".repeat(32)),
1002 },
1003 providers: vec![Provider {
1004 peer_id: "12".repeat(32),
1005 addresses: vec![PeerAddress {
1006 host: "::1".into(),
1007 port: 9444,
1008 kind: "direct".into(),
1009 }],
1010 }],
1011 redirect_depth: 1,
1012 max_redirects: 4,
1013 };
1014 let v = serde_json::to_value(&r).unwrap();
1015 assert_eq!(v["redirect_depth"], 1);
1016 assert_eq!(v["max_redirects"], 4);
1017 assert_eq!(v["providers"][0]["addresses"][0]["host"], "::1");
1018 assert_eq!(serde_json::from_value::<RedirectInfo>(v).unwrap(), r);
1019 }
1020
1021 /// **Proves:** `cache.stats` models the live dig-node result field-for-field
1022 /// (the nested `content_cache{hits,misses}` object included).
1023 /// **Catches:** a drift from the node's `cache.stats` wire shape (#1075).
1024 #[test]
1025 fn cache_stats_wire_shape() {
1026 let s = CacheStats {
1027 cap_bytes: 1 << 30,
1028 used_bytes: 2048,
1029 entry_count: 3,
1030 total_bytes: 2048,
1031 evicted_count: 1,
1032 evicted_bytes: 512,
1033 content_cache: ContentCacheCounters { hits: 7, misses: 2 },
1034 };
1035 let v = serde_json::to_value(s).unwrap();
1036 assert_eq!(v["cap_bytes"], 1 << 30);
1037 assert_eq!(v["entry_count"], 3);
1038 assert_eq!(v["content_cache"]["hits"], 7);
1039 assert_eq!(v["content_cache"]["misses"], 2);
1040 assert_eq!(serde_json::from_value::<CacheStats>(v).unwrap(), s);
1041 }
1042
1043 /// **Proves:** the subscription-management results carry the exact
1044 /// `{subscribed, added|removed, store_id}` / `{subscriptions, count}` shapes
1045 /// the live node returns.
1046 #[test]
1047 fn subscription_result_shapes() {
1048 let sub = SubscribeResult {
1049 subscribed: true,
1050 added: true,
1051 store_id: "ab".repeat(32),
1052 };
1053 let v = serde_json::to_value(&sub).unwrap();
1054 assert_eq!(v["subscribed"], true);
1055 assert_eq!(v["added"], true);
1056 assert_eq!(serde_json::from_value::<SubscribeResult>(v).unwrap(), sub);
1057
1058 let unsub = UnsubscribeResult {
1059 subscribed: false,
1060 removed: true,
1061 store_id: "cd".repeat(32),
1062 };
1063 let v = serde_json::to_value(&unsub).unwrap();
1064 assert_eq!(v["subscribed"], false);
1065 assert_eq!(v["removed"], true);
1066 assert_eq!(
1067 serde_json::from_value::<UnsubscribeResult>(v).unwrap(),
1068 unsub
1069 );
1070
1071 let list = SubscriptionsList {
1072 subscriptions: vec!["ef".repeat(32)],
1073 count: 1,
1074 };
1075 let v = serde_json::to_value(&list).unwrap();
1076 assert_eq!(v["count"], 1);
1077 assert_eq!(
1078 serde_json::from_value::<SubscriptionsList>(v).unwrap(),
1079 list
1080 );
1081 }
1082
1083 /// **Proves:** the peer connect/disconnect params + results round-trip and
1084 /// match the node's `{connected|disconnected, peer_id}` shapes.
1085 #[test]
1086 fn peer_connect_disconnect_shapes() {
1087 let p = PeerConnectParams {
1088 peer: "12".repeat(32),
1089 };
1090 let v = serde_json::to_value(&p).unwrap();
1091 assert_eq!(serde_json::from_value::<PeerConnectParams>(v).unwrap(), p);
1092
1093 let c = PeerConnectResult {
1094 connected: true,
1095 peer_id: "12".repeat(32),
1096 };
1097 let v = serde_json::to_value(&c).unwrap();
1098 assert_eq!(v["connected"], true);
1099 assert_eq!(serde_json::from_value::<PeerConnectResult>(v).unwrap(), c);
1100
1101 let d = PeerDisconnectResult {
1102 disconnected: true,
1103 peer_id: "34".repeat(32),
1104 };
1105 let v = serde_json::to_value(&d).unwrap();
1106 assert_eq!(v["disconnected"], true);
1107 assert_eq!(
1108 serde_json::from_value::<PeerDisconnectResult>(v).unwrap(),
1109 d
1110 );
1111 }
1112
1113 /// **Proves:** `cache.getConfig` uses the canonical `cache_dir` field name.
1114 /// **Catches:** a regression to the shell's historical `dir` name.
1115 #[test]
1116 fn cache_config_field_name_is_cache_dir() {
1117 let c = CacheConfig {
1118 cap_bytes: 1 << 30,
1119 used_bytes: 0,
1120 cache_dir: "/var/cache/dig".into(),
1121 shared: true,
1122 };
1123 let v = serde_json::to_value(&c).unwrap();
1124 assert!(v.get("cache_dir").is_some());
1125 assert!(v.get("dir").is_none(), "must not use the legacy `dir` name");
1126 }
1127}