Skip to main content

kinetic_core/error/
dns.rs

1//! DNS zone payload validation error types (`KIN-DNS-NNN`).
2//!
3//! Errors produced during [`DnsZone`](crate::types::dns::DnsZone) parsing and record-level
4//! validation. A DNS zone is the JSON payload embedded inside a Kinetic reveal record;
5//! every field must pass these checks before the zone is stored in the DHT.
6//!
7//! The 50-record limit and JSON nesting depth cap enforce the 80 KB DHT record size ceiling.
8use super::Severity;
9use thiserror::Error;
10
11/// Error type for DNS zone payloads and record validation.
12#[derive(Error, Debug)]
13pub enum DnsError {
14    /// The DNS JSON payload is nested too deeply.
15    #[error("Payload rejected: JSON nested too deeply")]
16    NestedTooDeeply,
17
18    /// The DNS JSON payload could not be parsed.
19    #[error("Failed to parse DNS zone: {0}")]
20    ParseError(#[from] serde_json::Error),
21
22    /// The zone contains more than the maximum allowed number of records.
23    #[error("Maximum of 50 DNS records allowed per zone to prevent network bloat")]
24    TooManyRecords,
25
26    /// A DNS label has an invalid length (empty or >62 chars).
27    #[error("Invalid label length: {0}")]
28    InvalidLabelLength(String),
29
30    /// A DNS label contains invalid characters.
31    #[error("Invalid label character: {0}")]
32    InvalidLabelCharacters(String),
33
34    /// A CNAME record was provided alongside other records for the same label.
35    #[error("CNAME record for '{0}' must be the only record")]
36    InvalidCnameConfiguration(String),
37
38    /// A TXT record exceeds the maximum allowed length (255 bytes).
39    #[error("TXT record too long for label {0}")]
40    TxtRecordTooLong(String),
41
42    /// A CNAME target is empty or too long.
43    #[error("CNAME target length invalid for label {0}")]
44    InvalidCnameTarget(String),
45
46    /// A PeerId string could not be parsed into a valid libp2p PeerId.
47    #[error("Invalid PeerId string: {0}")]
48    InvalidPeerId(String),
49
50    /// A KID string does not start with the required `did:kin:` prefix.
51    #[error("Invalid KID string (missing prefix): {0}")]
52    InvalidKid(String),
53
54    /// An IPFS CID string is invalid.
55    #[error("Invalid IPFS CID string: {0}")]
56    InvalidIpfsCid(String),
57}
58
59impl PartialEq for DnsError {
60    fn eq(&self, other: &Self) -> bool {
61        match (self, other) {
62            (Self::NestedTooDeeply, Self::NestedTooDeeply) => true,
63            (Self::ParseError(a), Self::ParseError(b)) => a.to_string() == b.to_string(),
64            (Self::TooManyRecords, Self::TooManyRecords) => true,
65            (Self::InvalidLabelLength(a), Self::InvalidLabelLength(b)) => a == b,
66            (Self::InvalidLabelCharacters(a), Self::InvalidLabelCharacters(b)) => a == b,
67            (Self::InvalidCnameConfiguration(a), Self::InvalidCnameConfiguration(b)) => a == b,
68            (Self::TxtRecordTooLong(a), Self::TxtRecordTooLong(b)) => a == b,
69            (Self::InvalidCnameTarget(a), Self::InvalidCnameTarget(b)) => a == b,
70            (Self::InvalidPeerId(a), Self::InvalidPeerId(b)) => a == b,
71            (Self::InvalidKid(a), Self::InvalidKid(b)) => a == b,
72            (Self::InvalidIpfsCid(a), Self::InvalidIpfsCid(b)) => a == b,
73            _ => false,
74        }
75    }
76}
77
78impl Eq for DnsError {}
79
80impl DnsError {
81    /// Stable protocol error code.
82    pub fn code(&self) -> &'static str {
83        match self {
84            Self::NestedTooDeeply => "KIN-DNS-001",
85            Self::ParseError(_) => "KIN-DNS-002",
86            Self::TooManyRecords => "KIN-DNS-003",
87            Self::InvalidLabelLength(_) => "KIN-DNS-004",
88            Self::InvalidLabelCharacters(_) => "KIN-DNS-005",
89            Self::InvalidCnameConfiguration(_) => "KIN-DNS-006",
90            Self::TxtRecordTooLong(_) => "KIN-DNS-007",
91            Self::InvalidCnameTarget(_) => "KIN-DNS-008",
92            Self::InvalidPeerId(_) => "KIN-DNS-009",
93            Self::InvalidKid(_) => "KIN-DNS-010",
94            Self::InvalidIpfsCid(_) => "KIN-DNS-011",
95        }
96    }
97
98    /// RFC 7807 type URI for this error.
99    pub fn error_type_uri(&self) -> String {
100        format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
101    }
102
103    /// Severity level for logging and monitoring.
104    pub fn severity(&self) -> Severity {
105        // DNS validation failures are usually bad requests (Warning level from the node's perspective).
106        Severity::Warning
107    }
108
109    /// Whether the client should offer a retry action.
110    pub fn is_retryable(&self) -> bool {
111        false // These are all deterministic validation failures
112    }
113
114    /// Returns the user-facing message.
115    pub fn user_message(&self) -> String {
116        match self {
117            Self::NestedTooDeeply => {
118                "The DNS zone contains data that is nested too deeply.".to_string()
119            }
120            Self::ParseError(_) => "Failed to parse the DNS zone data.".to_string(),
121            Self::TooManyRecords => {
122                "The DNS zone contains too many records (maximum 50).".to_string()
123            }
124            Self::InvalidLabelLength(_) => "A DNS record label has an invalid length.".to_string(),
125            Self::InvalidLabelCharacters(_) => {
126                "A DNS record label contains invalid characters.".to_string()
127            }
128            Self::InvalidCnameConfiguration(_) => {
129                "A CNAME record must be the only record for its label.".to_string()
130            }
131            Self::TxtRecordTooLong(_) => {
132                "A TXT record is too long (maximum 255 bytes).".to_string()
133            }
134            Self::InvalidCnameTarget(_) => "A CNAME target is invalid or too long.".to_string(),
135            Self::InvalidPeerId(_) => "A PeerId string is invalid.".to_string(),
136            Self::InvalidKid(_) => {
137                "A KID string is invalid or missing the 'did:kin:' prefix.".to_string()
138            }
139            Self::InvalidIpfsCid(_) => "An IPFS CID string is invalid.".to_string(),
140        }
141    }
142}