canic_subnet_catalog/
lib.rs1pub mod model;
4pub mod resolver;
5
6use candid::Principal;
7pub use model::{
8 ClassificationSource, GeographicScope, RoutingRange, SubnetCatalog, SubnetInfo, SubnetKind,
9 SubnetSpecialization,
10};
11pub use resolver::{ResolveAs, ResolvedSubnet, ResolvedSubnetSubject};
12use thiserror::Error as ThisError;
13
14pub const CATALOG_SCHEMA_VERSION: u32 = 1;
15pub const MAINNET_NETWORK: &str = "ic";
16pub const MAINNET_REGISTRY_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai";
17
18#[derive(Debug, ThisError)]
22pub enum CatalogError {
23 #[error(transparent)]
24 Json(#[from] serde_json::Error),
25
26 #[error("unsupported subnet catalog schema version {found}; supported version is {supported}")]
27 UnsupportedSchemaVersion { found: u32, supported: u32 },
28
29 #[error("subnet catalog must contain at least one subnet")]
30 EmptySubnets,
31
32 #[error("subnet catalog must contain at least one routing range")]
33 EmptyRoutingRanges,
34
35 #[error("invalid principal in {field}: {value}: {reason}")]
36 InvalidPrincipal {
37 field: &'static str,
38 value: String,
39 reason: String,
40 },
41
42 #[error("duplicate subnet principal in catalog: {subnet_principal}")]
43 DuplicateSubnet { subnet_principal: String },
44
45 #[error("routing range references unknown subnet: {subnet_principal}")]
46 UnknownRoutingSubnet { subnet_principal: String },
47
48 #[error(
49 "invalid routing range for {subnet_principal}: start {start_canister_id} sorts after end {end_canister_id}"
50 )]
51 InvalidRoutingRange {
52 subnet_principal: String,
53 start_canister_id: String,
54 end_canister_id: String,
55 },
56
57 #[error("subnet principal {subnet_principal} was not found in the cached catalog")]
58 UnknownSubnet { subnet_principal: String },
59
60 #[error("principal prefix {prefix:?} did not match cached subnet principals")]
61 PrincipalPrefixNotFound { prefix: String },
62
63 #[error("principal prefix {prefix:?} is ambiguous; matches: {matches:?}")]
64 AmbiguousPrincipalPrefix {
65 prefix: String,
66 matches: Vec<String>,
67 },
68
69 #[error(
70 "canister principal {canister_principal} was not covered by cached routing ranges at registry_version={registry_version}, catalog_schema_version={catalog_schema_version}"
71 )]
72 RouteNotFound {
73 canister_principal: String,
74 registry_version: u64,
75 catalog_schema_version: u32,
76 },
77}
78
79pub fn parse_catalog_json(data: &str) -> Result<SubnetCatalog, CatalogError> {
81 let catalog = serde_json::from_str::<SubnetCatalog>(data)?;
82 catalog.validate()?;
83 Ok(catalog)
84}
85
86pub fn catalog_to_pretty_json(catalog: &SubnetCatalog) -> Result<String, CatalogError> {
88 Ok(serde_json::to_string_pretty(catalog)?)
89}
90
91pub fn canonical_principal_text(value: &str) -> Result<String, CatalogError> {
93 Ok(parse_principal(value, "principal")?.to_text())
94}
95
96pub(crate) fn parse_principal(value: &str, field: &'static str) -> Result<Principal, CatalogError> {
97 Principal::from_text(value).map_err(|err| CatalogError::InvalidPrincipal {
98 field,
99 value: value.to_string(),
100 reason: err.to_string(),
101 })
102}
103
104pub(crate) fn principal_bytes(value: &str, field: &'static str) -> Result<Vec<u8>, CatalogError> {
105 Ok(parse_principal(value, field)?.as_slice().to_vec())
106}
107
108#[cfg(test)]
109mod tests;