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/// IcDashboardReportProvenance
185///
186/// Shared off-chain provenance and authority guarantees for Dashboard reports.
187///
188
189#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
190pub struct IcDashboardReportProvenance {
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}
208
209///
210/// IcCanisterReport
211///
212/// One live canister metadata report from the official Dashboard API.
213///
214
215#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
216pub struct IcCanisterReport {
217    /// Shared Dashboard provenance, flattened in serialized report JSON.
218    #[serde(flatten)]
219    pub provenance: IcDashboardReportProvenance,
220    /// Canonical canister principal.
221    pub canister_id: String,
222    /// Dashboard database row identifier.
223    pub dashboard_id: u64,
224    /// Raw optional Dashboard canister classification.
225    pub canister_type: Option<String>,
226    /// Raw Dashboard canister name; an empty string means no name was recorded.
227    pub name: String,
228    /// Canonical Subnet principal recorded by the Dashboard.
229    pub subnet_id: String,
230    /// Canonically ordered controller principals recorded by the Dashboard.
231    pub controllers: Vec<String>,
232    /// Raw Dashboard language label; an empty string means no language was recorded.
233    pub language: String,
234    /// Raw current module hash; an empty string means no hash was recorded.
235    pub module_hash: String,
236    /// Raw Dashboard row update timestamp.
237    pub dashboard_updated_at: String,
238    /// Number of proposal-linked upgrades when history is available.
239    pub upgrade_count: Option<usize>,
240    /// Proposal-linked upgrade history, or `None` when the Dashboard returned `null`.
241    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
242}
243
244///
245/// IcCanisterCountReport
246///
247/// One filtered canister count from the official Dashboard API.
248///
249
250#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
251pub struct IcCanisterCountReport {
252    /// Shared Dashboard provenance, flattened in serialized report JSON.
253    #[serde(flatten)]
254    pub provenance: IcDashboardReportProvenance,
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    /// Shared Dashboard provenance, flattened in serialized report JSON.
312    #[serde(flatten)]
313    pub provenance: IcDashboardReportProvenance,
314    /// Filters applied by the Dashboard.
315    pub filters: IcCanisterFilters,
316    /// Maximum rows requested from the API.
317    pub requested_limit: u16,
318    /// Number of rows returned in this report.
319    pub returned_count: usize,
320    /// Exclusive forward cursor supplied to this request.
321    pub after: Option<String>,
322    /// Exclusive backward cursor supplied to this request.
323    pub before: Option<String>,
324    /// Cursor for an explicit request for the preceding page.
325    pub previous_cursor: Option<String>,
326    /// Cursor for an explicit request for the following page.
327    pub next_cursor: Option<String>,
328    /// Canister discovery rows in Dashboard canister-id order.
329    pub rows: Vec<IcCanisterPageRow>,
330}
331
332///
333/// IcSourceRequest
334///
335/// Shared endpoint and collection provenance for IC Dashboard source calls and results.
336///
337
338#[cfg(feature = "host")]
339#[derive(Clone, Debug, Eq, PartialEq)]
340pub struct IcSourceRequest {
341    /// Dashboard API base endpoint.
342    pub endpoint: String,
343    /// Collection timestamp in UTC.
344    pub fetched_at: String,
345    /// Collector identity recorded in report provenance.
346    pub fetched_by: String,
347}
348
349#[cfg(feature = "host")]
350impl IcSourceRequest {
351    /// Construct source-call provenance.
352    #[must_use]
353    pub fn new(
354        endpoint: impl Into<String>,
355        fetched_at: impl Into<String>,
356        fetched_by: impl Into<String>,
357    ) -> Self {
358        Self {
359            endpoint: endpoint.into(),
360            fetched_at: fetched_at.into(),
361            fetched_by: fetched_by.into(),
362        }
363    }
364}
365
366///
367/// IcCanisterSourceData
368///
369/// Raw canister metadata and provenance returned by an IC Dashboard source.
370///
371
372#[cfg(feature = "host")]
373#[derive(Clone, Debug, Eq, PartialEq)]
374pub struct IcCanisterSourceData {
375    /// Source request and provenance preserved by the source.
376    pub source: IcSourceRequest,
377    /// Canister principal returned by the Dashboard.
378    pub canister_id: String,
379    /// Dashboard database row identifier.
380    pub dashboard_id: u64,
381    /// Raw optional Dashboard canister classification.
382    pub canister_type: Option<String>,
383    /// Raw Dashboard canister name.
384    pub name: String,
385    /// Subnet principal returned by the Dashboard.
386    pub subnet_id: String,
387    /// Controller principals returned by the Dashboard.
388    pub controllers: Vec<String>,
389    /// Raw Dashboard language label.
390    pub language: String,
391    /// Raw current module hash.
392    pub module_hash: String,
393    /// Raw Dashboard row update timestamp.
394    pub dashboard_updated_at: String,
395    /// Proposal-linked upgrades, or `None` when the Dashboard returned `null`.
396    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
397}
398
399///
400/// IcCanisterCountSourceData
401///
402/// Raw filtered count and provenance returned by a Dashboard source.
403///
404
405#[cfg(feature = "host")]
406#[derive(Clone, Debug, Eq, PartialEq)]
407pub struct IcCanisterCountSourceData {
408    /// Source request and provenance preserved by the source.
409    pub source: IcSourceRequest,
410    /// Filters applied by the source.
411    pub filters: IcCanisterFilters,
412    /// Number of matching Dashboard canister records.
413    pub total: u64,
414}
415
416///
417/// IcCanisterPageSourceData
418///
419/// Raw bounded canister page and provenance returned by a Dashboard source.
420///
421
422#[cfg(feature = "host")]
423#[derive(Clone, Debug, Eq, PartialEq)]
424pub struct IcCanisterPageSourceData {
425    /// Source request and provenance preserved by the source.
426    pub source: IcSourceRequest,
427    /// Filters applied by the source.
428    pub filters: IcCanisterFilters,
429    /// Maximum rows requested from the source.
430    pub requested_limit: u16,
431    /// Exclusive forward cursor supplied to the source.
432    pub after: Option<String>,
433    /// Exclusive backward cursor supplied to the source.
434    pub before: Option<String>,
435    /// Cursor for an explicit request for the preceding page.
436    pub previous_cursor: Option<String>,
437    /// Cursor for an explicit request for the following page.
438    pub next_cursor: Option<String>,
439    /// Canister discovery rows returned by the source.
440    pub rows: Vec<IcCanisterPageRow>,
441}
442
443///
444/// IcHostError
445///
446/// Typed error returned by IC Dashboard report builders and live sources.
447///
448
449#[cfg(feature = "host")]
450#[derive(Debug, ThisError)]
451pub enum IcHostError {
452    /// The synchronous adapter could not create its local async runtime.
453    #[error("failed to run IC Dashboard query: {0}")]
454    Runtime(#[from] RuntimeError),
455
456    /// A request supplied an invalid canister principal.
457    #[error("invalid {field}: {reason}")]
458    InvalidPrincipal {
459        /// Principal field being validated.
460        field: &'static str,
461        /// Principal parser diagnostic.
462        reason: String,
463    },
464
465    /// A request violates the bounded Dashboard query contract.
466    #[error("invalid {field}: {reason}")]
467    InvalidRequest {
468        /// Request field being validated.
469        field: &'static str,
470        /// Deterministic validation diagnostic.
471        reason: String,
472    },
473
474    /// The Dashboard API base endpoint is malformed or unsupported.
475    #[error("invalid IC Dashboard endpoint {endpoint}: {reason}")]
476    InvalidEndpoint {
477        /// Rejected endpoint.
478        endpoint: String,
479        /// URL validation diagnostic.
480        reason: String,
481    },
482
483    /// The HTTP client could not be constructed.
484    #[error("failed to build IC Dashboard HTTP client: {reason}")]
485    HttpClientBuild {
486        /// HTTP client construction diagnostic.
487        reason: String,
488    },
489
490    /// The live Dashboard request failed before a response was received.
491    #[error("IC Dashboard request to {url} failed: {reason}")]
492    HttpRequest {
493        /// Fully resolved request URL.
494        url: String,
495        /// HTTP transport error.
496        reason: String,
497    },
498
499    /// The Dashboard returned a non-success HTTP status.
500    #[error("IC Dashboard request to {url} returned HTTP status {status}")]
501    HttpStatus {
502        /// Fully resolved request URL.
503        url: String,
504        /// Numeric HTTP status.
505        status: u16,
506    },
507
508    /// The Dashboard response did not match the expected JSON shape.
509    #[error("failed to decode IC Dashboard response from {url}: {reason}")]
510    JsonDecode {
511        /// Fully resolved request URL.
512        url: String,
513        /// JSON response decoding error.
514        reason: String,
515    },
516
517    /// A source capability returned data that violates its public result contract.
518    #[error("invalid IC Dashboard canister source data: {reason}")]
519    InvalidSourceData {
520        /// Deterministic invariant failure.
521        reason: String,
522    },
523}