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}