Skip to main content

ic_query/ic/
model.rs

1//! Module: ic::model
2//!
3//! Responsibility: public IC Dashboard canister requests, source data, reports, and errors.
4//! Does not own: HTTP transport, source validation, report assembly, or rendering.
5//! Boundary: preserves raw Dashboard values and explicit off-chain provenance.
6
7#[cfg(feature = "host")]
8use crate::runtime::RuntimeError;
9use serde::Serialize;
10#[cfg(feature = "host")]
11use thiserror::Error as ThisError;
12
13///
14/// IcCanisterRequest
15///
16/// Request accepted by the official Dashboard canister report builder.
17///
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct IcCanisterRequest {
21    /// Dashboard API base endpoint.
22    pub source_endpoint: String,
23    /// Collection time as Unix seconds.
24    pub now_unix_secs: u64,
25    /// Canister principal to inspect.
26    pub canister_id: String,
27}
28
29impl IcCanisterRequest {
30    /// Construct a live Dashboard canister request.
31    #[must_use]
32    pub fn new(
33        source_endpoint: impl Into<String>,
34        now_unix_secs: u64,
35        canister_id: impl Into<String>,
36    ) -> Self {
37        Self {
38            source_endpoint: source_endpoint.into(),
39            now_unix_secs,
40            canister_id: canister_id.into(),
41        }
42    }
43}
44
45///
46/// IcCanisterFilters
47///
48/// Official Dashboard filters shared by canister count and page requests.
49///
50
51#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
52pub struct IcCanisterFilters {
53    /// Select canisters according to whether the Dashboard records a name.
54    pub has_name: Option<bool>,
55    /// Select canisters assigned to this Subnet principal.
56    pub subnet_id: Option<String>,
57    /// Select canisters controlled by this principal.
58    pub controller_id: Option<String>,
59    /// Raw Dashboard language labels to include.
60    pub languages: Vec<String>,
61    /// Raw Dashboard canister classifications to include.
62    pub canister_types: Vec<String>,
63    /// Raw Dashboard text search, between two and one hundred characters.
64    pub query: Option<String>,
65}
66
67///
68/// IcCanisterCountRequest
69///
70/// Request for one bounded official Dashboard canister-count lookup.
71///
72
73#[derive(Clone, Debug, Eq, PartialEq)]
74pub struct IcCanisterCountRequest {
75    /// Dashboard API v4 base endpoint.
76    pub source_endpoint: String,
77    /// Collection time as Unix seconds.
78    pub now_unix_secs: u64,
79    /// Filters applied by the Dashboard.
80    pub filters: IcCanisterFilters,
81}
82
83impl IcCanisterCountRequest {
84    /// Construct a live Dashboard canister-count request without filters.
85    #[must_use]
86    pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
87        Self {
88            source_endpoint: source_endpoint.into(),
89            now_unix_secs,
90            filters: IcCanisterFilters::default(),
91        }
92    }
93
94    /// Set the Dashboard filters used by this request.
95    #[must_use]
96    pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
97        self.filters = filters;
98        self
99    }
100}
101
102///
103/// IcCanisterPageRequest
104///
105/// Request for one bounded official Dashboard canister page.
106///
107
108#[derive(Clone, Debug, Eq, PartialEq)]
109pub struct IcCanisterPageRequest {
110    /// Dashboard API v4 base endpoint.
111    pub source_endpoint: String,
112    /// Collection time as Unix seconds.
113    pub now_unix_secs: u64,
114    /// Filters applied by the Dashboard.
115    pub filters: IcCanisterFilters,
116    /// Maximum rows requested from the API.
117    pub limit: u16,
118    /// Exclusive forward cursor returned by an earlier page.
119    pub after: Option<String>,
120    /// Exclusive backward cursor returned by an earlier page.
121    pub before: Option<String>,
122}
123
124impl IcCanisterPageRequest {
125    /// Construct a live Dashboard page request with the default bounded limit.
126    #[must_use]
127    pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
128        Self {
129            source_endpoint: source_endpoint.into(),
130            now_unix_secs,
131            filters: IcCanisterFilters::default(),
132            limit: super::DEFAULT_IC_CANISTER_PAGE_LIMIT,
133            after: None,
134            before: None,
135        }
136    }
137
138    /// Set the Dashboard filters used by this request.
139    #[must_use]
140    pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
141        self.filters = filters;
142        self
143    }
144
145    /// Set the maximum number of returned rows.
146    #[must_use]
147    pub const fn with_limit(mut self, limit: u16) -> Self {
148        self.limit = limit;
149        self
150    }
151
152    /// Set an exclusive forward cursor.
153    #[must_use]
154    pub fn with_after(mut self, after: impl Into<String>) -> Self {
155        self.after = Some(after.into());
156        self
157    }
158
159    /// Set an exclusive backward cursor.
160    #[must_use]
161    pub fn with_before(mut self, before: impl Into<String>) -> Self {
162        self.before = Some(before.into());
163        self
164    }
165}
166
167///
168/// IcCanisterUpgrade
169///
170/// One proposal-linked canister upgrade recorded by the Dashboard API.
171///
172
173#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
174pub struct IcCanisterUpgrade {
175    /// Proposal execution time as raw Unix seconds.
176    pub executed_timestamp_seconds: u64,
177    /// Wasm module hash as raw lowercase hexadecimal text.
178    pub module_hash: String,
179    /// NNS proposal that installed this module.
180    pub proposal_id: u64,
181}
182
183///
184/// IcCanisterReport
185///
186/// One live canister metadata report from the official Dashboard API.
187///
188
189#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
190pub struct IcCanisterReport {
191    /// Report schema version.
192    pub schema_version: u32,
193    /// Network represented by the official Dashboard API.
194    pub network: String,
195    /// Authority that supplied the report fields.
196    pub authority: String,
197    /// Dashboard API base endpoint queried by the source.
198    pub source_endpoint: String,
199    /// Time this report was collected.
200    pub fetched_at: String,
201    /// Collector identity.
202    pub fetched_by: String,
203    /// Whether the API response is cryptographically certified IC state.
204    pub certified: bool,
205    /// Whether every returned value is guaranteed to describe one point in time.
206    pub point_in_time_guaranteed: bool,
207    /// Canonical canister principal.
208    pub canister_id: String,
209    /// Dashboard database row identifier.
210    pub dashboard_id: u64,
211    /// Raw optional Dashboard canister classification.
212    pub canister_type: Option<String>,
213    /// Raw Dashboard canister name; an empty string means no name was recorded.
214    pub name: String,
215    /// Canonical Subnet principal recorded by the Dashboard.
216    pub subnet_id: String,
217    /// Canonically ordered controller principals recorded by the Dashboard.
218    pub controllers: Vec<String>,
219    /// Raw Dashboard language label; an empty string means no language was recorded.
220    pub language: String,
221    /// Raw current module hash; an empty string means no hash was recorded.
222    pub module_hash: String,
223    /// Raw Dashboard row update timestamp.
224    pub dashboard_updated_at: String,
225    /// Number of proposal-linked upgrades when history is available.
226    pub upgrade_count: Option<usize>,
227    /// Proposal-linked upgrade history, or `None` when the Dashboard returned `null`.
228    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
229}
230
231///
232/// IcCanisterCountReport
233///
234/// One filtered canister count from the official Dashboard API.
235///
236
237#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
238pub struct IcCanisterCountReport {
239    /// Report schema version.
240    pub schema_version: u32,
241    /// Network represented by the official Dashboard API.
242    pub network: String,
243    /// Authority that supplied the report fields.
244    pub authority: String,
245    /// Dashboard API v4 base endpoint queried by the source.
246    pub source_endpoint: String,
247    /// Time this report was collected.
248    pub fetched_at: String,
249    /// Collector identity.
250    pub fetched_by: String,
251    /// Whether the API response is cryptographically certified IC state.
252    pub certified: bool,
253    /// Whether every returned value is guaranteed to describe one point in time.
254    pub point_in_time_guaranteed: bool,
255    /// Filters applied by the Dashboard.
256    pub filters: IcCanisterFilters,
257    /// Number of matching Dashboard canister records.
258    pub total: u64,
259}
260
261///
262/// IcCanisterPageController
263///
264/// One controller entry returned by the Dashboard canister collection API.
265///
266
267#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
268pub struct IcCanisterPageController {
269    /// Canonical controller principal.
270    pub principal_id: String,
271    /// Raw optional Dashboard metadata associated with the controller.
272    pub raw_metadata: Option<String>,
273}
274
275///
276/// IcCanisterPageRow
277///
278/// One discovery row from a bounded Dashboard canister page.
279///
280
281#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
282pub struct IcCanisterPageRow {
283    /// Canonical canister principal.
284    pub canister_id: String,
285    /// Dashboard database row identifier.
286    pub dashboard_id: u64,
287    /// Raw optional Dashboard canister classification.
288    pub canister_type: Option<String>,
289    /// Raw Dashboard canister name.
290    pub name: String,
291    /// Canonical Subnet principal recorded by the Dashboard.
292    pub subnet_id: String,
293    /// Canonically ordered controller entries recorded by the Dashboard.
294    pub controllers: Vec<IcCanisterPageController>,
295    /// Raw Dashboard language label.
296    pub language: String,
297    /// Raw current module hash.
298    pub module_hash: String,
299    /// Raw Dashboard row update timestamp.
300    pub dashboard_updated_at: String,
301}
302
303///
304/// IcCanisterPageReport
305///
306/// One explicitly bounded page from the official Dashboard canister collection.
307///
308
309#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
310pub struct IcCanisterPageReport {
311    /// Report schema version.
312    pub schema_version: u32,
313    /// Network represented by the official Dashboard API.
314    pub network: String,
315    /// Authority that supplied the report fields.
316    pub authority: String,
317    /// Dashboard API v4 base endpoint queried by the source.
318    pub source_endpoint: String,
319    /// Time this report was collected.
320    pub fetched_at: String,
321    /// Collector identity.
322    pub fetched_by: String,
323    /// Whether the API response is cryptographically certified IC state.
324    pub certified: bool,
325    /// Whether every returned value is guaranteed to describe one point in time.
326    pub point_in_time_guaranteed: bool,
327    /// Filters applied by the Dashboard.
328    pub filters: IcCanisterFilters,
329    /// Maximum rows requested from the API.
330    pub requested_limit: u16,
331    /// Number of rows returned in this report.
332    pub returned_count: usize,
333    /// Exclusive forward cursor supplied to this request.
334    pub after: Option<String>,
335    /// Exclusive backward cursor supplied to this request.
336    pub before: Option<String>,
337    /// Cursor for an explicit request for the preceding page.
338    pub previous_cursor: Option<String>,
339    /// Cursor for an explicit request for the following page.
340    pub next_cursor: Option<String>,
341    /// Canister discovery rows in Dashboard canister-id order.
342    pub rows: Vec<IcCanisterPageRow>,
343}
344
345///
346/// IcSourceRequest
347///
348/// Shared endpoint and collection provenance for IC Dashboard source calls.
349///
350
351#[cfg(feature = "host")]
352#[derive(Clone, Debug, Eq, PartialEq)]
353pub struct IcSourceRequest {
354    /// Dashboard API base endpoint.
355    pub endpoint: String,
356    /// Collection timestamp in UTC.
357    pub fetched_at: String,
358    /// Collector identity recorded in report provenance.
359    pub fetched_by: String,
360}
361
362#[cfg(feature = "host")]
363impl IcSourceRequest {
364    /// Construct source-call provenance.
365    #[must_use]
366    pub fn new(
367        endpoint: impl Into<String>,
368        fetched_at: impl Into<String>,
369        fetched_by: impl Into<String>,
370    ) -> Self {
371        Self {
372            endpoint: endpoint.into(),
373            fetched_at: fetched_at.into(),
374            fetched_by: fetched_by.into(),
375        }
376    }
377}
378
379///
380/// IcCanisterSourceData
381///
382/// Raw canister metadata and provenance returned by an IC Dashboard source.
383///
384
385#[cfg(feature = "host")]
386#[derive(Clone, Debug, Eq, PartialEq)]
387pub struct IcCanisterSourceData {
388    /// Dashboard API base endpoint used by the source.
389    pub source_endpoint: String,
390    /// Collection timestamp supplied in the source request.
391    pub fetched_at: String,
392    /// Collector identity supplied in the source request.
393    pub fetched_by: String,
394    /// Canister principal returned by the Dashboard.
395    pub canister_id: String,
396    /// Dashboard database row identifier.
397    pub dashboard_id: u64,
398    /// Raw optional Dashboard canister classification.
399    pub canister_type: Option<String>,
400    /// Raw Dashboard canister name.
401    pub name: String,
402    /// Subnet principal returned by the Dashboard.
403    pub subnet_id: String,
404    /// Controller principals returned by the Dashboard.
405    pub controllers: Vec<String>,
406    /// Raw Dashboard language label.
407    pub language: String,
408    /// Raw current module hash.
409    pub module_hash: String,
410    /// Raw Dashboard row update timestamp.
411    pub dashboard_updated_at: String,
412    /// Proposal-linked upgrades, or `None` when the Dashboard returned `null`.
413    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
414}
415
416///
417/// IcCanisterCountSourceData
418///
419/// Raw filtered count and provenance returned by a Dashboard source.
420///
421
422#[cfg(feature = "host")]
423#[derive(Clone, Debug, Eq, PartialEq)]
424pub struct IcCanisterCountSourceData {
425    /// Dashboard API v4 base endpoint used by the source.
426    pub source_endpoint: String,
427    /// Collection timestamp supplied in the source request.
428    pub fetched_at: String,
429    /// Collector identity supplied in the source request.
430    pub fetched_by: String,
431    /// Filters applied by the source.
432    pub filters: IcCanisterFilters,
433    /// Number of matching Dashboard canister records.
434    pub total: u64,
435}
436
437///
438/// IcCanisterPageSourceData
439///
440/// Raw bounded canister page and provenance returned by a Dashboard source.
441///
442
443#[cfg(feature = "host")]
444#[derive(Clone, Debug, Eq, PartialEq)]
445pub struct IcCanisterPageSourceData {
446    /// Dashboard API v4 base endpoint used by the source.
447    pub source_endpoint: String,
448    /// Collection timestamp supplied in the source request.
449    pub fetched_at: String,
450    /// Collector identity supplied in the source request.
451    pub fetched_by: String,
452    /// Filters applied by the source.
453    pub filters: IcCanisterFilters,
454    /// Maximum rows requested from the source.
455    pub requested_limit: u16,
456    /// Exclusive forward cursor supplied to the source.
457    pub after: Option<String>,
458    /// Exclusive backward cursor supplied to the source.
459    pub before: Option<String>,
460    /// Cursor for an explicit request for the preceding page.
461    pub previous_cursor: Option<String>,
462    /// Cursor for an explicit request for the following page.
463    pub next_cursor: Option<String>,
464    /// Canister discovery rows returned by the source.
465    pub rows: Vec<IcCanisterPageRow>,
466}
467
468///
469/// IcHostError
470///
471/// Typed error returned by IC Dashboard report builders and live sources.
472///
473
474#[cfg(feature = "host")]
475#[derive(Debug, ThisError)]
476pub enum IcHostError {
477    /// The synchronous adapter could not create its local async runtime.
478    #[error("failed to run IC Dashboard query: {0}")]
479    Runtime(#[from] RuntimeError),
480
481    /// A request supplied an invalid canister principal.
482    #[error("invalid {field}: {reason}")]
483    InvalidPrincipal {
484        /// Principal field being validated.
485        field: &'static str,
486        /// Principal parser diagnostic.
487        reason: String,
488    },
489
490    /// A request violates the bounded Dashboard query contract.
491    #[error("invalid {field}: {reason}")]
492    InvalidRequest {
493        /// Request field being validated.
494        field: &'static str,
495        /// Deterministic validation diagnostic.
496        reason: String,
497    },
498
499    /// The Dashboard API base endpoint is malformed or unsupported.
500    #[error("invalid IC Dashboard endpoint {endpoint}: {reason}")]
501    InvalidEndpoint {
502        /// Rejected endpoint.
503        endpoint: String,
504        /// URL validation diagnostic.
505        reason: String,
506    },
507
508    /// The HTTP client could not be constructed.
509    #[error("failed to build IC Dashboard HTTP client: {reason}")]
510    HttpClientBuild {
511        /// HTTP client construction diagnostic.
512        reason: String,
513    },
514
515    /// The live Dashboard request failed before a response was received.
516    #[error("IC Dashboard request to {url} failed: {reason}")]
517    HttpRequest {
518        /// Fully resolved request URL.
519        url: String,
520        /// HTTP transport error.
521        reason: String,
522    },
523
524    /// The Dashboard returned a non-success HTTP status.
525    #[error("IC Dashboard request to {url} returned HTTP status {status}")]
526    HttpStatus {
527        /// Fully resolved request URL.
528        url: String,
529        /// Numeric HTTP status.
530        status: u16,
531    },
532
533    /// The Dashboard response did not match the expected JSON shape.
534    #[error("failed to decode IC Dashboard response from {url}: {reason}")]
535    JsonDecode {
536        /// Fully resolved request URL.
537        url: String,
538        /// JSON response decoding error.
539        reason: String,
540    },
541
542    /// A source capability returned data that violates its public result contract.
543    #[error("invalid IC Dashboard canister source data: {reason}")]
544    InvalidSourceData {
545        /// Deterministic invariant failure.
546        reason: String,
547    },
548}