1#![cfg(not(target_family = "wasm"))]
2#![expect(
3 missing_docs,
4 clippy::too_many_arguments,
5 clippy::pedantic,
6 clippy::nursery,
7 reason = "initial upstream source transfer preserves public signatures and protocol names; see upstream/NOTICE.md"
8)]
9use self::common::rest::{
15 AutoProgressConfig, ExtendedSubnetConfigSet, IcpConfig, IcpFeatures, InitialTime,
16 InstanceHttpGatewayConfig, RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId, SubnetKind,
17 SubnetSpec,
18};
19use candid::Principal;
20pub use ic_management_canister_types::{
21 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
22 EnvironmentVariable, Snapshot,
23};
24pub use ic_platform::ic_protocol::SubnetMetrics;
25use reqwest::Url;
26use schemars::JsonSchema;
27use serde::{Deserialize, Serialize};
28use slog::Level;
29use std::{
30 net::SocketAddr,
31 path::PathBuf,
32 time::{Duration, SystemTime, UNIX_EPOCH},
33};
34use strum_macros::EnumIter;
35use tempfile::TempDir;
36
37mod error;
38mod http;
39pub use error::{ClientError, Operation, RequestContext};
40pub mod common;
41mod nonblocking;
42pub use nonblocking::{
43 PocketIc, call_candid, call_candid_as, get_default_effective_canister_id, query_candid,
44 query_candid_as, update_candid, update_candid_as, with_candid,
45};
46pub mod snapshot;
47
48pub const LATEST_SERVER_VERSION: &str = "16.1.0";
50
51const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
53
54enum PocketIcStateKind {
55 StateDir(PathBuf),
57 TempDir(TempDir),
63}
64
65pub struct PocketIcState {
66 state: PocketIcStateKind,
67}
68
69impl PocketIcState {
70 #[expect(
71 clippy::new_without_default,
72 reason = "preserve the imported PocketIC contract without an unrelated API change"
73 )]
74 pub fn new() -> Self {
75 let temp_dir = TempDir::new().unwrap();
76 Self {
77 state: PocketIcStateKind::TempDir(temp_dir),
78 }
79 }
80
81 pub fn new_from_path(state_dir: PathBuf) -> Self {
82 Self {
83 state: PocketIcStateKind::StateDir(state_dir),
84 }
85 }
86
87 pub fn into_path(self) -> PathBuf {
88 match self.state {
89 PocketIcStateKind::StateDir(state_dir) => state_dir,
90 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
91 }
92 }
93
94 pub(crate) fn state_dir(&self) -> PathBuf {
95 match &self.state {
96 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
97 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
98 }
99 }
100}
101
102pub struct PocketIcBuilder {
103 config: Option<ExtendedSubnetConfigSet>,
104 http_gateway_config: Option<InstanceHttpGatewayConfig>,
105 server_url: Option<Url>,
106 max_request_time_ms: Option<u64>,
107 read_only_state_dir: Option<PathBuf>,
108 state_dir: Option<PocketIcState>,
109 icp_config: IcpConfig,
110 log_level: Option<Level>,
111 bitcoind_addr: Option<Vec<SocketAddr>>,
112 dogecoind_addr: Option<Vec<SocketAddr>>,
113 icp_features: IcpFeatures,
114 initial_time: Option<InitialTime>,
115 mainnet_nns_subnet_id: Option<bool>,
116 disable_ingress_validation: Option<bool>,
117}
118
119#[expect(
120 clippy::new_without_default,
121 reason = "preserve the imported PocketIC contract without an unrelated API change"
122)]
123impl PocketIcBuilder {
124 pub fn new() -> Self {
125 Self {
126 config: None,
127 http_gateway_config: None,
128 server_url: None,
129 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
130 read_only_state_dir: None,
131 state_dir: None,
132 icp_config: IcpConfig::default(),
133 log_level: None,
134 bitcoind_addr: None,
135 dogecoind_addr: None,
136 icp_features: IcpFeatures::default(),
137 initial_time: None,
138 mainnet_nns_subnet_id: None,
139 disable_ingress_validation: None,
140 }
141 }
142
143 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
144 let mut builder = Self::new();
145 builder.config = Some(config.into());
146 builder
147 }
148
149 pub async fn build(self) -> Result<PocketIc, ClientError> {
150 PocketIc::from_components(
151 self.config.unwrap_or_default(),
152 self.server_url.ok_or(ClientError::MissingServerUrl)?,
153 self.max_request_time_ms,
154 self.read_only_state_dir,
155 self.state_dir,
156 self.icp_config,
157 self.log_level,
158 self.bitcoind_addr,
159 self.dogecoind_addr,
160 self.icp_features,
161 self.initial_time,
162 self.http_gateway_config,
163 self.mainnet_nns_subnet_id,
164 self.disable_ingress_validation,
165 )
166 .await
167 }
168
169 pub fn with_server_url(mut self, server_url: Url) -> Self {
171 self.server_url = Some(server_url);
172 self
173 }
174
175 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
176 self.max_request_time_ms = max_request_time_ms;
177 self
178 }
179
180 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
181 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
182 self
183 }
184
185 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
186 self.state_dir = Some(state_dir);
187 self
188 }
189
190 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
191 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
192 self
193 }
194
195 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
196 self.icp_config = icp_config;
197 self
198 }
199
200 pub fn with_log_level(mut self, log_level: Level) -> Self {
201 self.log_level = Some(log_level);
202 self
203 }
204
205 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
206 self.with_bitcoind_addrs(vec![bitcoind_addr])
207 }
208
209 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
210 Self {
211 bitcoind_addr: Some(bitcoind_addrs),
212 ..self
213 }
214 }
215
216 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
217 Self {
218 dogecoind_addr: Some(dogecoind_addrs),
219 ..self
220 }
221 }
222
223 pub fn with_nns_subnet(mut self) -> Self {
225 let mut config = self.config.unwrap_or_default();
226 config.nns = Some(config.nns.unwrap_or_default());
227 self.config = Some(config);
228 self
229 }
230
231 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
248 self.with_subnet_state(SubnetKind::NNS, path_to_state)
249 }
250
251 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
268 let mut config = self.config.unwrap_or_default();
269 #[cfg(not(windows))]
270 let state_dir = path_to_state;
271 #[cfg(windows)]
272 let state_dir = wsl_path(&path_to_state, "subnet state").into();
273 let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
274 match subnet_kind {
275 SubnetKind::NNS => config.nns = Some(subnet_spec),
276 SubnetKind::SNS => config.sns = Some(subnet_spec),
277 SubnetKind::II => config.ii = Some(subnet_spec),
278 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
279 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
280 SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
281 SubnetKind::Application => config.application.push(subnet_spec),
282 SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
283 SubnetKind::System => config.system.push(subnet_spec),
284 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
285 };
286 self.config = Some(config);
287 self
288 }
289
290 pub fn with_sns_subnet(mut self) -> Self {
292 let mut config = self.config.unwrap_or_default();
293 config.sns = Some(config.sns.unwrap_or_default());
294 self.config = Some(config);
295 self
296 }
297
298 pub fn with_ii_subnet(mut self) -> Self {
300 let mut config = self.config.unwrap_or_default();
301 config.ii = Some(config.ii.unwrap_or_default());
302 self.config = Some(config);
303 self
304 }
305
306 pub fn with_fiduciary_subnet(mut self) -> Self {
308 let mut config = self.config.unwrap_or_default();
309 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
310 self.config = Some(config);
311 self
312 }
313
314 pub fn with_bitcoin_subnet(mut self) -> Self {
316 let mut config = self.config.unwrap_or_default();
317 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
318 self.config = Some(config);
319 self
320 }
321
322 pub fn with_test_threshold_keys_subnet(mut self) -> Self {
324 let mut config = self.config.unwrap_or_default();
325 config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
326 self.config = Some(config);
327 self
328 }
329
330 pub fn with_system_subnet(mut self) -> Self {
332 let mut config = self.config.unwrap_or_default();
333 config.system.push(SubnetSpec::default());
334 self.config = Some(config);
335 self
336 }
337
338 pub fn with_application_subnet(mut self) -> Self {
340 let mut config = self.config.unwrap_or_default();
341 config.application.push(SubnetSpec::default());
342 self.config = Some(config);
343 self
344 }
345
346 pub fn with_verified_application_subnet(mut self) -> Self {
348 let mut config = self.config.unwrap_or_default();
349 config.verified_application.push(SubnetSpec::default());
350 self.config = Some(config);
351 self
352 }
353
354 pub fn with_benchmarking_application_subnet(mut self) -> Self {
356 let mut config = self.config.unwrap_or_default();
357 config
358 .application
359 .push(SubnetSpec::default().with_benchmarking_instruction_config());
360 self.config = Some(config);
361 self
362 }
363
364 pub fn with_benchmarking_system_subnet(mut self) -> Self {
366 let mut config = self.config.unwrap_or_default();
367 config
368 .system
369 .push(SubnetSpec::default().with_benchmarking_instruction_config());
370 self.config = Some(config);
371 self
372 }
373
374 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
379 self.icp_features = icp_features;
380 self
381 }
382
383 #[deprecated(note = "Use `with_initial_time` instead")]
387 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
388 self.initial_time = Some(InitialTime::Timestamp(RawTime {
389 nanos_since_epoch: initial_timestamp_nanos,
390 }));
391 self
392 }
393
394 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
398 self.initial_time = Some(InitialTime::Timestamp(RawTime {
399 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
400 }));
401 self
402 }
403
404 pub fn with_auto_progress(mut self) -> Self {
410 let config = AutoProgressConfig {
411 artificial_delay_ms: None,
412 };
413 self.initial_time = Some(InitialTime::AutoProgress(config));
414 self
415 }
416
417 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
418 self.http_gateway_config = Some(http_gateway_config);
419 self
420 }
421
422 pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
423 self.mainnet_nns_subnet_id = Some(true);
424 self
425 }
426
427 pub fn disable_ingress_validation(mut self) -> Self {
428 self.disable_ingress_validation = Some(true);
429 self
430 }
431}
432
433#[derive(Copy, Clone, PartialEq, PartialOrd)]
436pub struct Time(Duration);
437
438impl Time {
439 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
441 self.0.as_nanos().try_into().unwrap()
442 }
443
444 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
445 Time(Duration::from_nanos(nanos))
446 }
447}
448
449impl std::fmt::Debug for Time {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
452 write!(f, "{nanos_since_unix_epoch}")
453 }
454}
455
456impl std::ops::Add<Duration> for Time {
457 type Output = Time;
458 fn add(self, dur: Duration) -> Time {
459 Time(self.0 + dur)
460 }
461}
462
463impl From<SystemTime> for Time {
464 fn from(time: SystemTime) -> Self {
465 Self::from_nanos_since_unix_epoch(
466 time.duration_since(UNIX_EPOCH)
467 .unwrap()
468 .as_nanos()
469 .try_into()
470 .unwrap(),
471 )
472 }
473}
474
475impl TryFrom<Time> for SystemTime {
476 type Error = String;
477
478 fn try_from(time: Time) -> Result<SystemTime, String> {
479 let nanos = time.as_nanos_since_unix_epoch();
480 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
481 let roundtrip: Time = system_time.into();
482 if roundtrip.as_nanos_since_unix_epoch() == nanos {
483 Ok(system_time)
484 } else {
485 Err(format!(
486 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
487 ))
488 }
489 }
490}
491
492#[derive(Clone, Debug)]
494pub enum CreateCanisterPlacement {
495 SubnetId(SubnetId),
497 CanisterId(CanisterId),
499}
500
501#[derive(Clone, Debug, Default)]
503pub struct CreateCanisterParams {
504 pub cycles: Option<u128>,
506 pub settings: Option<CanisterSettings>,
508 pub placement: Option<CreateCanisterPlacement>,
510}
511
512#[derive(Clone, Copy, Debug)]
514pub enum TryFromError {
515 ValueOutOfRange(u64),
516}
517
518#[derive(
525 PartialOrd,
526 Ord,
527 Clone,
528 Copy,
529 Debug,
530 PartialEq,
531 Eq,
532 Hash,
533 Serialize,
534 Deserialize,
535 JsonSchema,
536 EnumIter,
537)]
538pub enum ErrorCode {
539 SubnetOversubscribed = 101,
541 MaxNumberOfCanistersReached = 102,
542 CanisterQueueFull = 201,
544 IngressMessageTimeout = 202,
545 CanisterQueueNotEmpty = 203,
546 IngressHistoryFull = 204,
547 CanisterIdAlreadyExists = 205,
548 StopCanisterRequestTimeout = 206,
549 CanisterOutOfCycles = 207,
550 CertifiedStateUnavailable = 208,
551 CanisterInstallCodeRateLimited = 209,
552 CanisterHeapDeltaRateLimited = 210,
553 SubnetCoolingDown = 211,
554 CanisterNotFound = 301,
556 CanisterSnapshotNotFound = 305,
557 InsufficientMemoryAllocation = 402,
559 InsufficientCyclesForCreateCanister = 403,
560 SubnetNotFound = 404,
561 CanisterNotHostedBySubnet = 405,
562 CanisterRejectedMessage = 406,
563 UnknownManagementMessage = 407,
564 InvalidManagementPayload = 408,
565 CanisterSnapshotImmutable = 409,
566 InvalidSubnetAdmin = 410,
567 CanisterTrapped = 502,
569 CanisterCalledTrap = 503,
570 CanisterContractViolation = 504,
571 CanisterInvalidWasm = 505,
572 CanisterDidNotReply = 506,
573 CanisterOutOfMemory = 507,
574 CanisterStopped = 508,
575 CanisterStopping = 509,
576 CanisterNotStopped = 510,
577 CanisterStoppingCancelled = 511,
578 CanisterInvalidController = 512,
579 CanisterFunctionNotFound = 513,
580 CanisterNonEmpty = 514,
581 QueryCallGraphLoopDetected = 517,
582 InsufficientCyclesInCall = 520,
583 CanisterWasmEngineError = 521,
584 CanisterInstructionLimitExceeded = 522,
585 CanisterMemoryAccessLimitExceeded = 524,
586 QueryCallGraphTooDeep = 525,
587 QueryCallGraphTotalInstructionLimitExceeded = 526,
588 CompositeQueryCalledInReplicatedMode = 527,
589 QueryTimeLimitExceeded = 528,
590 QueryCallGraphInternal = 529,
591 InsufficientCyclesInComputeAllocation = 530,
592 InsufficientCyclesInMemoryAllocation = 531,
593 InsufficientCyclesInMemoryGrow = 532,
594 ReservedCyclesLimitExceededInMemoryAllocation = 533,
595 ReservedCyclesLimitExceededInMemoryGrow = 534,
596 InsufficientCyclesInMessageMemoryGrow = 535,
597 CanisterMethodNotFound = 536,
598 CanisterWasmModuleNotFound = 537,
599 CanisterAlreadyInstalled = 538,
600 CanisterWasmMemoryLimitExceeded = 539,
601 ReservedCyclesLimitIsTooLow = 540,
602 CanisterInvalidControllerOrSubnetAdmin = 541,
603 CanisterStatusAccessDenied = 542,
604 DeadlineExpired = 601,
606 ResponseDropped = 602,
607}
608
609impl TryFrom<u64> for ErrorCode {
610 type Error = TryFromError;
611 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
612 match err {
613 101 => Ok(ErrorCode::SubnetOversubscribed),
615 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
616 201 => Ok(ErrorCode::CanisterQueueFull),
618 202 => Ok(ErrorCode::IngressMessageTimeout),
619 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
620 204 => Ok(ErrorCode::IngressHistoryFull),
621 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
622 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
623 207 => Ok(ErrorCode::CanisterOutOfCycles),
624 208 => Ok(ErrorCode::CertifiedStateUnavailable),
625 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
626 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
627 211 => Ok(ErrorCode::SubnetCoolingDown),
628 301 => Ok(ErrorCode::CanisterNotFound),
630 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
631 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
633 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
634 404 => Ok(ErrorCode::SubnetNotFound),
635 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
636 406 => Ok(ErrorCode::CanisterRejectedMessage),
637 407 => Ok(ErrorCode::UnknownManagementMessage),
638 408 => Ok(ErrorCode::InvalidManagementPayload),
639 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
640 410 => Ok(ErrorCode::InvalidSubnetAdmin),
641 502 => Ok(ErrorCode::CanisterTrapped),
643 503 => Ok(ErrorCode::CanisterCalledTrap),
644 504 => Ok(ErrorCode::CanisterContractViolation),
645 505 => Ok(ErrorCode::CanisterInvalidWasm),
646 506 => Ok(ErrorCode::CanisterDidNotReply),
647 507 => Ok(ErrorCode::CanisterOutOfMemory),
648 508 => Ok(ErrorCode::CanisterStopped),
649 509 => Ok(ErrorCode::CanisterStopping),
650 510 => Ok(ErrorCode::CanisterNotStopped),
651 511 => Ok(ErrorCode::CanisterStoppingCancelled),
652 512 => Ok(ErrorCode::CanisterInvalidController),
653 513 => Ok(ErrorCode::CanisterFunctionNotFound),
654 514 => Ok(ErrorCode::CanisterNonEmpty),
655 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
656 520 => Ok(ErrorCode::InsufficientCyclesInCall),
657 521 => Ok(ErrorCode::CanisterWasmEngineError),
658 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
659 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
660 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
661 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
662 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
663 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
664 529 => Ok(ErrorCode::QueryCallGraphInternal),
665 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
666 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
667 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
668 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
669 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
670 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
671 536 => Ok(ErrorCode::CanisterMethodNotFound),
672 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
673 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
674 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
675 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
676 541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
677 542 => Ok(ErrorCode::CanisterStatusAccessDenied),
678 601 => Ok(ErrorCode::DeadlineExpired),
680 602 => Ok(ErrorCode::ResponseDropped),
681 _ => Err(TryFromError::ValueOutOfRange(err)),
682 }
683 }
684}
685
686impl std::fmt::Display for ErrorCode {
687 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
688 write!(f, "IC{:04}", *self as i32)
690 }
691}
692
693#[derive(
698 PartialOrd,
699 Ord,
700 Clone,
701 Copy,
702 Debug,
703 PartialEq,
704 Eq,
705 Hash,
706 Serialize,
707 Deserialize,
708 JsonSchema,
709 EnumIter,
710)]
711pub enum RejectCode {
712 SysFatal = 1,
713 SysTransient = 2,
714 DestinationInvalid = 3,
715 CanisterReject = 4,
716 CanisterError = 5,
717 SysUnknown = 6,
718}
719
720impl TryFrom<u64> for RejectCode {
721 type Error = TryFromError;
722 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
723 match err {
724 1 => Ok(RejectCode::SysFatal),
725 2 => Ok(RejectCode::SysTransient),
726 3 => Ok(RejectCode::DestinationInvalid),
727 4 => Ok(RejectCode::CanisterReject),
728 5 => Ok(RejectCode::CanisterError),
729 6 => Ok(RejectCode::SysUnknown),
730 _ => Err(TryFromError::ValueOutOfRange(err)),
731 }
732 }
733}
734
735#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
737pub struct RejectResponse {
738 pub reject_code: RejectCode,
739 pub reject_message: String,
740 pub error_code: ErrorCode,
741 pub certified: bool,
742}
743
744impl std::fmt::Display for RejectResponse {
745 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746 write!(
748 f,
749 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
750 self.reject_code, self.reject_message, self.error_code
751 )
752 }
753}
754
755#[derive(Debug, Serialize, Deserialize)]
761pub enum IngressStatusResult {
762 NotAvailable,
763 Forbidden(String),
764 Success(Result<Vec<u8>, RejectResponse>),
765}
766
767#[derive(Clone, Debug, Default)]
768pub struct TickConfigs {
769 pub blockmakers: Option<Vec<SubnetBlockmakers>>,
770}
771
772impl From<TickConfigs> for RawTickConfigs {
773 fn from(tick_configs: TickConfigs) -> Self {
774 Self {
775 blockmakers: tick_configs.blockmakers.map(|blockmakers| {
776 blockmakers
777 .into_iter()
778 .map(|blockmaker| blockmaker.into())
779 .collect()
780 }),
781 }
782 }
783}
784
785#[derive(Clone, Debug)]
786pub struct SubnetBlockmakers {
787 pub subnet: Principal,
788 pub blockmaker: Principal,
789 pub failed_blockmakers: Vec<Principal>,
790}
791
792impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
793 fn from(blockmaker: SubnetBlockmakers) -> Self {
794 Self {
795 subnet: blockmaker.subnet.into(),
796 blockmaker: blockmaker.blockmaker.into(),
797 failed_blockmakers: blockmaker
798 .failed_blockmakers
799 .into_iter()
800 .map(|p| p.into())
801 .collect(),
802 }
803 }
804}
805
806#[cfg_attr(
814 all(not(windows), not(test)),
815 expect(
816 dead_code,
817 reason = "the retained WSL converter is used by Windows implementations"
818 )
819)]
820fn windows_to_wsl(path: &str) -> Result<String, String> {
821 let path = path.strip_prefix(r"\\?\").unwrap_or(path);
822 let mut chars = path.chars();
823 let drive = match (chars.next(), chars.next()) {
824 (Some(drive), Some(':')) if drive.is_ascii_alphabetic() => drive.to_ascii_lowercase(),
825 _ => {
826 return Err(format!(
827 "`{path}` does not start with a drive prefix such as `C:`"
828 ));
829 }
830 };
831 let rest = chars.as_str();
832 if !rest.is_empty() && !rest.starts_with(['\\', '/']) {
833 return Err(format!("`{path}` is a drive-relative path"));
834 }
835 Ok(format!("/mnt/{drive}{}", rest.replace('\\', "/")))
836}
837
838#[cfg(windows)]
839fn wsl_path(path: &PathBuf, desc: &str) -> String {
840 windows_to_wsl(
841 path.as_os_str()
842 .to_str()
843 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
844 )
845 .unwrap_or_else(|e| {
846 panic!(
847 "Could not convert {} path ({:?}) to WSL path: {:?}",
848 desc, path, e
849 )
850 })
851}
852
853pub fn copy_dir(
854 src: impl AsRef<std::path::Path>,
855 dst: impl AsRef<std::path::Path>,
856) -> std::io::Result<()> {
857 std::fs::create_dir_all(&dst)?;
858 for entry in std::fs::read_dir(src)? {
859 let entry = entry?;
860 let ty = entry.file_type()?;
861 if ty.is_dir() {
862 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
863 } else {
864 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
865 }
866 }
867 Ok(())
868}
869
870#[cfg(test)]
871mod tests;