Skip to main content

ic_query/ic/
model.rs

1//! Module: ic::model
2//!
3//! Responsibility: public IC Dashboard 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;
10use std::{fmt, str::FromStr};
11#[cfg(feature = "host")]
12use thiserror::Error as ThisError;
13
14///
15/// IcMetricKind
16///
17/// One bounded network metric exposed by the official Dashboard Metrics API.
18///
19
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
21#[serde(rename_all = "kebab-case")]
22pub enum IcMetricKind {
23    /// Network instruction execution rate.
24    InstructionRate,
25    /// Network message execution rate.
26    MessageExecutionRate,
27    /// Network cycle burn rate.
28    CycleBurnRate,
29    /// Network block ingestion rate.
30    BlockRate,
31    /// Total and currently up node counts.
32    IcNodeCount,
33    /// Total Subnet count.
34    IcSubnetTotal,
35    /// Running and stopped canister counts.
36    RegisteredCanistersCount,
37    /// Total estimated IC energy-consumption rate in kWh.
38    TotalIcEnergyConsumptionRateKwh,
39    /// Active boundary-node count.
40    BoundaryNodesCount,
41}
42
43impl IcMetricKind {
44    /// Return every metric supported by the bounded report adapter.
45    #[must_use]
46    pub const fn all() -> [Self; 9] {
47        [
48            Self::InstructionRate,
49            Self::MessageExecutionRate,
50            Self::CycleBurnRate,
51            Self::BlockRate,
52            Self::IcNodeCount,
53            Self::IcSubnetTotal,
54            Self::RegisteredCanistersCount,
55            Self::TotalIcEnergyConsumptionRateKwh,
56            Self::BoundaryNodesCount,
57        ]
58    }
59
60    /// Return the official Dashboard Metrics API path name.
61    #[must_use]
62    pub const fn as_str(self) -> &'static str {
63        match self {
64            Self::InstructionRate => "instruction-rate",
65            Self::MessageExecutionRate => "message-execution-rate",
66            Self::CycleBurnRate => "cycle-burn-rate",
67            Self::BlockRate => "block-rate",
68            Self::IcNodeCount => "ic-node-count",
69            Self::IcSubnetTotal => "ic-subnet-total",
70            Self::RegisteredCanistersCount => "registered-canisters-count",
71            Self::TotalIcEnergyConsumptionRateKwh => "total-ic-energy-consumption-rate-kwh",
72            Self::BoundaryNodesCount => "boundary-nodes-count",
73        }
74    }
75
76    #[cfg(feature = "host")]
77    pub(crate) const fn series_names(self) -> &'static [&'static str] {
78        match self {
79            Self::InstructionRate => &["instruction_rate"],
80            Self::MessageExecutionRate => &["message_execution_rate"],
81            Self::CycleBurnRate => &["cycle_burn_rate"],
82            Self::BlockRate => &["block_rate"],
83            Self::IcNodeCount => &["total_nodes", "up_nodes"],
84            Self::IcSubnetTotal => &["ic_subnet_total"],
85            Self::RegisteredCanistersCount => &["running_canisters", "stopped_canisters"],
86            Self::TotalIcEnergyConsumptionRateKwh => &["energy_consumption_rate"],
87            Self::BoundaryNodesCount => &["boundary_nodes_count"],
88        }
89    }
90}
91
92impl fmt::Display for IcMetricKind {
93    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94        formatter.write_str(self.as_str())
95    }
96}
97
98impl FromStr for IcMetricKind {
99    type Err = String;
100
101    fn from_str(value: &str) -> Result<Self, Self::Err> {
102        Self::all()
103            .into_iter()
104            .find(|metric| metric.as_str() == value)
105            .ok_or_else(|| format!("unsupported IC Dashboard metric {value:?}"))
106    }
107}
108
109///
110/// IcMetricQuery
111///
112/// One explicitly bounded official Dashboard metric time-series query.
113///
114
115#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
116pub struct IcMetricQuery {
117    /// Official Dashboard metric to retrieve.
118    pub metric: IcMetricKind,
119    /// Inclusive query start as Unix seconds.
120    pub start_unix_secs: u64,
121    /// Inclusive query end as Unix seconds.
122    pub end_unix_secs: u64,
123    /// Requested observation interval in seconds.
124    pub step_secs: u32,
125}
126
127impl IcMetricQuery {
128    /// Construct one explicit metric query window.
129    #[must_use]
130    pub const fn new(
131        metric: IcMetricKind,
132        start_unix_secs: u64,
133        end_unix_secs: u64,
134        step_secs: u32,
135    ) -> Self {
136        Self {
137            metric,
138            start_unix_secs,
139            end_unix_secs,
140            step_secs,
141        }
142    }
143}
144
145///
146/// IcMetricRequest
147///
148/// Request accepted by the bounded official Dashboard metric report builder.
149///
150
151#[derive(Clone, Debug, Eq, PartialEq)]
152pub struct IcMetricRequest {
153    /// Dashboard Metrics API base endpoint.
154    pub source_endpoint: String,
155    /// Collection time as Unix seconds.
156    pub now_unix_secs: u64,
157    /// Explicitly bounded metric query.
158    pub query: IcMetricQuery,
159}
160
161impl IcMetricRequest {
162    /// Construct one bounded live Dashboard metric request.
163    #[must_use]
164    pub fn new(
165        source_endpoint: impl Into<String>,
166        now_unix_secs: u64,
167        query: IcMetricQuery,
168    ) -> Self {
169        Self {
170            source_endpoint: source_endpoint.into(),
171            now_unix_secs,
172            query,
173        }
174    }
175}
176
177///
178/// IcDailyStatsQuery
179///
180/// One explicitly bounded official Dashboard daily-statistics query.
181///
182
183#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
184pub struct IcDailyStatsQuery {
185    /// Inclusive query start as Unix seconds.
186    pub start_unix_secs: u64,
187    /// Inclusive query end as Unix seconds.
188    pub end_unix_secs: u64,
189}
190
191impl IcDailyStatsQuery {
192    /// Construct one explicit daily-statistics query window.
193    #[must_use]
194    pub const fn new(start_unix_secs: u64, end_unix_secs: u64) -> Self {
195        Self {
196            start_unix_secs,
197            end_unix_secs,
198        }
199    }
200}
201
202///
203/// IcDailyStatsRequest
204///
205/// Request accepted by the bounded official Dashboard daily-statistics builder.
206///
207
208#[derive(Clone, Debug, Eq, PartialEq)]
209pub struct IcDailyStatsRequest {
210    /// Dashboard API v3 base endpoint.
211    pub source_endpoint: String,
212    /// Collection time as Unix seconds.
213    pub now_unix_secs: u64,
214    /// Explicitly bounded daily-statistics query.
215    pub query: IcDailyStatsQuery,
216}
217
218impl IcDailyStatsRequest {
219    /// Construct one bounded live Dashboard daily-statistics request.
220    #[must_use]
221    pub fn new(
222        source_endpoint: impl Into<String>,
223        now_unix_secs: u64,
224        query: IcDailyStatsQuery,
225    ) -> Self {
226        Self {
227            source_endpoint: source_endpoint.into(),
228            now_unix_secs,
229            query,
230        }
231    }
232}
233
234///
235/// IcBoundaryNodeDataCentersRequest
236///
237/// Request accepted by the official Dashboard boundary-node data-center builder.
238///
239
240#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct IcBoundaryNodeDataCentersRequest {
242    /// Dashboard API v4 base endpoint.
243    pub source_endpoint: String,
244    /// Collection time as Unix seconds.
245    pub now_unix_secs: u64,
246}
247
248impl IcBoundaryNodeDataCentersRequest {
249    /// Construct one live Dashboard boundary-node data-center request.
250    #[must_use]
251    pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
252        Self {
253            source_endpoint: source_endpoint.into(),
254            now_unix_secs,
255        }
256    }
257}
258
259///
260/// IcCanisterRequest
261///
262/// Request accepted by the official Dashboard canister report builder.
263///
264
265#[derive(Clone, Debug, Eq, PartialEq)]
266pub struct IcCanisterRequest {
267    /// Dashboard API base endpoint.
268    pub source_endpoint: String,
269    /// Collection time as Unix seconds.
270    pub now_unix_secs: u64,
271    /// Canister principal to inspect.
272    pub canister_id: String,
273}
274
275impl IcCanisterRequest {
276    /// Construct a live Dashboard canister request.
277    #[must_use]
278    pub fn new(
279        source_endpoint: impl Into<String>,
280        now_unix_secs: u64,
281        canister_id: impl Into<String>,
282    ) -> Self {
283        Self {
284            source_endpoint: source_endpoint.into(),
285            now_unix_secs,
286            canister_id: canister_id.into(),
287        }
288    }
289}
290
291///
292/// IcCanisterFilters
293///
294/// Official Dashboard filters shared by canister count and page requests.
295///
296
297#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
298pub struct IcCanisterFilters {
299    /// Select canisters according to whether the Dashboard records a name.
300    pub has_name: Option<bool>,
301    /// Select canisters assigned to this Subnet principal.
302    pub subnet_id: Option<String>,
303    /// Select canisters controlled by this principal.
304    pub controller_id: Option<String>,
305    /// Raw Dashboard language labels to include.
306    pub languages: Vec<String>,
307    /// Raw Dashboard canister classifications to include.
308    pub canister_types: Vec<String>,
309    /// Raw Dashboard text search, between two and one hundred characters.
310    pub query: Option<String>,
311}
312
313///
314/// IcCanisterCountRequest
315///
316/// Request for one bounded official Dashboard canister-count lookup.
317///
318
319#[derive(Clone, Debug, Eq, PartialEq)]
320pub struct IcCanisterCountRequest {
321    /// Dashboard API v4 base endpoint.
322    pub source_endpoint: String,
323    /// Collection time as Unix seconds.
324    pub now_unix_secs: u64,
325    /// Filters applied by the Dashboard.
326    pub filters: IcCanisterFilters,
327}
328
329impl IcCanisterCountRequest {
330    /// Construct a live Dashboard canister-count request without filters.
331    #[must_use]
332    pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
333        Self {
334            source_endpoint: source_endpoint.into(),
335            now_unix_secs,
336            filters: IcCanisterFilters::default(),
337        }
338    }
339
340    /// Set the Dashboard filters used by this request.
341    #[must_use]
342    pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
343        self.filters = filters;
344        self
345    }
346}
347
348///
349/// IcCanisterPageRequest
350///
351/// Request for one bounded official Dashboard canister page.
352///
353
354#[derive(Clone, Debug, Eq, PartialEq)]
355pub struct IcCanisterPageRequest {
356    /// Dashboard API v4 base endpoint.
357    pub source_endpoint: String,
358    /// Collection time as Unix seconds.
359    pub now_unix_secs: u64,
360    /// Filters applied by the Dashboard.
361    pub filters: IcCanisterFilters,
362    /// Maximum rows requested from the API.
363    pub limit: u16,
364    /// Exclusive forward cursor returned by an earlier page.
365    pub after: Option<String>,
366    /// Exclusive backward cursor returned by an earlier page.
367    pub before: Option<String>,
368}
369
370impl IcCanisterPageRequest {
371    /// Construct a live Dashboard page request with the default bounded limit.
372    #[must_use]
373    pub fn new(source_endpoint: impl Into<String>, now_unix_secs: u64) -> Self {
374        Self {
375            source_endpoint: source_endpoint.into(),
376            now_unix_secs,
377            filters: IcCanisterFilters::default(),
378            limit: super::DEFAULT_IC_CANISTER_PAGE_LIMIT,
379            after: None,
380            before: None,
381        }
382    }
383
384    /// Set the Dashboard filters used by this request.
385    #[must_use]
386    pub fn with_filters(mut self, filters: IcCanisterFilters) -> Self {
387        self.filters = filters;
388        self
389    }
390
391    /// Set the maximum number of returned rows.
392    #[must_use]
393    pub const fn with_limit(mut self, limit: u16) -> Self {
394        self.limit = limit;
395        self
396    }
397
398    /// Set an exclusive forward cursor.
399    #[must_use]
400    pub fn with_after(mut self, after: impl Into<String>) -> Self {
401        self.after = Some(after.into());
402        self
403    }
404
405    /// Set an exclusive backward cursor.
406    #[must_use]
407    pub fn with_before(mut self, before: impl Into<String>) -> Self {
408        self.before = Some(before.into());
409        self
410    }
411}
412
413///
414/// IcCanisterUpgrade
415///
416/// One proposal-linked canister upgrade recorded by the Dashboard API.
417///
418
419#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
420pub struct IcCanisterUpgrade {
421    /// Proposal execution time as raw Unix seconds.
422    pub executed_timestamp_seconds: u64,
423    /// Wasm module hash as raw lowercase hexadecimal text.
424    pub module_hash: String,
425    /// NNS proposal that installed this module.
426    pub proposal_id: u64,
427}
428
429///
430/// IcDashboardReportProvenance
431///
432/// Shared off-chain provenance and authority guarantees for Dashboard reports.
433///
434
435#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
436pub struct IcDashboardReportProvenance {
437    /// Report schema version.
438    pub schema_version: u32,
439    /// Network represented by the official Dashboard API.
440    pub network: String,
441    /// Authority that supplied the report fields.
442    pub authority: String,
443    /// Dashboard API base endpoint queried by the source.
444    pub source_endpoint: String,
445    /// Time this report was collected.
446    pub fetched_at: String,
447    /// Collector identity.
448    pub fetched_by: String,
449    /// Whether the API response is cryptographically certified IC state.
450    pub certified: bool,
451    /// Whether every returned value is guaranteed to describe one point in time.
452    pub point_in_time_guaranteed: bool,
453}
454
455///
456/// IcMetricObservation
457///
458/// One raw timestamp and value returned by the Dashboard Metrics API.
459///
460
461#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
462pub struct IcMetricObservation {
463    /// Observation timestamp as Unix seconds.
464    pub timestamp_unix_secs: u64,
465    /// Raw value string returned by the Dashboard.
466    pub value: String,
467}
468
469///
470/// IcMetricSeries
471///
472/// One named raw series in a Dashboard metric response.
473///
474
475#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
476pub struct IcMetricSeries {
477    /// Raw Dashboard response field that names this series.
478    pub name: String,
479    /// Observations in strictly increasing timestamp order.
480    pub observations: Vec<IcMetricObservation>,
481}
482
483///
484/// IcMetricReport
485///
486/// One bounded time-series response from the official Dashboard Metrics API.
487///
488
489#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
490pub struct IcMetricReport {
491    /// Shared Dashboard provenance, flattened in serialized report JSON.
492    #[serde(flatten)]
493    pub provenance: IcDashboardReportProvenance,
494    /// Metric and explicit time-series bounds, flattened in report JSON.
495    #[serde(flatten)]
496    pub query: IcMetricQuery,
497    /// Number of named series returned by the API.
498    pub returned_series_count: usize,
499    /// Total number of observations across all returned series.
500    pub returned_observation_count: usize,
501    /// Raw named time series in canonical series-name order.
502    pub series: Vec<IcMetricSeries>,
503}
504
505///
506/// IcDailyStatsRow
507///
508/// Selected raw daily network-activity values returned by the Dashboard API.
509///
510
511#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
512pub struct IcDailyStatsRow {
513    /// Raw UTC calendar day returned by the Dashboard.
514    pub day: String,
515    /// Observation timestamp as Unix seconds.
516    pub timestamp_unix_secs: u64,
517    /// Raw average query-transaction rate.
518    pub average_query_transactions_per_second: String,
519    /// Raw average update-transaction rate.
520    pub average_update_transactions_per_second: String,
521    /// Raw average total-transaction rate.
522    pub average_transactions_per_second: String,
523    /// Raw maximum query-transaction rate.
524    pub max_query_transactions_per_second: String,
525    /// Raw maximum update-transaction rate.
526    pub max_update_transactions_per_second: String,
527    /// Raw maximum total-transaction rate.
528    pub max_total_transactions_per_second: String,
529    /// Raw average block-production rate.
530    pub blocks_per_second_average: String,
531}
532
533///
534/// IcDailyStatsReport
535///
536/// One bounded daily network-activity response from the official Dashboard API.
537///
538
539#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
540pub struct IcDailyStatsReport {
541    /// Shared Dashboard provenance, flattened in serialized report JSON.
542    #[serde(flatten)]
543    pub provenance: IcDashboardReportProvenance,
544    /// Exact requested time bounds, flattened in report JSON.
545    #[serde(flatten)]
546    pub query: IcDailyStatsQuery,
547    /// Number of daily rows returned by the API.
548    pub returned_day_count: usize,
549    /// Rows in strictly increasing timestamp order.
550    pub rows: Vec<IcDailyStatsRow>,
551}
552
553///
554/// IcBoundaryNodeDataCenterRow
555///
556/// One raw data-center aggregate returned by the boundary-node API.
557///
558
559#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
560pub struct IcBoundaryNodeDataCenterRow {
561    /// Dashboard data-center identifier.
562    pub dc_id: String,
563    /// Raw data-center display name.
564    pub name: String,
565    /// Raw infrastructure-owner label.
566    pub owner: String,
567    /// Raw Dashboard region label.
568    pub region: String,
569    /// Raw decimal latitude.
570    pub latitude: String,
571    /// Raw decimal longitude.
572    pub longitude: String,
573    /// Raw decimal count of boundary nodes assigned to this data center.
574    pub total_nodes: String,
575}
576
577///
578/// IcBoundaryNodeDataCentersReport
579///
580/// One complete response from the official boundary-node data-center resource.
581///
582
583#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
584pub struct IcBoundaryNodeDataCentersReport {
585    /// Shared Dashboard provenance, flattened in serialized report JSON.
586    #[serde(flatten)]
587    pub provenance: IcDashboardReportProvenance,
588    /// Number of data-center rows returned by the API.
589    pub data_center_count: usize,
590    /// Sum of the raw per-data-center boundary-node counts.
591    pub total_node_count: u64,
592    /// Rows in canonical data-center-id order, including zero-node locations.
593    pub rows: Vec<IcBoundaryNodeDataCenterRow>,
594}
595
596///
597/// IcCanisterReport
598///
599/// One live canister metadata report from the official Dashboard API.
600///
601
602#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
603pub struct IcCanisterReport {
604    /// Shared Dashboard provenance, flattened in serialized report JSON.
605    #[serde(flatten)]
606    pub provenance: IcDashboardReportProvenance,
607    /// Canonical canister principal.
608    pub canister_id: String,
609    /// Dashboard database row identifier.
610    pub dashboard_id: u64,
611    /// Raw optional Dashboard canister classification.
612    pub canister_type: Option<String>,
613    /// Raw Dashboard canister name; an empty string means no name was recorded.
614    pub name: String,
615    /// Canonical Subnet principal recorded by the Dashboard.
616    pub subnet_id: String,
617    /// Canonically ordered controller principals recorded by the Dashboard.
618    pub controllers: Vec<String>,
619    /// Raw Dashboard language label; an empty string means no language was recorded.
620    pub language: String,
621    /// Raw current module hash; an empty string means no hash was recorded.
622    pub module_hash: String,
623    /// Raw Dashboard row update timestamp.
624    pub dashboard_updated_at: String,
625    /// Number of proposal-linked upgrades when history is available.
626    pub upgrade_count: Option<usize>,
627    /// Proposal-linked upgrade history, or `None` when the Dashboard returned `null`.
628    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
629}
630
631///
632/// IcCanisterCountReport
633///
634/// One filtered canister count from the official Dashboard API.
635///
636
637#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
638pub struct IcCanisterCountReport {
639    /// Shared Dashboard provenance, flattened in serialized report JSON.
640    #[serde(flatten)]
641    pub provenance: IcDashboardReportProvenance,
642    /// Filters applied by the Dashboard.
643    pub filters: IcCanisterFilters,
644    /// Number of matching Dashboard canister records.
645    pub total: u64,
646}
647
648///
649/// IcCanisterPageController
650///
651/// One controller entry returned by the Dashboard canister collection API.
652///
653
654#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
655pub struct IcCanisterPageController {
656    /// Canonical controller principal.
657    pub principal_id: String,
658    /// Raw optional Dashboard metadata associated with the controller.
659    pub raw_metadata: Option<String>,
660}
661
662///
663/// IcCanisterPageRow
664///
665/// One discovery row from a bounded Dashboard canister page.
666///
667
668#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
669pub struct IcCanisterPageRow {
670    /// Canonical canister principal.
671    pub canister_id: String,
672    /// Dashboard database row identifier.
673    pub dashboard_id: u64,
674    /// Raw optional Dashboard canister classification.
675    pub canister_type: Option<String>,
676    /// Raw Dashboard canister name.
677    pub name: String,
678    /// Canonical Subnet principal recorded by the Dashboard.
679    pub subnet_id: String,
680    /// Canonically ordered controller entries recorded by the Dashboard.
681    pub controllers: Vec<IcCanisterPageController>,
682    /// Raw Dashboard language label.
683    pub language: String,
684    /// Raw current module hash.
685    pub module_hash: String,
686    /// Raw Dashboard row update timestamp.
687    pub dashboard_updated_at: String,
688}
689
690///
691/// IcCanisterPageReport
692///
693/// One explicitly bounded page from the official Dashboard canister collection.
694///
695
696#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
697pub struct IcCanisterPageReport {
698    /// Shared Dashboard provenance, flattened in serialized report JSON.
699    #[serde(flatten)]
700    pub provenance: IcDashboardReportProvenance,
701    /// Filters applied by the Dashboard.
702    pub filters: IcCanisterFilters,
703    /// Maximum rows requested from the API.
704    pub requested_limit: u16,
705    /// Number of rows returned in this report.
706    pub returned_count: usize,
707    /// Exclusive forward cursor supplied to this request.
708    pub after: Option<String>,
709    /// Exclusive backward cursor supplied to this request.
710    pub before: Option<String>,
711    /// Cursor for an explicit request for the preceding page.
712    pub previous_cursor: Option<String>,
713    /// Cursor for an explicit request for the following page.
714    pub next_cursor: Option<String>,
715    /// Canister discovery rows in Dashboard canister-id order.
716    pub rows: Vec<IcCanisterPageRow>,
717}
718
719///
720/// IcSourceRequest
721///
722/// Shared endpoint and collection provenance for IC Dashboard source calls and results.
723///
724
725#[cfg(feature = "host")]
726#[derive(Clone, Debug, Eq, PartialEq)]
727pub struct IcSourceRequest {
728    /// Dashboard API base endpoint.
729    pub endpoint: String,
730    /// Collection timestamp in UTC.
731    pub fetched_at: String,
732    /// Collector identity recorded in report provenance.
733    pub fetched_by: String,
734}
735
736#[cfg(feature = "host")]
737impl IcSourceRequest {
738    /// Construct source-call provenance.
739    #[must_use]
740    pub fn new(
741        endpoint: impl Into<String>,
742        fetched_at: impl Into<String>,
743        fetched_by: impl Into<String>,
744    ) -> Self {
745        Self {
746            endpoint: endpoint.into(),
747            fetched_at: fetched_at.into(),
748            fetched_by: fetched_by.into(),
749        }
750    }
751}
752
753///
754/// IcCanisterSourceData
755///
756/// Raw canister metadata and provenance returned by an IC Dashboard source.
757///
758
759#[cfg(feature = "host")]
760#[derive(Clone, Debug, Eq, PartialEq)]
761pub struct IcCanisterSourceData {
762    /// Source request and provenance preserved by the source.
763    pub source: IcSourceRequest,
764    /// Canister principal returned by the Dashboard.
765    pub canister_id: String,
766    /// Dashboard database row identifier.
767    pub dashboard_id: u64,
768    /// Raw optional Dashboard canister classification.
769    pub canister_type: Option<String>,
770    /// Raw Dashboard canister name.
771    pub name: String,
772    /// Subnet principal returned by the Dashboard.
773    pub subnet_id: String,
774    /// Controller principals returned by the Dashboard.
775    pub controllers: Vec<String>,
776    /// Raw Dashboard language label.
777    pub language: String,
778    /// Raw current module hash.
779    pub module_hash: String,
780    /// Raw Dashboard row update timestamp.
781    pub dashboard_updated_at: String,
782    /// Proposal-linked upgrades, or `None` when the Dashboard returned `null`.
783    pub upgrades: Option<Vec<IcCanisterUpgrade>>,
784}
785
786///
787/// IcCanisterCountSourceData
788///
789/// Raw filtered count and provenance returned by a Dashboard source.
790///
791
792#[cfg(feature = "host")]
793#[derive(Clone, Debug, Eq, PartialEq)]
794pub struct IcCanisterCountSourceData {
795    /// Source request and provenance preserved by the source.
796    pub source: IcSourceRequest,
797    /// Filters applied by the source.
798    pub filters: IcCanisterFilters,
799    /// Number of matching Dashboard canister records.
800    pub total: u64,
801}
802
803///
804/// IcCanisterPageSourceData
805///
806/// Raw bounded canister page and provenance returned by a Dashboard source.
807///
808
809#[cfg(feature = "host")]
810#[derive(Clone, Debug, Eq, PartialEq)]
811pub struct IcCanisterPageSourceData {
812    /// Source request and provenance preserved by the source.
813    pub source: IcSourceRequest,
814    /// Filters applied by the source.
815    pub filters: IcCanisterFilters,
816    /// Maximum rows requested from the source.
817    pub requested_limit: u16,
818    /// Exclusive forward cursor supplied to the source.
819    pub after: Option<String>,
820    /// Exclusive backward cursor supplied to the source.
821    pub before: Option<String>,
822    /// Cursor for an explicit request for the preceding page.
823    pub previous_cursor: Option<String>,
824    /// Cursor for an explicit request for the following page.
825    pub next_cursor: Option<String>,
826    /// Canister discovery rows returned by the source.
827    pub rows: Vec<IcCanisterPageRow>,
828}
829
830///
831/// IcMetricSourceData
832///
833/// Raw bounded metric series and provenance returned by a Dashboard source.
834///
835
836#[cfg(feature = "host")]
837#[derive(Clone, Debug, Eq, PartialEq)]
838pub struct IcMetricSourceData {
839    /// Source request and provenance preserved by the source.
840    pub source: IcSourceRequest,
841    /// Metric query applied by the source.
842    pub query: IcMetricQuery,
843    /// Raw named time series returned by the source.
844    pub series: Vec<IcMetricSeries>,
845}
846
847///
848/// IcDailyStatsSourceData
849///
850/// Raw bounded daily network-activity rows and provenance returned by a source.
851///
852
853#[cfg(feature = "host")]
854#[derive(Clone, Debug, Eq, PartialEq)]
855pub struct IcDailyStatsSourceData {
856    /// Source request and provenance preserved by the source.
857    pub source: IcSourceRequest,
858    /// Daily-statistics query applied by the source.
859    pub query: IcDailyStatsQuery,
860    /// Selected raw daily rows returned by the source.
861    pub rows: Vec<IcDailyStatsRow>,
862}
863
864///
865/// IcBoundaryNodeDataCentersSourceData
866///
867/// Raw boundary-node data-center rows and provenance returned by a Dashboard source.
868///
869
870#[cfg(feature = "host")]
871#[derive(Clone, Debug, Eq, PartialEq)]
872pub struct IcBoundaryNodeDataCentersSourceData {
873    /// Source request and provenance preserved by the source.
874    pub source: IcSourceRequest,
875    /// Raw data-center rows returned by the source.
876    pub rows: Vec<IcBoundaryNodeDataCenterRow>,
877}
878
879///
880/// IcHostError
881///
882/// Typed error returned by IC Dashboard report builders and live sources.
883///
884
885#[cfg(feature = "host")]
886#[derive(Debug, ThisError)]
887pub enum IcHostError {
888    /// The synchronous adapter could not create its local async runtime.
889    #[error("failed to run IC Dashboard query: {0}")]
890    Runtime(#[from] RuntimeError),
891
892    /// A request supplied an invalid canister principal.
893    #[error("invalid {field}: {reason}")]
894    InvalidPrincipal {
895        /// Principal field being validated.
896        field: &'static str,
897        /// Principal parser diagnostic.
898        reason: String,
899    },
900
901    /// A request violates the bounded Dashboard query contract.
902    #[error("invalid {field}: {reason}")]
903    InvalidRequest {
904        /// Request field being validated.
905        field: &'static str,
906        /// Deterministic validation diagnostic.
907        reason: String,
908    },
909
910    /// The Dashboard API base endpoint is malformed or unsupported.
911    #[error("invalid IC Dashboard endpoint {endpoint}: {reason}")]
912    InvalidEndpoint {
913        /// Rejected endpoint.
914        endpoint: String,
915        /// URL validation diagnostic.
916        reason: String,
917    },
918
919    /// The HTTP client could not be constructed.
920    #[error("failed to build IC Dashboard HTTP client: {reason}")]
921    HttpClientBuild {
922        /// HTTP client construction diagnostic.
923        reason: String,
924    },
925
926    /// The live Dashboard request failed before a response was received.
927    #[error("IC Dashboard request to {url} failed: {reason}")]
928    HttpRequest {
929        /// Fully resolved request URL.
930        url: String,
931        /// HTTP transport error.
932        reason: String,
933    },
934
935    /// The Dashboard returned a non-success HTTP status.
936    #[error("IC Dashboard request to {url} returned HTTP status {status}")]
937    HttpStatus {
938        /// Fully resolved request URL.
939        url: String,
940        /// Numeric HTTP status.
941        status: u16,
942    },
943
944    /// The Dashboard response did not match the expected JSON shape.
945    #[error("failed to decode IC Dashboard response from {url}: {reason}")]
946    JsonDecode {
947        /// Fully resolved request URL.
948        url: String,
949        /// JSON response decoding error.
950        reason: String,
951    },
952
953    /// A source capability returned data that violates its public result contract.
954    #[error("invalid IC Dashboard source data: {reason}")]
955    InvalidSourceData {
956        /// Deterministic invariant failure.
957        reason: String,
958    },
959}