Skip to main content

ic_query/subnet_catalog/
error.rs

1use crate::subnet_catalog::RoutingRange;
2use thiserror::Error as ThisError;
3
4///
5/// CatalogError
6///
7/// Errors returned while parsing, validating, or resolving a subnet catalog.
8///
9
10#[derive(Debug, ThisError)]
11pub enum CatalogError {
12    /// Catalog JSON could not be encoded or decoded.
13    #[error(transparent)]
14    Json(#[from] serde_json::Error),
15
16    /// The catalog uses a schema other than the current version-1 contract.
17    #[error("unsupported subnet catalog schema version {found}; supported version is {supported}")]
18    UnsupportedSchemaVersion {
19        /// Schema identifier found in the input.
20        found: u32,
21        /// Current supported schema identifier.
22        supported: u32,
23    },
24
25    /// Catalog network identity does not match the required authority.
26    #[error("subnet catalog network is {actual:?}; expected {expected:?}")]
27    NetworkMismatch {
28        /// Required network identity.
29        expected: String,
30        /// Catalog network identity.
31        actual: String,
32    },
33
34    /// Catalog Registry canister identity does not match mainnet authority.
35    #[error("subnet catalog Registry canister is {actual}; expected {expected}")]
36    RegistryCanisterMismatch {
37        /// Required Registry canister principal.
38        expected: String,
39        /// Catalog Registry canister principal.
40        actual: String,
41    },
42
43    /// Registry version zero cannot identify an authority snapshot.
44    #[error("subnet catalog Registry version must be greater than zero")]
45    InvalidRegistryVersion,
46
47    /// The catalog contains no Subnet records.
48    #[error("subnet catalog must contain at least one subnet")]
49    EmptySubnets,
50
51    /// The catalog contains no routing authority ranges.
52    #[error("subnet catalog must contain at least one routing range")]
53    EmptyRoutingRanges,
54
55    /// A catalog identity or routing boundary is not a valid IC principal.
56    #[error("invalid principal in {field}: {value}: {reason}")]
57    InvalidPrincipal {
58        /// Catalog field containing the invalid identity.
59        field: &'static str,
60        /// Rejected principal text.
61        value: String,
62        /// Principal parsing diagnostic.
63        reason: String,
64    },
65
66    /// Multiple Subnet rows claim the same principal.
67    #[error("duplicate subnet principal in catalog: {subnet_principal}")]
68    DuplicateSubnet {
69        /// Repeated Subnet principal.
70        subnet_principal: String,
71    },
72
73    /// Subnet rows are not strictly ordered by canonical principal text.
74    #[error("noncanonical subnet order: {previous} must sort before {current}")]
75    NonCanonicalSubnetOrder {
76        /// Previous Subnet principal.
77        previous: String,
78        /// Current Subnet principal.
79        current: String,
80    },
81
82    /// A routing range points to a Subnet absent from the catalog.
83    #[error("routing range references unknown subnet: {subnet_principal}")]
84    UnknownRoutingSubnet {
85        /// Target Subnet principal missing its record.
86        subnet_principal: String,
87    },
88
89    /// A routing range's principal-byte boundaries are reversed.
90    #[error(
91        "invalid routing range for {subnet_principal}: start {start_canister_id} sorts after end {end_canister_id}"
92    )]
93    InvalidRoutingRange {
94        /// Subnet to which this range routes.
95        subnet_principal: String,
96        /// Inclusive start principal of the rejected range.
97        start_canister_id: String,
98        /// Inclusive end principal of the rejected range.
99        end_canister_id: String,
100    },
101
102    /// Two inclusive routing ranges cover a common principal.
103    #[error("overlapping routing ranges: {first} overlaps {second}")]
104    OverlappingRoutingRanges {
105        /// Earlier range in canonical routing order.
106        first: Box<RoutingRange>,
107        /// Later range that overlaps the earlier range.
108        second: Box<RoutingRange>,
109    },
110
111    /// Routing ranges are not strictly ordered by principal bytes and target.
112    #[error("noncanonical routing range order: {previous} must sort before {current}")]
113    NonCanonicalRoutingOrder {
114        /// Previous routing range.
115        previous: Box<RoutingRange>,
116        /// Current routing range.
117        current: Box<RoutingRange>,
118    },
119
120    /// Derived Subnet kind contradicts its raw Registry numeric code.
121    #[error(
122        "subnet {subnet_principal} kind {actual} contradicts Registry subnet_type={registry_subnet_type}; expected {expected}"
123    )]
124    SubnetKindMismatch {
125        /// Subnet principal.
126        subnet_principal: String,
127        /// Raw Registry numeric discriminant.
128        registry_subnet_type: i32,
129        /// Kind required by the raw discriminant.
130        expected: String,
131        /// Kind supplied by the raw catalog.
132        actual: String,
133    },
134
135    /// Charging default contradicts the raw Registry Subnet kind.
136    #[error(
137        "subnet {subnet_principal} charges_apply_by_default={actual} contradicts expected {expected}"
138    )]
139    ChargingPolicyMismatch {
140        /// Subnet principal.
141        subnet_principal: String,
142        /// Charging default required by the raw kind.
143        expected: bool,
144        /// Charging default supplied by the raw catalog.
145        actual: bool,
146    },
147
148    /// Classification metadata contradicts its policy or source.
149    #[error("subnet {subnet_principal} classification field {field} is invalid: {reason}")]
150    ClassificationMismatch {
151        /// Subnet principal.
152        subnet_principal: String,
153        /// Invalid classification field.
154        field: &'static str,
155        /// Deterministic mismatch reason.
156        reason: String,
157    },
158
159    /// A provenance timestamp is not valid canonical UTC text.
160    #[error("invalid catalog timestamp in {field}: {value:?}")]
161    InvalidTimestamp {
162        /// Timestamp field.
163        field: &'static str,
164        /// Invalid timestamp text.
165        value: String,
166    },
167
168    /// A provenance timestamp is beyond the caller's future-skew policy.
169    #[error(
170        "catalog timestamp in {field} is in the future: {value}; latest accepted unix time is {latest_allowed_unix_secs}"
171    )]
172    FutureTimestamp {
173        /// Timestamp field.
174        field: &'static str,
175        /// Future timestamp text.
176        value: String,
177        /// Latest accepted Unix time.
178        latest_allowed_unix_secs: u64,
179    },
180
181    /// A source endpoint is not a clean credential-free HTTP(S) base URL.
182    #[error("invalid catalog source endpoint {endpoint:?}: {reason}")]
183    InvalidSourceEndpoint {
184        /// Invalid endpoint.
185        endpoint: String,
186        /// Endpoint validation reason.
187        reason: String,
188    },
189
190    /// Provenance evidence is internally inconsistent.
191    #[error("invalid catalog provenance field {field}: {reason}")]
192    InvalidProvenance {
193        /// Invalid provenance field.
194        field: &'static str,
195        /// Deterministic validation reason.
196        reason: String,
197    },
198
199    /// Serialized raw evidence cannot establish the claimed assurance level.
200    #[error(
201        "unsupported catalog assurance {assurance} for raw validation; certified authority requires a sealed authenticated Registry archive"
202    )]
203    UnsupportedAssurance {
204        /// Unsupported assurance label.
205        assurance: String,
206    },
207
208    /// Classification policy schema is not supported by this build.
209    #[error("classification policy schema {found} is unsupported; expected {supported}")]
210    ClassificationPolicyVersionMismatch {
211        /// Catalog policy schema.
212        found: u32,
213        /// Supported policy schema.
214        supported: u32,
215    },
216
217    /// Classification policy digest does not match this build.
218    #[error("classification policy digest mismatch: found {actual}; expected {expected}")]
219    ClassificationPolicyDigestMismatch {
220        /// Expected policy digest.
221        expected: String,
222        /// Catalog policy digest.
223        actual: String,
224    },
225
226    /// Resolver implementation identity does not match this build.
227    #[error(
228        "resolver policy mismatch: version {actual_version} backend {actual_backend:?}; expected version {expected_version} backend {expected_backend:?}"
229    )]
230    ResolverPolicyMismatch {
231        /// Expected resolver schema.
232        expected_version: u32,
233        /// Catalog resolver schema.
234        actual_version: u32,
235        /// Expected resolver backend.
236        expected_backend: String,
237        /// Catalog resolver backend.
238        actual_backend: String,
239    },
240
241    /// Catalog digest is not exactly 32 lowercase hexadecimal bytes.
242    #[error("invalid catalog digest {value:?}; expected 64 lowercase hexadecimal characters")]
243    InvalidCatalogDigest {
244        /// Invalid digest text.
245        value: String,
246    },
247
248    /// Agreement digest is absent or not exactly 32 lowercase hexadecimal bytes.
249    #[error(
250        "invalid catalog agreement digest {value:?}; expected 64 lowercase hexadecimal characters"
251    )]
252    InvalidAgreementDigest {
253        /// Invalid digest text.
254        value: String,
255    },
256
257    /// Stored agreement digest does not match the canonical Registry payload.
258    #[error("catalog agreement digest mismatch: found {actual}; expected {expected}")]
259    AgreementDigestMismatch {
260        /// Recomputed agreement digest.
261        expected: String,
262        /// Stored agreement digest.
263        actual: String,
264    },
265
266    /// Stored catalog digest does not match the canonical authority payload.
267    #[error("catalog digest mismatch: found {actual}; expected {expected}")]
268    CatalogDigestMismatch {
269        /// Recomputed digest.
270        expected: String,
271        /// Stored digest.
272        actual: String,
273    },
274
275    /// An exact Subnet lookup did not match a catalog row.
276    #[error("subnet principal {subnet_principal} was not found in the cached catalog")]
277    UnknownSubnet {
278        /// Requested Subnet principal.
279        subnet_principal: String,
280    },
281
282    /// A Subnet principal prefix has no catalog match.
283    #[error("principal prefix {prefix:?} did not match cached subnet principals")]
284    PrincipalPrefixNotFound {
285        /// Requested principal prefix.
286        prefix: String,
287    },
288
289    /// A Subnet principal prefix matches more than one catalog row.
290    #[error("principal prefix {prefix:?} is ambiguous; matches: {matches:?}")]
291    AmbiguousPrincipalPrefix {
292        /// Requested principal prefix.
293        prefix: String,
294        /// Canonical Subnet principals matching the prefix.
295        matches: Vec<String>,
296    },
297
298    /// No routing range covers the requested canister at this Registry version.
299    #[error(
300        "canister principal {canister_principal} was not covered by cached routing ranges at registry_version={registry_version}, catalog_schema_version={catalog_schema_version}"
301    )]
302    RouteNotFound {
303        /// Canonical canister principal whose route was requested.
304        canister_principal: String,
305        /// Registry version represented by the catalog.
306        registry_version: u64,
307        /// Schema identifier retained by the catalog.
308        catalog_schema_version: u32,
309    },
310}