use serde::{Deserialize, Serialize};
pub type HexId = String;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeerAddress {
pub host: String,
pub port: u16,
pub kind: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct Provider {
pub peer_id: HexId,
pub addresses: Vec<PeerAddress>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ContentRef {
pub store_id: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub root: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub retrieval_key: Option<HexId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct RedirectInfo {
pub content: ContentRef,
pub providers: Vec<Provider>,
pub redirect_depth: u64,
pub max_redirects: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct GetContentParams {
pub store_id: HexId,
pub retrieval_key: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub root: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub redirect_depth: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ContentChunk {
pub ciphertext: String,
pub root: HexId,
pub complete: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub next_offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub inclusion_proof: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub chunk_lens: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub total_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub program_hash: Option<HexId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct GetAnchoredRootParams {
pub store_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AnchoredRoot {
pub store_id: HexId,
pub root: HexId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct GetCollectionParams {
pub launcher_ids: Vec<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub did: Option<HexId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct Collection {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub did: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub declared_did: Option<HexId>,
pub item_count: u64,
pub resolved_count: u64,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub royalty_basis_points: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ListCollectionItemsParams {
pub launcher_ids: Vec<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub limit: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct NftMetadata {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub edition_number: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub edition_total: Option<u64>,
#[serde(default)]
pub data_uris: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub data_hash: Option<HexId>,
#[serde(default)]
pub metadata_uris: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub metadata_hash: Option<HexId>,
#[serde(default)]
pub license_uris: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub license_hash: Option<HexId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CollectionItem {
pub launcher_id: HexId,
pub coin_id: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub owner_did: Option<HexId>,
pub royalty_puzzle_hash: HexId,
pub royalty_basis_points: u64,
pub owner_puzzle_hash: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub metadata: Option<NftMetadata>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CollectionItemsPage {
pub items: Vec<CollectionItem>,
pub offset: u64,
pub limit: u64,
pub total: u64,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub next_offset: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct RelayStatus {
pub url: String,
pub reserved: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct NetworkInfo {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub peer_id: Option<HexId>,
pub network_id: String,
pub listen_addr: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub reflexive_addr: Option<String>,
pub candidate_addresses: Vec<String>,
pub reachability: String,
pub relay: RelayStatus,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeersList {
pub peers: Vec<Provider>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AnnounceParams {
pub peer_id: HexId,
pub addresses: Vec<PeerAddress>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AnnounceAck {
pub accepted: bool,
pub known_peers: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AvailabilityQuery {
pub store_id: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub root: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub retrieval_key: Option<HexId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct GetAvailabilityParams {
pub items: Vec<AvailabilityQuery>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub redirect_depth: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub budget_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ask_id: Option<String>,
}
impl GetAvailabilityParams {
pub fn new(items: Vec<AvailabilityQuery>) -> Self {
GetAvailabilityParams {
items,
redirect_depth: None,
budget_ms: None,
ask_id: None,
}
}
pub fn with_redirect_depth(mut self, redirect_depth: u64) -> Self {
self.redirect_depth = Some(redirect_depth);
self
}
pub fn hops_consumed(&self) -> u64 {
self.redirect_depth.unwrap_or(0)
}
pub fn with_budget_ms(mut self, budget_ms: u64) -> Self {
self.budget_ms = Some(budget_ms);
self
}
pub fn budget_ms(&self) -> Option<u64> {
self.budget_ms
}
pub fn with_ask_id(mut self, ask_id: impl Into<String>) -> Self {
self.ask_id = Some(ask_id.into());
self
}
pub fn ask_id(&self) -> Option<&str> {
self.ask_id.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AvailabilityAnswer {
pub available: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub roots: Option<Vec<HexId>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub total_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub chunk_count: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub providers: Option<Vec<Provider>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub absence_established: Option<bool>,
}
impl AvailabilityAnswer {
pub fn absence_established_or_unknown(&self) -> Option<bool> {
self.absence_established
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AvailabilityBatch {
pub items: Vec<AvailabilityAnswer>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ListInventoryParams {
#[serde(skip_serializing_if = "Option::is_none", default)]
pub store_id: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub limit: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub enum Inventory {
ForStore {
store_id: HexId,
roots: Vec<HexId>,
},
AllStores {
stores: Vec<HexId>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct FetchRangeParams {
pub store_id: HexId,
pub root: HexId,
pub retrieval_key: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub offset: Option<u64>,
pub length: u64,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub capsule: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub redirect_depth: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skip_layout: Option<bool>,
}
impl FetchRangeParams {
pub fn resource(
store_id: impl Into<HexId>,
root: impl Into<HexId>,
retrieval_key: impl Into<HexId>,
length: u64,
) -> Self {
FetchRangeParams {
store_id: store_id.into(),
root: root.into(),
retrieval_key: retrieval_key.into(),
offset: None,
length,
capsule: None,
redirect_depth: None,
skip_layout: None,
}
}
pub fn with_offset(mut self, offset: u64) -> Self {
self.offset = Some(offset);
self
}
pub fn with_capsule(mut self, capsule: bool) -> Self {
self.capsule = Some(capsule);
self
}
pub fn with_redirect_depth(mut self, redirect_depth: u64) -> Self {
self.redirect_depth = Some(redirect_depth);
self
}
pub fn with_skip_layout(mut self, skip_layout: bool) -> Self {
self.skip_layout = Some(skip_layout);
self
}
pub fn suppresses_layout(&self) -> bool {
self.skip_layout.unwrap_or(false)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct RangeFrame {
pub offset: u64,
pub length: u64,
pub bytes: String,
pub complete: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub total_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub chunk_lens: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub chunk_index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub inclusion_proof: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub root: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub range_proof: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub first_chunk_index: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chunk_count: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chunk_lens_offset: Option<u64>,
}
impl RangeFrame {
pub fn data(offset: u64, length: u64, bytes: impl Into<String>) -> Self {
RangeFrame {
offset,
length,
bytes: bytes.into(),
complete: false,
total_length: None,
chunk_lens: None,
chunk_index: None,
inclusion_proof: None,
root: None,
range_proof: None,
first_chunk_index: None,
chunk_count: None,
chunk_lens_offset: None,
}
}
pub fn with_complete(mut self, complete: bool) -> Self {
self.complete = complete;
self
}
pub fn with_identity(
mut self,
root: impl Into<HexId>,
total_length: u64,
chunk_count: u64,
) -> Self {
self.root = Some(root.into());
self.total_length = Some(total_length);
self.chunk_count = Some(chunk_count);
self
}
pub fn with_chunk_index(mut self, chunk_index: u64) -> Self {
self.chunk_index = Some(chunk_index);
self
}
pub fn with_first_chunk_index(mut self, first_chunk_index: u64) -> Self {
self.first_chunk_index = Some(first_chunk_index);
self
}
pub fn with_chunk_lens_page(mut self, chunk_lens_offset: u64, chunk_lens: Vec<u64>) -> Self {
self.chunk_lens_offset = Some(chunk_lens_offset);
self.chunk_lens = Some(chunk_lens);
self
}
pub fn with_inclusion_proof(mut self, inclusion_proof: impl Into<String>) -> Self {
self.inclusion_proof = Some(inclusion_proof.into());
self
}
pub fn with_range_proof(mut self, range_proof: Vec<String>) -> Self {
self.range_proof = Some(range_proof);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct GetModuleInfoParams {
pub store_id: HexId,
pub root: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ModuleInfo {
pub total_size: u64,
pub module_hash: HexId,
pub chunk_hashes: Vec<HexId>,
pub chunk_lens: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct FetchModuleRangeParams {
pub store_id: HexId,
pub root: HexId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub offset: Option<u64>,
pub length: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct StageParams {
pub dir: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub store_id: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub salt: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct StageResult {
pub capsule: String,
pub store_id: HexId,
pub root: HexId,
pub module_path: String,
pub size: u64,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub content_address: Option<String>,
#[serde(default)]
pub files: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ephemeral: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CacheConfig {
pub cap_bytes: u64,
pub used_bytes: u64,
pub cache_dir: String,
pub shared: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct SetCapBytesParams {
pub cap_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct SetCapBytesResult {
pub cap_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CachedCapsule {
pub capsule: String,
pub store_id: HexId,
pub root: HexId,
pub size_bytes: u64,
pub last_used_unix_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CachedList {
pub cached: Vec<CachedCapsule>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CapsuleKey {
pub store_id: HexId,
pub root: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct RemoveCachedResult {
pub removed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct FetchAndCacheResult {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub size_bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub served_root: Option<HexId>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub message: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeerStatusSnapshot {
pub running: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub peer_id: Option<HexId>,
pub network_id: String,
pub relay: RelayStatus,
pub connected_peers: u64,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub last_error: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ContentCacheCounters {
pub hits: u64,
pub misses: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CacheStats {
pub cap_bytes: u64,
pub used_bytes: u64,
pub entry_count: u64,
pub total_bytes: u64,
pub evicted_count: u64,
pub evicted_bytes: u64,
pub content_cache: ContentCacheCounters,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct SubscribeParams {
pub store_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct SubscribeResult {
pub subscribed: bool,
pub added: bool,
pub store_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct UnsubscribeResult {
pub subscribed: bool,
pub removed: bool,
pub store_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct SubscriptionsList {
pub subscriptions: Vec<HexId>,
pub count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeerConnectParams {
pub peer: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeerConnectResult {
pub connected: bool,
pub peer_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PeerDisconnectResult {
pub disconnected: bool,
pub peer_id: HexId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct Health {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub network_id: Option<String>,
#[serde(default)]
pub methods: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct Methods {
pub methods: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn content_chunk_node_profile_is_lean() {
let c = ContentChunk {
ciphertext: "AAA=".into(),
root: "ab".repeat(32),
complete: false,
next_offset: Some(3_145_728),
inclusion_proof: Some("cHJvb2Y=".into()),
chunk_lens: Some(vec![10, 20]),
source: Some("local".into()),
total_length: None,
length: None,
offset: None,
program_hash: None,
};
let v = serde_json::to_value(&c).unwrap();
assert_eq!(v["source"], "local");
assert!(
v.get("total_length").is_none(),
"node profile must omit total_length"
);
assert!(v.get("program_hash").is_none());
assert_eq!(serde_json::from_value::<ContentChunk>(v).unwrap(), c);
}
#[test]
fn content_chunk_network_profile_carries_extras() {
let c = ContentChunk {
ciphertext: "AAA=".into(),
root: "cd".repeat(32),
complete: true,
next_offset: None,
inclusion_proof: None,
chunk_lens: None,
source: None,
total_length: Some(100),
length: Some(100),
offset: Some(0),
program_hash: Some("ef".repeat(32)),
};
let v = serde_json::to_value(&c).unwrap();
assert_eq!(v["total_length"], 100);
assert_eq!(v["length"], 100);
assert!(v.get("source").is_none());
}
#[test]
fn inventory_untagged_by_shape() {
let for_store = Inventory::ForStore {
store_id: "ab".repeat(32),
roots: vec!["cd".repeat(32)],
};
let s = serde_json::to_string(&for_store).unwrap();
assert!(s.contains("\"roots\""));
assert!(!s.contains("ForStore"));
assert_eq!(serde_json::from_str::<Inventory>(&s).unwrap(), for_store);
let all = Inventory::AllStores {
stores: vec!["ef".repeat(32)],
};
let s = serde_json::to_string(&all).unwrap();
assert!(s.contains("\"stores\""));
assert_eq!(serde_json::from_str::<Inventory>(&s).unwrap(), all);
}
#[test]
fn redirect_info_shape() {
let r = RedirectInfo {
content: ContentRef {
store_id: "ab".repeat(32),
root: Some("cd".repeat(32)),
retrieval_key: Some("ef".repeat(32)),
},
providers: vec![Provider {
peer_id: "12".repeat(32),
addresses: vec![PeerAddress {
host: "::1".into(),
port: 9444,
kind: "direct".into(),
}],
}],
redirect_depth: 1,
max_redirects: 4,
};
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["redirect_depth"], 1);
assert_eq!(v["max_redirects"], 4);
assert_eq!(v["providers"][0]["addresses"][0]["host"], "::1");
assert_eq!(serde_json::from_value::<RedirectInfo>(v).unwrap(), r);
}
#[test]
fn cache_stats_wire_shape() {
let s = CacheStats {
cap_bytes: 1 << 30,
used_bytes: 2048,
entry_count: 3,
total_bytes: 2048,
evicted_count: 1,
evicted_bytes: 512,
content_cache: ContentCacheCounters { hits: 7, misses: 2 },
};
let v = serde_json::to_value(s).unwrap();
assert_eq!(v["cap_bytes"], 1 << 30);
assert_eq!(v["entry_count"], 3);
assert_eq!(v["content_cache"]["hits"], 7);
assert_eq!(v["content_cache"]["misses"], 2);
assert_eq!(serde_json::from_value::<CacheStats>(v).unwrap(), s);
}
#[test]
fn subscription_result_shapes() {
let sub = SubscribeResult {
subscribed: true,
added: true,
store_id: "ab".repeat(32),
};
let v = serde_json::to_value(&sub).unwrap();
assert_eq!(v["subscribed"], true);
assert_eq!(v["added"], true);
assert_eq!(serde_json::from_value::<SubscribeResult>(v).unwrap(), sub);
let unsub = UnsubscribeResult {
subscribed: false,
removed: true,
store_id: "cd".repeat(32),
};
let v = serde_json::to_value(&unsub).unwrap();
assert_eq!(v["subscribed"], false);
assert_eq!(v["removed"], true);
assert_eq!(
serde_json::from_value::<UnsubscribeResult>(v).unwrap(),
unsub
);
let list = SubscriptionsList {
subscriptions: vec!["ef".repeat(32)],
count: 1,
};
let v = serde_json::to_value(&list).unwrap();
assert_eq!(v["count"], 1);
assert_eq!(
serde_json::from_value::<SubscriptionsList>(v).unwrap(),
list
);
}
#[test]
fn module_info_chunk_lens_shape() {
let info = ModuleInfo {
total_size: 1024,
module_hash: "ab".repeat(32),
chunk_hashes: vec!["cd".repeat(32), "ef".repeat(32)],
chunk_lens: vec![512, 512],
};
let v = serde_json::to_value(&info).unwrap();
assert_eq!(v["total_size"], 1024);
assert_eq!(v["chunk_hashes"].as_array().unwrap().len(), 2);
assert_eq!(v["chunk_lens"].as_array().unwrap().len(), 2);
assert_eq!(v["chunk_lens"][0], 512);
assert_eq!(v["chunk_lens"][1], 512);
assert_eq!(serde_json::from_value::<ModuleInfo>(v).unwrap(), info);
}
#[test]
fn module_info_rejects_missing_chunk_lens() {
let json_str = r#"{"total_size": 2048, "module_hash": "1122334455667788990011223344556677889900112233445566778899001122", "chunk_hashes": []}"#;
let result: Result<ModuleInfo, _> = serde_json::from_str(json_str);
assert!(
result.is_err(),
"ModuleInfo must reject JSON missing the required chunk_lens field"
);
let err = result.unwrap_err();
assert!(
err.to_string().contains("chunk_lens"),
"error message should mention chunk_lens: {}",
err
);
}
#[test]
fn peer_connect_disconnect_shapes() {
let p = PeerConnectParams {
peer: "12".repeat(32),
};
let v = serde_json::to_value(&p).unwrap();
assert_eq!(serde_json::from_value::<PeerConnectParams>(v).unwrap(), p);
let c = PeerConnectResult {
connected: true,
peer_id: "12".repeat(32),
};
let v = serde_json::to_value(&c).unwrap();
assert_eq!(v["connected"], true);
assert_eq!(serde_json::from_value::<PeerConnectResult>(v).unwrap(), c);
let d = PeerDisconnectResult {
disconnected: true,
peer_id: "34".repeat(32),
};
let v = serde_json::to_value(&d).unwrap();
assert_eq!(v["disconnected"], true);
assert_eq!(
serde_json::from_value::<PeerDisconnectResult>(v).unwrap(),
d
);
}
#[test]
fn cache_config_field_name_is_cache_dir() {
let c = CacheConfig {
cap_bytes: 1 << 30,
used_bytes: 0,
cache_dir: "/var/cache/dig".into(),
shared: true,
};
let v = serde_json::to_value(&c).unwrap();
assert!(v.get("cache_dir").is_some());
assert!(v.get("dir").is_none(), "must not use the legacy `dir` name");
}
#[test]
fn get_availability_params_accepts_an_older_clients_params() {
let older = json!({
"items": [ { "store_id": "ab".repeat(32) } ]
});
let p: GetAvailabilityParams = serde_json::from_value(older).unwrap();
assert_eq!(p.items.len(), 1);
assert_eq!(p.redirect_depth, None, "an absent budget stays absent");
assert_eq!(p.hops_consumed(), 0, "absent means zero hops consumed");
}
#[test]
fn get_availability_params_omits_an_absent_hop_budget() {
let p = GetAvailabilityParams::new(vec![AvailabilityQuery {
store_id: "ab".repeat(32),
root: None,
retrieval_key: None,
}]);
let v = serde_json::to_value(&p).unwrap();
let keys: Vec<&String> = v.as_object().unwrap().keys().collect();
assert_eq!(keys, vec!["items"], "hop-zero params carry only `items`");
}
#[test]
fn get_availability_params_round_trips_the_hop_budget() {
let p = GetAvailabilityParams::new(vec![AvailabilityQuery {
store_id: "cd".repeat(32),
root: Some("ef".repeat(32)),
retrieval_key: None,
}])
.with_redirect_depth(2);
let v = serde_json::to_value(&p).unwrap();
assert_eq!(v["redirect_depth"], 2);
assert_eq!(p.hops_consumed(), 2);
assert_eq!(
serde_json::from_value::<GetAvailabilityParams>(v).unwrap(),
p
);
}
#[test]
fn get_availability_params_accepts_a_client_older_than_the_recursive_ask() {
let older = json!({ "items": [ { "store_id": "ab".repeat(32) } ] });
let p: GetAvailabilityParams = serde_json::from_value(older).unwrap();
assert_eq!(p.budget_ms(), None, "absent budget_ms means unbudgeted");
assert_eq!(p.ask_id(), None, "absent ask_id means dedup opted out");
assert_eq!(p.hops_consumed(), 0);
}
#[test]
fn get_availability_params_omits_absent_recursive_ask_fields() {
let p = GetAvailabilityParams::new(vec![AvailabilityQuery {
store_id: "ab".repeat(32),
root: None,
retrieval_key: None,
}]);
let v = serde_json::to_value(&p).unwrap();
let keys: Vec<&String> = v.as_object().unwrap().keys().collect();
assert_eq!(keys, vec!["items"], "a plain ask carries only `items`");
}
#[test]
fn the_time_budget_is_a_separate_field_from_the_hop_budget() {
let p = GetAvailabilityParams::new(vec![AvailabilityQuery {
store_id: "cd".repeat(32),
root: None,
retrieval_key: None,
}])
.with_redirect_depth(2)
.with_budget_ms(9_000);
let v = serde_json::to_value(&p).unwrap();
assert_eq!(v["redirect_depth"], 2, "hops counted UP from zero");
assert_eq!(v["budget_ms"], 9_000, "milliseconds counted DOWN to zero");
assert_eq!(p.hops_consumed(), 2);
assert_eq!(p.budget_ms(), Some(9_000));
assert_eq!(
serde_json::from_value::<GetAvailabilityParams>(v).unwrap(),
p
);
}
#[test]
fn a_zero_time_budget_is_not_the_same_as_an_absent_one() {
let exhausted = GetAvailabilityParams::new(vec![]).with_budget_ms(0);
let v = serde_json::to_value(&exhausted).unwrap();
assert_eq!(v["budget_ms"], 0, "an exhausted budget stays on the wire");
assert_eq!(
serde_json::from_value::<GetAvailabilityParams>(v)
.unwrap()
.budget_ms(),
Some(0)
);
assert_eq!(
GetAvailabilityParams::new(vec![]).budget_ms(),
None,
"unbudgeted is a different state from budget zero"
);
}
#[test]
fn the_ask_id_is_not_the_jsonrpc_correlator() {
const ASK_ID: &str = "3f9c1a04b7e25d68f0a1c3b5d7e9f012";
assert_eq!(ASK_ID.len(), 32, "16 random bytes as lowercase hex");
let envelope = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "dig.getAvailability",
"params": {
"items": [ { "store_id": "ab".repeat(32) } ],
"ask_id": ASK_ID,
}
});
let p: GetAvailabilityParams = serde_json::from_value(envelope["params"].clone()).unwrap();
assert_eq!(p.ask_id(), Some(ASK_ID));
assert_ne!(
p.ask_id(),
Some("1"),
"the dedup identity must not be read from the envelope `id`"
);
assert_eq!(envelope["id"], 1, "the correlator is untouched beside it");
}
#[test]
fn absence_established_keeps_absent_distinct_from_false() {
let asserted = AvailabilityAnswer {
available: false,
absence_established: Some(true),
..Default::default()
};
let inconclusive = AvailabilityAnswer {
available: false,
absence_established: Some(false),
..Default::default()
};
let older_server = AvailabilityAnswer {
available: false,
..Default::default()
};
assert_ne!(
inconclusive, older_server,
"an explicit `false` is a claim; an absent field is not"
);
assert_eq!(asserted.absence_established_or_unknown(), Some(true));
assert_eq!(inconclusive.absence_established_or_unknown(), Some(false));
assert_eq!(
older_server.absence_established_or_unknown(),
None,
"an older server makes no claim either way"
);
let batch = serde_json::to_value(AvailabilityBatch {
items: vec![asserted, inconclusive, older_server],
})
.unwrap();
assert_eq!(batch["items"][0]["absence_established"], true);
assert_eq!(batch["items"][1]["absence_established"], false);
assert!(
batch["items"][2].get("absence_established").is_none(),
"the unknown state is an absent key, never `false` and never `null`"
);
}
#[test]
fn an_answer_carrying_the_new_field_still_parses_as_the_shipped_shape() {
let newer = json!({
"items": [ { "available": false, "absence_established": true } ]
});
let b: AvailabilityBatch = serde_json::from_value(newer).unwrap();
assert_eq!(b.items.len(), 1);
assert!(!b.items[0].available);
assert_eq!(b.items[0].absence_established_or_unknown(), Some(true));
}
#[test]
fn availability_hop_budget_mirrors_the_redirect_budget() {
let handed_back = RedirectInfo {
content: ContentRef {
store_id: "ab".repeat(32),
root: None,
retrieval_key: None,
},
providers: vec![],
redirect_depth: 3,
max_redirects: 4,
};
let echoed = handed_back.redirect_depth;
let availability = serde_json::to_value(
GetAvailabilityParams::new(vec![AvailabilityQuery {
store_id: "ab".repeat(32),
root: None,
retrieval_key: None,
}])
.with_redirect_depth(echoed),
)
.unwrap();
let content = serde_json::to_value(GetContentParams {
store_id: "ab".repeat(32),
retrieval_key: "cd".repeat(32),
root: None,
offset: None,
mode: None,
redirect_depth: Some(echoed),
})
.unwrap();
let range = serde_json::to_value(
FetchRangeParams::resource("ab".repeat(32), "cd".repeat(32), "ef".repeat(32), 1)
.with_redirect_depth(echoed),
)
.unwrap();
for (method, params) in [
("dig.getAvailability", &availability),
("dig.getContent", &content),
("dig.fetchRange", &range),
] {
assert_eq!(
params["redirect_depth"], 3,
"{method} must carry the echoed depth under `redirect_depth`"
);
}
assert!(
handed_back.redirect_depth < handed_back.max_redirects,
"the budget counts UP toward `max_redirects`"
);
}
#[test]
fn get_availability_params_tolerates_an_unknown_field() {
let newer = json!({
"items": [ { "store_id": "ab".repeat(32) } ],
"redirect_depth": 1,
"a_field_this_build_does_not_know": true
});
let p: GetAvailabilityParams = serde_json::from_value(newer).unwrap();
assert_eq!(p.hops_consumed(), 1);
}
}