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    /// Classification policy schema is not supported by this build.
200    #[error("classification policy schema {found} is unsupported; expected {supported}")]
201    ClassificationPolicyVersionMismatch {
202        /// Catalog policy schema.
203        found: u32,
204        /// Supported policy schema.
205        supported: u32,
206    },
207
208    /// Classification policy digest does not match this build.
209    #[error("classification policy digest mismatch: found {actual}; expected {expected}")]
210    ClassificationPolicyDigestMismatch {
211        /// Expected policy digest.
212        expected: String,
213        /// Catalog policy digest.
214        actual: String,
215    },
216
217    /// Resolver implementation identity does not match this build.
218    #[error(
219        "resolver policy mismatch: version {actual_version} backend {actual_backend:?}; expected version {expected_version} backend {expected_backend:?}"
220    )]
221    ResolverPolicyMismatch {
222        /// Expected resolver schema.
223        expected_version: u32,
224        /// Catalog resolver schema.
225        actual_version: u32,
226        /// Expected resolver backend.
227        expected_backend: String,
228        /// Catalog resolver backend.
229        actual_backend: String,
230    },
231
232    /// Catalog digest is not exactly 32 lowercase hexadecimal bytes.
233    #[error("invalid catalog digest {value:?}; expected 64 lowercase hexadecimal characters")]
234    InvalidCatalogDigest {
235        /// Invalid digest text.
236        value: String,
237    },
238
239    /// Agreement digest is absent or not exactly 32 lowercase hexadecimal bytes.
240    #[error(
241        "invalid catalog agreement digest {value:?}; expected 64 lowercase hexadecimal characters"
242    )]
243    InvalidAgreementDigest {
244        /// Invalid digest text.
245        value: String,
246    },
247
248    /// Stored agreement digest does not match the canonical Registry payload.
249    #[error("catalog agreement digest mismatch: found {actual}; expected {expected}")]
250    AgreementDigestMismatch {
251        /// Recomputed agreement digest.
252        expected: String,
253        /// Stored agreement digest.
254        actual: String,
255    },
256
257    /// Stored catalog digest does not match the canonical authority payload.
258    #[error("catalog digest mismatch: found {actual}; expected {expected}")]
259    CatalogDigestMismatch {
260        /// Recomputed digest.
261        expected: String,
262        /// Stored digest.
263        actual: String,
264    },
265
266    /// An exact Subnet lookup did not match a catalog row.
267    #[error("subnet principal {subnet_principal} was not found in the cached catalog")]
268    UnknownSubnet {
269        /// Requested Subnet principal.
270        subnet_principal: String,
271    },
272
273    /// A Subnet principal prefix has no catalog match.
274    #[error("principal prefix {prefix:?} did not match cached subnet principals")]
275    PrincipalPrefixNotFound {
276        /// Requested principal prefix.
277        prefix: String,
278    },
279
280    /// A Subnet principal prefix matches more than one catalog row.
281    #[error("principal prefix {prefix:?} is ambiguous; matches: {matches:?}")]
282    AmbiguousPrincipalPrefix {
283        /// Requested principal prefix.
284        prefix: String,
285        /// Canonical Subnet principals matching the prefix.
286        matches: Vec<String>,
287    },
288
289    /// No routing range covers the requested canister at this Registry version.
290    #[error(
291        "canister principal {canister_principal} was not covered by cached routing ranges at registry_version={registry_version}, catalog_schema_version={catalog_schema_version}"
292    )]
293    RouteNotFound {
294        /// Canonical canister principal whose route was requested.
295        canister_principal: String,
296        /// Registry version represented by the catalog.
297        registry_version: u64,
298        /// Schema identifier retained by the catalog.
299        catalog_schema_version: u32,
300    },
301}