1#![allow(dead_code)]
2#![allow(clippy::should_implement_trait)] use crate::config::directories::get_user_dfx_config_dir;
4use crate::config::model::bitcoin_adapter::BitcoinAdapterLogLevel;
5use crate::config::model::canister_http_adapter::HttpAdapterLogLevel;
6use crate::config::model::extension_canister_type::apply_extension_canister_types;
7use crate::error::config::{GetOutputEnvFileError, GetTempPathError};
8use crate::error::dfx_config::AddDependenciesError::CanisterCircularDependency;
9use crate::error::dfx_config::GetCanisterNamesWithDependenciesError::AddDependenciesFailed;
10use crate::error::dfx_config::GetComputeAllocationError::GetComputeAllocationFailed;
11use crate::error::dfx_config::GetFreezingThresholdError::GetFreezingThresholdFailed;
12use crate::error::dfx_config::GetLogVisibilityError::GetLogVisibilityFailed;
13use crate::error::dfx_config::GetMemoryAllocationError::GetMemoryAllocationFailed;
14use crate::error::dfx_config::GetPullCanistersError::PullCanistersSameId;
15use crate::error::dfx_config::GetRemoteCanisterIdError::GetRemoteCanisterIdFailed;
16use crate::error::dfx_config::GetReservedCyclesLimitError::GetReservedCyclesLimitFailed;
17use crate::error::dfx_config::GetSpecifiedIdError::GetSpecifiedIdFailed;
18use crate::error::dfx_config::GetWasmMemoryLimitError::GetWasmMemoryLimitFailed;
19use crate::error::dfx_config::GetWasmMemoryThresholdError::GetWasmMemoryThresholdFailed;
20use crate::error::dfx_config::{
21 AddDependenciesError, GetCanisterConfigError, GetCanisterNamesWithDependenciesError,
22 GetComputeAllocationError, GetFreezingThresholdError, GetLogVisibilityError,
23 GetMemoryAllocationError, GetPullCanistersError, GetRemoteCanisterIdError,
24 GetReservedCyclesLimitError, GetSpecifiedIdError, GetWasmMemoryLimitError,
25 GetWasmMemoryThresholdError,
26};
27use crate::error::fs::CanonicalizePathError;
28use crate::error::load_dfx_config::LoadDfxConfigError;
29use crate::error::load_dfx_config::LoadDfxConfigError::{
30 DetermineCurrentWorkingDirFailed, ResolveConfigPath,
31};
32use crate::error::load_networks_config::LoadNetworksConfigError;
33use crate::error::load_networks_config::LoadNetworksConfigError::{
34 GetConfigPathFailed as GetNetworkConfigPathFailed,
35 LoadConfigFromFileFailed as LoadNetworkConfigFromFileFailed,
36};
37use crate::error::load_tool_config::ToolConfigError;
38use crate::error::load_tool_config::ToolConfigError::{
39 GetConfigPathFailed as GetToolConfigPathFailed,
40 LoadConfigFromFileFailed as LoadToolConfigFromFileFailed, SaveDefaultConfigFailed,
41};
42use crate::error::socket_addr_conversion::SocketAddrConversionError;
43use crate::error::socket_addr_conversion::SocketAddrConversionError::{
44 EmptyIterator, ParseSocketAddrFailed,
45};
46use crate::error::structured_file::StructuredFileError;
47use crate::error::structured_file::StructuredFileError::DeserializeJsonFileFailed;
48use crate::extension::manager::ExtensionManager;
49use crate::fs::create_dir_all;
50use crate::json::structure::{PossiblyStr, SerdeVec};
51use crate::json::{load_json_file, save_json_file};
52use crate::util::ByteSchema;
53use byte_unit::Byte;
54use candid::Principal;
55use clap::ValueEnum;
56use ic_utils::interfaces::management_canister::LogVisibility;
57use schemars::JsonSchema;
58use serde::de::{Error as _, MapAccess, Visitor};
59use serde::{Deserialize, Deserializer, Serialize};
60use serde_json::Value;
61use std::collections::{BTreeMap, BTreeSet, HashSet};
62use std::default::Default;
63use std::fmt::{self, Debug, Display, Formatter};
64use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs};
65use std::path::{Path, PathBuf};
66use std::time::Duration;
67
68use super::network_descriptor::MOTOKO_PLAYGROUND_CANISTER_TIMEOUT_SECONDS;
69
70pub const CONFIG_FILE_NAME: &str = "dfx.json";
71
72pub const BUILTIN_CANISTER_TYPES: [&str; 5] = ["rust", "motoko", "assets", "custom", "pull"];
73
74const EMPTY_CONFIG_DEFAULTS: ConfigDefaults = ConfigDefaults {
75 bitcoin: None,
76 dogecoin: None,
77 bootstrap: None,
78 build: None,
79 canister_http: None,
80 proxy: None,
81 replica: None,
82};
83
84const EMPTY_CONFIG_DEFAULTS_BUILD: ConfigDefaultsBuild = ConfigDefaultsBuild {
85 packtool: None,
86 args: None,
87};
88
89#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
94pub struct ConfigCanistersCanisterRemote {
95 pub candid: Option<PathBuf>,
98
99 #[schemars(with = "BTreeMap<String, String>")]
103 pub id: BTreeMap<String, Principal>,
104}
105
106#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
111pub enum WasmOptLevel {
112 #[serde(rename = "cycles")]
113 Cycles,
114 #[serde(rename = "size")]
115 Size,
116 O4,
117 O3,
118 O2,
119 O1,
120 O0,
121 Oz,
122 Os,
123}
124impl Display for WasmOptLevel {
125 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 Debug::fmt(self, f)
127 }
128}
129
130#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
131#[serde(rename_all = "lowercase")]
132pub enum MetadataVisibility {
133 #[default]
135 Public,
136
137 Private,
139}
140
141#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
145pub struct CanisterMetadataSection {
146 pub name: String,
149
150 #[serde(default)]
152 pub visibility: MetadataVisibility,
153
154 pub networks: Option<BTreeSet<String>>,
159
160 pub path: Option<PathBuf>,
168
169 pub content: Option<String>,
173}
174
175impl CanisterMetadataSection {
176 pub fn applies_to_network(&self, network: &str) -> bool {
177 self.networks
178 .as_ref()
179 .map(|networks| networks.contains(network))
180 .unwrap_or(true)
181 }
182}
183
184#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
185pub struct Pullable {
186 pub wasm_url: String,
189 pub wasm_hash: Option<String>,
195 pub wasm_hash_url: Option<String>,
201 #[schemars(with = "Vec::<String>")]
204 pub dependencies: Vec<Principal>,
205 pub init_guide: String,
208 pub init_arg: Option<String>,
211}
212
213pub type TechStackCategoryMap = BTreeMap<String, BTreeMap<String, String>>;
214
215#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
218pub struct TechStack {
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub cdk: Option<TechStackCategoryMap>,
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub language: Option<TechStackCategoryMap>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub lib: Option<TechStackCategoryMap>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub tool: Option<TechStackCategoryMap>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub other: Option<TechStackCategoryMap>,
234}
235
236pub const DEFAULT_SHARED_LOCAL_BIND: &str = "127.0.0.1:4943"; pub const DEFAULT_PROJECT_LOCAL_BIND: &str = "127.0.0.1:8000";
238pub const DEFAULT_IC_GATEWAY: &str = "https://icp0.io";
239pub const DEFAULT_IC_GATEWAY_TRAILING_SLASH: &str = "https://icp0.io/";
240pub const DEFAULT_REPLICA_PORT: u16 = 8080;
241
242#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
245pub struct ConfigCanistersCanister {
246 #[serde(default)]
250 pub declarations: CanisterDeclarationsConfig,
251
252 #[serde(default)]
255 pub remote: Option<ConfigCanistersCanisterRemote>,
256
257 pub args: Option<String>,
260
261 #[serde(default)]
264 pub initialization_values: InitializationValues,
265
266 #[serde(default)]
269 pub dependencies: Vec<String>,
270
271 pub frontend: Option<BTreeMap<String, String>>,
275
276 #[serde(flatten)]
280 pub type_specific: CanisterTypeProperties,
281
282 #[serde(default)]
286 pub pre_install: SerdeVec<String>,
287
288 #[serde(default)]
292 pub post_install: SerdeVec<String>,
293
294 pub main: Option<PathBuf>,
297
298 pub shrink: Option<bool>,
303
304 #[serde(default)]
309 pub optimize: Option<WasmOptLevel>,
310
311 #[serde(default)]
314 pub metadata: Vec<CanisterMetadataSection>,
315
316 #[serde(default)]
319 pub pullable: Option<Pullable>,
320
321 #[serde(default)]
324 pub tech_stack: Option<TechStack>,
325
326 pub gzip: Option<bool>,
329
330 #[schemars(with = "Option<String>")]
335 pub specified_id: Option<Principal>,
336
337 pub init_arg: Option<String>,
341
342 pub init_arg_file: Option<String>,
346}
347
348#[derive(Clone, Debug, Serialize, JsonSchema)]
349#[serde(tag = "type", rename_all = "snake_case")]
350pub enum CanisterTypeProperties {
351 Rust {
353 package: String,
356
357 #[serde(rename = "crate")]
361 crate_name: Option<String>,
362
363 candid: PathBuf,
366
367 #[serde(default)]
370 skip_cargo_audit: bool,
371 },
372 Assets {
374 source: Vec<PathBuf>,
377
378 #[schemars(default)]
383 build: SerdeVec<String>,
384
385 workspace: Option<String>,
388 },
389 Custom {
391 wasm: String,
395
396 candid: String,
399
400 #[schemars(default)]
406 build: SerdeVec<String>,
407 },
408 Motoko,
410 Pull {
412 #[schemars(with = "String")]
415 id: Principal,
416 },
417}
418
419impl CanisterTypeProperties {
420 pub fn name(&self) -> &'static str {
421 match self {
422 Self::Rust { .. } => "rust",
423 Self::Motoko { .. } => "motoko",
424 Self::Assets { .. } => "assets",
425 Self::Custom { .. } => "custom",
426 Self::Pull { .. } => "pull",
427 }
428 }
429}
430
431#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
432#[serde(rename_all = "snake_case")]
433pub enum CanisterLogVisibility {
434 #[default]
435 Controllers,
436 Public,
437 #[schemars(with = "Vec::<String>")]
438 AllowedViewers(Vec<Principal>),
439}
440
441impl From<CanisterLogVisibility> for LogVisibility {
442 fn from(value: CanisterLogVisibility) -> Self {
443 match value {
444 CanisterLogVisibility::Controllers => LogVisibility::Controllers,
445 CanisterLogVisibility::Public => LogVisibility::Public,
446 CanisterLogVisibility::AllowedViewers(viewers) => {
447 LogVisibility::AllowedViewers(viewers)
448 }
449 }
450 }
451}
452
453#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
455#[serde(default)]
456pub struct InitializationValues {
457 pub compute_allocation: Option<PossiblyStr<u64>>,
461
462 #[schemars(with = "Option<ByteSchema>")]
466 pub memory_allocation: Option<Byte>,
467
468 #[serde(with = "humantime_serde")]
472 #[schemars(with = "Option<String>")]
473 pub freezing_threshold: Option<Duration>,
474
475 #[schemars(with = "Option<u128>")]
486 pub reserved_cycles_limit: Option<u128>,
487
488 #[schemars(with = "Option<ByteSchema>")]
499 pub wasm_memory_limit: Option<Byte>,
500
501 #[schemars(with = "Option<ByteSchema>")]
514 pub wasm_memory_threshold: Option<Byte>,
515
516 #[schemars(with = "Option<CanisterLogVisibility>")]
521 pub log_visibility: Option<CanisterLogVisibility>,
522}
523
524#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
528pub struct CanisterDeclarationsConfig {
529 pub output: Option<PathBuf>,
533
534 pub bindings: Option<Vec<String>>,
539
540 pub env_override: Option<String>,
544
545 #[serde(default)]
549 pub node_compatibility: bool,
550}
551
552#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
554pub struct ConfigDefaultsBitcoin {
555 #[serde(default)]
557 pub enabled: bool,
558
559 #[serde(default)]
562 pub nodes: Option<Vec<SocketAddr>>,
563
564 #[serde(default = "default_bitcoin_log_level")]
567 pub log_level: BitcoinAdapterLogLevel,
568
569 #[serde(default = "default_bitcoin_canister_init_arg")]
572 pub canister_init_arg: String,
573}
574
575pub fn default_bitcoin_log_level() -> BitcoinAdapterLogLevel {
576 BitcoinAdapterLogLevel::Info
577}
578
579pub fn default_bitcoin_canister_init_arg() -> String {
580 "(record { stability_threshold = 0 : nat; network = variant { regtest }; blocks_source = principal \"aaaaa-aa\"; fees = record { get_utxos_base = 50000000 : nat; get_utxos_cycles_per_ten_instructions = 10 : nat; get_utxos_maximum = 10000000000 : nat; get_balance = 10000000 : nat; get_balance_maximum = 100000000 : nat; get_block_headers_base = 50000000 : nat; get_block_headers_cycles_per_ten_instructions = 10 : nat; get_block_headers_maximum = 10000000000 : nat; get_current_fee_percentiles = 10000000 : nat; get_current_fee_percentiles_maximum = 100000000 : nat; send_transaction_base = 5000000000 : nat; send_transaction_per_byte = 20000000 : nat; }; syncing = variant { enabled }; api_access = variant { enabled }; disable_api_if_not_fully_synced = variant { enabled }})".to_string()
581}
582
583impl Default for ConfigDefaultsBitcoin {
584 fn default() -> Self {
585 ConfigDefaultsBitcoin {
586 enabled: false,
587 nodes: None,
588 log_level: default_bitcoin_log_level(),
589 canister_init_arg: default_bitcoin_canister_init_arg(),
590 }
591 }
592}
593
594#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
596pub struct ConfigDefaultsDogecoin {
597 #[serde(default)]
599 pub enabled: bool,
600
601 #[serde(default)]
604 pub nodes: Option<Vec<SocketAddr>>,
605}
606
607#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
609pub struct ConfigDefaultsCanisterHttp {
610 #[serde(default = "default_as_true")]
612 pub enabled: bool,
613
614 #[serde(default)]
617 pub log_level: HttpAdapterLogLevel,
618}
619
620impl Default for ConfigDefaultsCanisterHttp {
621 fn default() -> Self {
622 ConfigDefaultsCanisterHttp {
623 enabled: true,
624 log_level: HttpAdapterLogLevel::default(),
625 }
626 }
627}
628
629fn default_as_true() -> bool {
630 true
632}
633
634#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
637pub struct ConfigDefaultsBootstrap {
638 #[serde(default = "default_bootstrap_ip")]
640 pub ip: IpAddr,
641
642 #[serde(default = "default_bootstrap_port")]
644 pub port: u16,
645
646 #[serde(default = "default_bootstrap_timeout")]
649 pub timeout: u64,
650}
651
652pub fn default_bootstrap_ip() -> IpAddr {
653 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
654}
655pub fn default_bootstrap_port() -> u16 {
656 8081
657}
658pub fn default_bootstrap_timeout() -> u64 {
659 30
660}
661
662impl Default for ConfigDefaultsBootstrap {
663 fn default() -> Self {
664 ConfigDefaultsBootstrap {
665 ip: default_bootstrap_ip(),
666 port: default_bootstrap_port(),
667 timeout: default_bootstrap_timeout(),
668 }
669 }
670}
671
672#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
674pub struct ConfigDefaultsBuild {
675 pub packtool: Option<String>,
678
679 pub args: Option<String>,
681}
682
683#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
684#[serde(rename_all = "lowercase")]
685pub enum ReplicaLogLevel {
686 Critical,
687 Error,
688 Warning,
689 Info,
690 Debug,
691 Trace,
692}
693
694impl Default for ReplicaLogLevel {
695 fn default() -> Self {
696 Self::Error
697 }
698}
699
700impl ReplicaLogLevel {
701 pub fn to_ic_starter_string(&self) -> String {
702 match self {
703 Self::Critical => "critical".to_string(),
704 Self::Error => "error".to_string(),
705 Self::Warning => "warning".to_string(),
706 Self::Info => "info".to_string(),
707 Self::Debug => "debug".to_string(),
708 Self::Trace => "trace".to_string(),
709 }
710 }
711 pub fn to_pocketic_string(&self) -> String {
712 match self {
713 Self::Critical => "CRITICAL".to_string(),
714 Self::Error => "ERROR".to_string(),
715 Self::Warning => "WARN".to_string(),
716 Self::Info => "INFO".to_string(),
717 Self::Debug => "DEBUG".to_string(),
718 Self::Trace => "TRACE".to_string(),
719 }
720 }
721}
722
723#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
725pub struct ConfigDefaultsReplica {
726 pub port: Option<u16>,
728
729 pub subnet_type: Option<ReplicaSubnetType>,
734
735 pub log_level: Option<ReplicaLogLevel>,
737}
738
739#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
741pub struct ConfigDefaultsProxy {
742 pub domain: Option<SerdeVec<String>>,
744}
745
746#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
751#[serde(rename_all = "lowercase")]
752pub enum NetworkType {
753 #[default]
755 Ephemeral,
756
757 Persistent,
759}
760
761impl NetworkType {
762 fn ephemeral() -> Self {
763 NetworkType::Ephemeral
764 }
765 fn persistent() -> Self {
766 NetworkType::Persistent
767 }
768}
769
770#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
771#[serde(rename_all = "lowercase")]
772pub enum ReplicaSubnetType {
773 System,
774 #[default]
775 Application,
776 VerifiedApplication,
777}
778
779impl ReplicaSubnetType {
780 pub fn as_ic_starter_string(&self) -> String {
782 match self {
783 ReplicaSubnetType::System => "system".to_string(),
784 ReplicaSubnetType::Application => "application".to_string(),
785 ReplicaSubnetType::VerifiedApplication => "verified_application".to_string(),
786 }
787 }
788}
789
790fn default_playground_timeout_seconds() -> u64 {
791 MOTOKO_PLAYGROUND_CANISTER_TIMEOUT_SECONDS
792}
793
794#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
796pub struct PlaygroundConfig {
797 pub playground_canister: String,
799
800 #[serde(default = "default_playground_timeout_seconds")]
802 pub timeout_seconds: u64,
803}
804
805#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
807pub struct ConfigNetworkProvider {
808 pub providers: Vec<String>,
810
811 #[serde(default = "NetworkType::persistent")]
813 pub r#type: NetworkType,
814 pub playground: Option<PlaygroundConfig>,
815}
816
817#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
819pub struct ConfigLocalProvider {
820 pub bind: Option<String>,
824
825 #[serde(default = "NetworkType::ephemeral")]
827 pub r#type: NetworkType,
828
829 pub bitcoin: Option<ConfigDefaultsBitcoin>,
830 pub dogecoin: Option<ConfigDefaultsDogecoin>,
831 pub bootstrap: Option<ConfigDefaultsBootstrap>,
832 pub canister_http: Option<ConfigDefaultsCanisterHttp>,
833 pub replica: Option<ConfigDefaultsReplica>,
834 pub playground: Option<PlaygroundConfig>,
835 pub proxy: Option<ConfigDefaultsProxy>,
836}
837
838#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
839#[serde(untagged)]
840pub enum ConfigNetwork {
841 ConfigNetworkProvider(ConfigNetworkProvider),
842 ConfigLocalProvider(ConfigLocalProvider),
843}
844
845#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
846pub enum Profile {
847 Debug,
849 Release,
851}
852
853#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
855pub struct ConfigDefaults {
856 pub bitcoin: Option<ConfigDefaultsBitcoin>,
857 pub dogecoin: Option<ConfigDefaultsDogecoin>,
858 pub bootstrap: Option<ConfigDefaultsBootstrap>,
859 pub build: Option<ConfigDefaultsBuild>,
860 pub canister_http: Option<ConfigDefaultsCanisterHttp>,
861 pub proxy: Option<ConfigDefaultsProxy>,
862 pub replica: Option<ConfigDefaultsReplica>,
863}
864
865#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
867pub struct ConfigInterface {
868 pub profile: Option<Profile>,
869
870 pub version: Option<u32>,
872
873 pub dfx: Option<String>,
876
877 pub canisters: Option<BTreeMap<String, ConfigCanistersCanister>>,
879
880 pub defaults: Option<ConfigDefaults>,
882
883 pub networks: Option<BTreeMap<String, ConfigNetwork>>,
886
887 pub output_env_file: Option<PathBuf>,
889}
890
891pub type TopLevelConfigNetworks = BTreeMap<String, ConfigNetwork>;
892
893#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
894pub struct NetworksConfigInterface {
895 pub networks: TopLevelConfigNetworks,
896}
897
898impl ConfigCanistersCanister {}
899
900pub fn to_socket_addr(s: &str) -> Result<SocketAddr, SocketAddrConversionError> {
901 match s.to_socket_addrs() {
902 Ok(mut a) => match a.next() {
903 Some(res) => Ok(res),
904 None => Err(EmptyIterator(s.to_string())),
905 },
906 Err(err) => Err(ParseSocketAddrFailed(s.to_string(), err)),
907 }
908}
909
910impl ConfigDefaultsBuild {
911 pub fn get_packtool(&self) -> Option<String> {
912 match &self.packtool {
913 Some(v) if !v.is_empty() => self.packtool.to_owned(),
914 _ => None,
915 }
916 }
917 pub fn get_args(&self) -> Option<String> {
918 match &self.args {
919 Some(v) if !v.is_empty() => self.args.to_owned(),
920 _ => None,
921 }
922 }
923}
924
925impl ConfigDefaults {
926 pub fn get_build(&self) -> &ConfigDefaultsBuild {
927 match &self.build {
928 Some(x) => x,
929 None => &EMPTY_CONFIG_DEFAULTS_BUILD,
930 }
931 }
932}
933
934impl NetworksConfigInterface {
935 pub fn get_network(&self, name: &str) -> Option<&ConfigNetwork> {
936 self.networks.get(name)
937 }
938}
939
940impl ConfigInterface {
941 pub fn get_defaults(&self) -> &ConfigDefaults {
942 match &self.defaults {
943 Some(v) => v,
944 _ => &EMPTY_CONFIG_DEFAULTS,
945 }
946 }
947
948 pub fn get_network(&self, name: &str) -> Option<&ConfigNetwork> {
949 self.networks
950 .as_ref()
951 .and_then(|networks| networks.get(name))
952 }
953
954 pub fn get_version(&self) -> u32 {
955 self.version.unwrap_or(1)
956 }
957 pub fn get_dfx(&self) -> Option<String> {
958 self.dfx.to_owned()
959 }
960
961 pub fn get_canister_names_with_dependencies(
964 &self,
965 some_canister: Option<&str>,
966 ) -> Result<Vec<String>, GetCanisterNamesWithDependenciesError> {
967 self.canisters
968 .as_ref()
969 .ok_or(GetCanisterNamesWithDependenciesError::CanistersFieldDoesNotExist())
970 .and_then(|canister_map| match some_canister {
971 Some(specific_canister) => {
972 let mut names = HashSet::new();
973 let mut path = vec![];
974 add_dependencies(canister_map, &mut names, &mut path, specific_canister)
975 .map(|_| names.into_iter().collect())
976 .map_err(|err| AddDependenciesFailed(specific_canister.to_string(), err))
977 }
978 None => Ok(canister_map.keys().cloned().collect()),
979 })
980 }
981
982 pub fn get_remote_canister_id(
983 &self,
984 canister: &str,
985 network: &str,
986 ) -> Result<Option<Principal>, GetRemoteCanisterIdError> {
987 let maybe_principal = self
988 .get_canister_config(canister)
989 .map_err(|e| {
990 GetRemoteCanisterIdFailed(
991 Box::new(canister.to_string()),
992 Box::new(network.to_string()),
993 e,
994 )
995 })?
996 .remote
997 .as_ref()
998 .and_then(|r| r.id.get(network))
999 .copied();
1000
1001 Ok(maybe_principal)
1002 }
1003
1004 pub fn is_remote_canister(
1005 &self,
1006 canister: &str,
1007 network: &str,
1008 ) -> Result<bool, GetRemoteCanisterIdError> {
1009 Ok(self.get_remote_canister_id(canister, network)?.is_some())
1010 }
1011
1012 pub fn get_compute_allocation(
1013 &self,
1014 canister_name: &str,
1015 ) -> Result<Option<u64>, GetComputeAllocationError> {
1016 Ok(self
1017 .get_canister_config(canister_name)
1018 .map_err(|e| GetComputeAllocationFailed(canister_name.to_string(), e))?
1019 .initialization_values
1020 .compute_allocation
1021 .map(|x| x.0))
1022 }
1023
1024 pub fn get_memory_allocation(
1025 &self,
1026 canister_name: &str,
1027 ) -> Result<Option<Byte>, GetMemoryAllocationError> {
1028 Ok(self
1029 .get_canister_config(canister_name)
1030 .map_err(|e| GetMemoryAllocationFailed(canister_name.to_string(), e))?
1031 .initialization_values
1032 .memory_allocation)
1033 }
1034
1035 pub fn get_freezing_threshold(
1036 &self,
1037 canister_name: &str,
1038 ) -> Result<Option<Duration>, GetFreezingThresholdError> {
1039 Ok(self
1040 .get_canister_config(canister_name)
1041 .map_err(|e| GetFreezingThresholdFailed(canister_name.to_string(), e))?
1042 .initialization_values
1043 .freezing_threshold)
1044 }
1045
1046 pub fn get_reserved_cycles_limit(
1047 &self,
1048 canister_name: &str,
1049 ) -> Result<Option<u128>, GetReservedCyclesLimitError> {
1050 Ok(self
1051 .get_canister_config(canister_name)
1052 .map_err(|e| GetReservedCyclesLimitFailed(canister_name.to_string(), e))?
1053 .initialization_values
1054 .reserved_cycles_limit)
1055 }
1056
1057 pub fn get_wasm_memory_limit(
1058 &self,
1059 canister_name: &str,
1060 ) -> Result<Option<Byte>, GetWasmMemoryLimitError> {
1061 Ok(self
1062 .get_canister_config(canister_name)
1063 .map_err(|e| GetWasmMemoryLimitFailed(canister_name.to_string(), e))?
1064 .initialization_values
1065 .wasm_memory_limit)
1066 }
1067
1068 pub fn get_wasm_memory_threshold(
1069 &self,
1070 canister_name: &str,
1071 ) -> Result<Option<Byte>, GetWasmMemoryThresholdError> {
1072 Ok(self
1073 .get_canister_config(canister_name)
1074 .map_err(|e| GetWasmMemoryThresholdFailed(canister_name.to_string(), e))?
1075 .initialization_values
1076 .wasm_memory_threshold)
1077 }
1078
1079 pub fn get_log_visibility(
1080 &self,
1081 canister_name: &str,
1082 ) -> Result<Option<LogVisibility>, GetLogVisibilityError> {
1083 Ok(self
1084 .get_canister_config(canister_name)
1085 .map_err(|e| GetLogVisibilityFailed(canister_name.to_string(), e))?
1086 .initialization_values
1087 .log_visibility
1088 .clone()
1089 .map(|visibility| visibility.into()))
1090 }
1091
1092 fn get_canister_config(
1093 &self,
1094 canister_name: &str,
1095 ) -> Result<&ConfigCanistersCanister, GetCanisterConfigError> {
1096 self.canisters
1097 .as_ref()
1098 .ok_or(GetCanisterConfigError::CanistersFieldDoesNotExist())?
1099 .get(canister_name)
1100 .ok_or_else(|| GetCanisterConfigError::CanisterNotFound(canister_name.to_string()))
1101 }
1102
1103 pub fn get_pull_canisters(&self) -> Result<BTreeMap<String, Principal>, GetPullCanistersError> {
1104 let mut res = BTreeMap::new();
1105 let mut id_to_name: BTreeMap<Principal, &String> = BTreeMap::new();
1106 if let Some(map) = &self.canisters {
1107 for (k, v) in map {
1108 if let CanisterTypeProperties::Pull { id } = v.type_specific {
1109 if let Some(other_name) = id_to_name.get(&id) {
1110 return Err(PullCanistersSameId(other_name.to_string(), k.clone(), id));
1111 }
1112 res.insert(k.clone(), id);
1113 id_to_name.insert(id, k);
1114 }
1115 }
1116 };
1117 Ok(res)
1118 }
1119
1120 pub fn get_specified_id(
1121 &self,
1122 canister_name: &str,
1123 ) -> Result<Option<Principal>, GetSpecifiedIdError> {
1124 Ok(self
1125 .get_canister_config(canister_name)
1126 .map_err(|e| GetSpecifiedIdFailed(canister_name.to_string(), e))?
1127 .specified_id)
1128 }
1129}
1130
1131fn add_dependencies(
1132 all_canisters: &BTreeMap<String, ConfigCanistersCanister>,
1133 names: &mut HashSet<String>,
1134 path: &mut Vec<String>,
1135 canister_name: &str,
1136) -> Result<(), AddDependenciesError> {
1137 let inserted = names.insert(String::from(canister_name));
1138
1139 if !inserted {
1140 return if path.contains(&String::from(canister_name)) {
1141 path.push(String::from(canister_name));
1142 Err(CanisterCircularDependency(path.clone()))
1143 } else {
1144 Ok(())
1145 };
1146 }
1147
1148 let canister_config = all_canisters
1149 .get(canister_name)
1150 .ok_or_else(|| AddDependenciesError::CanisterNotFound(canister_name.to_string()))?;
1151
1152 path.push(String::from(canister_name));
1153
1154 for canister in &canister_config.dependencies {
1155 add_dependencies(all_canisters, names, path, canister)?;
1156 }
1157
1158 path.pop();
1159
1160 Ok(())
1161}
1162
1163#[derive(Clone, Debug)]
1164pub struct Config {
1165 path: PathBuf,
1166 json: Value,
1167 pub config: ConfigInterface,
1169}
1170
1171#[allow(dead_code)]
1172impl Config {
1173 fn resolve_config_path(working_dir: &Path) -> Result<Option<PathBuf>, CanonicalizePathError> {
1174 let mut curr = crate::fs::canonicalize(working_dir)?;
1175 while curr.parent().is_some() {
1176 if curr.join(CONFIG_FILE_NAME).is_file() {
1177 return Ok(Some(curr.join(CONFIG_FILE_NAME)));
1178 } else {
1179 curr.pop();
1180 }
1181 }
1182
1183 if curr.join(CONFIG_FILE_NAME).is_file() {
1185 return Ok(Some(curr.join(CONFIG_FILE_NAME)));
1186 }
1187
1188 Ok(None)
1189 }
1190
1191 fn from_file(
1192 path: &Path,
1193 extension_manager: Option<&ExtensionManager>,
1194 ) -> Result<Config, LoadDfxConfigError> {
1195 let content = crate::fs::read(path)?;
1196 Config::from_slice(path.to_path_buf(), &content, extension_manager)
1197 }
1198
1199 pub fn from_dir(
1200 working_dir: &Path,
1201 extension_manager: Option<&ExtensionManager>,
1202 ) -> Result<Option<Config>, LoadDfxConfigError> {
1203 let path = Config::resolve_config_path(working_dir).map_err(ResolveConfigPath)?;
1204 path.map(|path| Config::from_file(&path, extension_manager))
1205 .transpose()
1206 }
1207
1208 pub fn from_current_dir(
1209 extension_manager: Option<&ExtensionManager>,
1210 ) -> Result<Option<Config>, LoadDfxConfigError> {
1211 let working_dir = std::env::current_dir().map_err(DetermineCurrentWorkingDirFailed)?;
1212 Config::from_dir(&working_dir, extension_manager)
1213 }
1214
1215 fn from_slice(
1216 path: PathBuf,
1217 content: &[u8],
1218 extension_manager: Option<&ExtensionManager>,
1219 ) -> Result<Config, LoadDfxConfigError> {
1220 let json: Value = serde_json::from_slice(content)
1221 .map_err(|e| LoadDfxConfigError::DeserializeValueFailed(Box::new(path.clone()), e))?;
1222 let effective_json = apply_extension_canister_types(json.clone(), extension_manager)?;
1223
1224 let config = serde_json::from_value(effective_json)
1225 .map_err(|e| LoadDfxConfigError::DeserializeValueFailed(Box::new(path.clone()), e))?;
1226 Ok(Config { path, json, config })
1227 }
1228
1229 #[cfg(test)]
1231 pub(crate) fn from_str(content: &str) -> Result<Config, StructuredFileError> {
1232 Ok(Config::from_slice(PathBuf::from("-"), content.as_bytes(), None).unwrap())
1233 }
1234
1235 #[cfg(test)]
1236 pub(crate) fn from_str_and_path(
1237 path: PathBuf,
1238 content: &str,
1239 ) -> Result<Config, StructuredFileError> {
1240 Ok(Config::from_slice(path, content.as_bytes(), None).unwrap())
1241 }
1242
1243 pub fn get_path(&self) -> &PathBuf {
1244 &self.path
1245 }
1246 pub fn get_temp_path(&self) -> Result<PathBuf, GetTempPathError> {
1247 let path = self.get_path().parent().unwrap().join(".dfx");
1248 create_dir_all(&path)?;
1249 Ok(path)
1250 }
1251 pub fn get_json(&self) -> &Value {
1252 &self.json
1253 }
1254 pub fn get_mut_json(&mut self) -> &mut Value {
1255 &mut self.json
1256 }
1257 pub fn get_config(&self) -> &ConfigInterface {
1258 &self.config
1259 }
1260
1261 pub fn get_project_root(&self) -> &Path {
1262 self.path.parent().expect(
1266 "An incorrect configuration path was set with no parent, i.e. did not include root",
1267 )
1268 }
1269
1270 pub fn get_output_env_file(
1273 &self,
1274 from_cmdline: Option<PathBuf>,
1275 ) -> Result<Option<PathBuf>, GetOutputEnvFileError> {
1276 from_cmdline
1277 .or(self.config.output_env_file.clone())
1278 .map(|p| {
1279 if p.is_relative() {
1280 let p = self.get_project_root().join(p);
1281
1282 let env_parent = crate::fs::parent(&p)?;
1284 let env_parent = crate::fs::canonicalize(&env_parent)?;
1285 if !env_parent.starts_with(self.get_project_root()) {
1286 Err(GetOutputEnvFileError::OutputEnvFileMustBeInProjectRoot(p))
1287 } else {
1288 Ok(self.get_project_root().join(p))
1289 }
1290 } else {
1291 Err(GetOutputEnvFileError::OutputEnvFileMustBeRelative(p))
1292 }
1293 })
1294 .transpose()
1295 }
1296
1297 pub fn save(&self) -> Result<(), StructuredFileError> {
1298 save_json_file(&self.path, &self.json)
1299 }
1300}
1301
1302impl<'de> Deserialize<'de> for CanisterTypeProperties {
1304 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1305 where
1306 D: Deserializer<'de>,
1307 {
1308 deserializer.deserialize_map(PropertiesVisitor)
1309 }
1310}
1311
1312struct PropertiesVisitor;
1313
1314impl<'de> Visitor<'de> for PropertiesVisitor {
1315 type Value = CanisterTypeProperties;
1316 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1317 f.write_str("canister type metadata")
1318 }
1319 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1320 where
1321 A: MapAccess<'de>,
1322 {
1323 let missing_field = A::Error::missing_field;
1324 let mut wasm = None;
1325 let mut candid = None;
1326 let mut package = None;
1327 let mut skip_cargo_audit = None;
1328 let mut crate_name = None;
1329 let mut source = None;
1330 let mut build = None;
1331 let mut r#type = None;
1332 let mut id = None;
1333 let mut workspace = None;
1334 while let Some(key) = map.next_key::<String>()? {
1335 match &*key {
1336 "package" => package = Some(map.next_value()?),
1337 "crate" => crate_name = Some(map.next_value()?),
1338 "source" => source = Some(map.next_value()?),
1339 "candid" => candid = Some(map.next_value()?),
1340 "build" => build = Some(map.next_value()?),
1341 "wasm" => wasm = Some(map.next_value()?),
1342 "type" => r#type = Some(map.next_value::<String>()?),
1343 "id" => id = Some(map.next_value()?),
1344 "workspace" => workspace = Some(map.next_value()?),
1345 "skip_cargo_audit" => skip_cargo_audit = Some(map.next_value()?),
1346 _ => continue,
1347 }
1348 }
1349 let props = match r#type.as_deref() {
1350 Some("motoko") | None => CanisterTypeProperties::Motoko,
1351 Some("rust") => CanisterTypeProperties::Rust {
1352 candid: PathBuf::from(candid.ok_or_else(|| missing_field("candid"))?),
1353 package: package.ok_or_else(|| missing_field("package"))?,
1354 skip_cargo_audit: skip_cargo_audit.unwrap_or(false),
1355 crate_name,
1356 },
1357 Some("assets") => CanisterTypeProperties::Assets {
1358 source: source.ok_or_else(|| missing_field("source"))?,
1359 build: build.unwrap_or_default(),
1360 workspace,
1361 },
1362 Some("custom") => CanisterTypeProperties::Custom {
1363 build: build.unwrap_or_default(),
1364 candid: candid.ok_or_else(|| missing_field("candid"))?,
1365 wasm: wasm.ok_or_else(|| missing_field("wasm"))?,
1366 },
1367 Some("pull") => CanisterTypeProperties::Pull {
1368 id: id.ok_or_else(|| missing_field("id"))?,
1369 },
1370 Some(x) => return Err(A::Error::unknown_variant(x, &BUILTIN_CANISTER_TYPES)),
1371 };
1372 Ok(props)
1373 }
1374}
1375
1376#[derive(Clone)]
1377pub struct NetworksConfig {
1378 path: PathBuf,
1379 json: Value,
1380 networks_config: NetworksConfigInterface,
1382}
1383
1384impl NetworksConfig {
1385 pub fn get_path(&self) -> &PathBuf {
1386 &self.path
1387 }
1388 pub fn get_interface(&self) -> &NetworksConfigInterface {
1389 &self.networks_config
1390 }
1391
1392 pub fn new() -> Result<NetworksConfig, LoadNetworksConfigError> {
1393 let dir = get_user_dfx_config_dir().map_err(GetNetworkConfigPathFailed)?;
1394
1395 let path = dir.join("networks.json");
1396 if path.exists() {
1397 NetworksConfig::from_file(&path).map_err(LoadNetworkConfigFromFileFailed)
1398 } else {
1399 Ok(NetworksConfig {
1400 path,
1401 json: Default::default(),
1402 networks_config: NetworksConfigInterface {
1403 networks: BTreeMap::new(),
1404 },
1405 })
1406 }
1407 }
1408
1409 fn from_file(path: &Path) -> Result<NetworksConfig, StructuredFileError> {
1410 let content = crate::fs::read(path)?;
1411
1412 let networks: BTreeMap<String, ConfigNetwork> = serde_json::from_slice(&content)
1413 .map_err(|e| DeserializeJsonFileFailed(Box::new(path.to_path_buf()), e))?;
1414 let networks_config = NetworksConfigInterface { networks };
1415 let json = serde_json::from_slice(&content)
1416 .map_err(|e| DeserializeJsonFileFailed(Box::new(path.to_path_buf()), e))?;
1417 let path = PathBuf::from(path);
1418 Ok(NetworksConfig {
1419 path,
1420 json,
1421 networks_config,
1422 })
1423 }
1424}
1425
1426pub struct ToolConfig {
1427 path: PathBuf,
1428 json: Value,
1429 tool_config: ToolConfigInterface,
1430}
1431
1432#[derive(Serialize, Deserialize, JsonSchema)]
1433pub struct ToolConfigInterface {
1434 pub telemetry: TelemetryState,
1435}
1436
1437impl ToolConfig {
1438 pub fn path(&self) -> &PathBuf {
1439 &self.path
1440 }
1441 pub fn interface(&self) -> &ToolConfigInterface {
1442 &self.tool_config
1443 }
1444
1445 pub fn interface_mut(&mut self) -> &mut ToolConfigInterface {
1446 &mut self.tool_config
1447 }
1448
1449 pub fn new() -> Result<Self, ToolConfigError> {
1450 let dir = get_user_dfx_config_dir().map_err(GetToolConfigPathFailed)?;
1451
1452 let path = dir.join("config.json");
1453 if path.exists() {
1454 Self::from_file(&path).map_err(LoadToolConfigFromFileFailed)
1455 } else {
1456 let default = Self {
1457 path,
1458 json: Default::default(),
1459 tool_config: ToolConfigInterface {
1460 telemetry: TelemetryState::On,
1461 },
1462 };
1463 default.save()?;
1464 Ok(default)
1465 }
1466 }
1467
1468 pub fn save(&self) -> Result<(), ToolConfigError> {
1469 self.save_to_file(&self.path)
1470 .map_err(SaveDefaultConfigFailed)
1471 }
1472
1473 pub fn config_path(&self) -> &Path {
1474 &self.path
1475 }
1476
1477 fn from_file(path: &Path) -> Result<Self, StructuredFileError> {
1478 let json: Value = load_json_file(path)?;
1479 let tool_config: ToolConfigInterface = serde_json::from_value(json.clone())
1480 .map_err(|e| DeserializeJsonFileFailed(Box::new(path.to_path_buf()), e))?;
1481 let path = PathBuf::from(path);
1482 Ok(Self {
1483 path,
1484 json,
1485 tool_config,
1486 })
1487 }
1488
1489 fn save_to_file(&self, path: &Path) -> Result<(), StructuredFileError> {
1490 save_json_file(path, &self.tool_config)?;
1491 Ok(())
1492 }
1493}
1494
1495#[derive(Serialize, Deserialize, Copy, Clone, JsonSchema, PartialEq, Eq, ValueEnum)]
1496#[serde(rename_all = "snake_case")]
1497pub enum TelemetryState {
1498 On,
1499 Off,
1500 Local,
1501}
1502
1503impl TelemetryState {
1504 pub fn should_collect(&self) -> bool {
1505 *self != TelemetryState::Off
1506 }
1507 pub fn should_publish(&self) -> bool {
1508 *self == TelemetryState::On
1509 }
1510}
1511
1512impl Display for TelemetryState {
1513 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1514 Display::fmt(
1515 match self {
1516 Self::On => "on",
1517 Self::Off => "off",
1518 Self::Local => "local",
1519 },
1520 f,
1521 )
1522 }
1523}
1524
1525#[cfg(test)]
1526mod tests {
1527 use super::*;
1528
1529 #[test]
1530 fn find_dfinity_config_current_path() {
1531 let root_dir = tempfile::tempdir().unwrap();
1532 let root_path = root_dir.keep().canonicalize().unwrap();
1533 let config_path = root_path.join("foo/fah/bar").join(CONFIG_FILE_NAME);
1534
1535 std::fs::create_dir_all(config_path.parent().unwrap()).unwrap();
1536 std::fs::write(&config_path, "{}").unwrap();
1537
1538 assert_eq!(
1539 config_path,
1540 Config::resolve_config_path(config_path.parent().unwrap())
1541 .unwrap()
1542 .unwrap(),
1543 );
1544 }
1545
1546 #[test]
1547 fn find_dfinity_config_parent() {
1548 let root_dir = tempfile::tempdir().unwrap();
1549 let root_path = root_dir.keep().canonicalize().unwrap();
1550 let config_path = root_path.join("foo/fah/bar").join(CONFIG_FILE_NAME);
1551
1552 std::fs::create_dir_all(config_path.parent().unwrap()).unwrap();
1553 std::fs::write(&config_path, "{}").unwrap();
1554
1555 assert!(
1556 Config::resolve_config_path(config_path.parent().unwrap().parent().unwrap())
1557 .unwrap()
1558 .is_none()
1559 );
1560 }
1561
1562 #[test]
1563 fn find_dfinity_config_subdir() {
1564 let root_dir = tempfile::tempdir().unwrap();
1565 let root_path = root_dir.keep().canonicalize().unwrap();
1566 let config_path = root_path.join("foo/fah/bar").join(CONFIG_FILE_NAME);
1567 let subdir_path = config_path.parent().unwrap().join("baz/blue");
1568
1569 std::fs::create_dir_all(&subdir_path).unwrap();
1570 std::fs::write(&config_path, "{}").unwrap();
1571
1572 assert_eq!(
1573 config_path,
1574 Config::resolve_config_path(subdir_path.as_path())
1575 .unwrap()
1576 .unwrap(),
1577 );
1578 }
1579
1580 #[test]
1581 fn local_defaults_to_ephemeral() {
1582 let config = Config::from_str(
1583 r#"{
1584 "networks": {
1585 "local": {
1586 "bind": "localhost:8000"
1587 }
1588 }
1589 }"#,
1590 )
1591 .unwrap();
1592
1593 let network = config.get_config().get_network("local").unwrap();
1594 if let ConfigNetwork::ConfigLocalProvider(local_network) = network {
1595 assert_eq!(local_network.r#type, NetworkType::Ephemeral);
1596 } else {
1597 panic!("not a local provider");
1598 }
1599 }
1600
1601 #[test]
1602 fn local_can_override_to_persistent() {
1603 let config = Config::from_str(
1604 r#"{
1605 "networks": {
1606 "local": {
1607 "bind": "localhost:8000",
1608 "type": "persistent"
1609 }
1610 }
1611 }"#,
1612 )
1613 .unwrap();
1614
1615 let network = config.get_config().get_network("local").unwrap();
1616 if let ConfigNetwork::ConfigLocalProvider(local_network) = network {
1617 assert_eq!(local_network.r#type, NetworkType::Persistent);
1618 } else {
1619 panic!("not a local provider");
1620 }
1621 }
1622
1623 #[test]
1624 fn network_defaults_to_persistent() {
1625 let config = Config::from_str(
1626 r#"{
1627 "networks": {
1628 "somewhere": {
1629 "providers": [ "https://1.2.3.4:5000" ]
1630 }
1631 }
1632 }"#,
1633 )
1634 .unwrap();
1635
1636 let network = config.get_config().get_network("somewhere").unwrap();
1637 if let ConfigNetwork::ConfigNetworkProvider(network_provider) = network {
1638 assert_eq!(network_provider.r#type, NetworkType::Persistent);
1639 } else {
1640 panic!("not a network provider");
1641 }
1642 }
1643
1644 #[test]
1645 fn network_can_override_to_ephemeral() {
1646 let config = Config::from_str(
1647 r#"{
1648 "networks": {
1649 "staging": {
1650 "providers": [ "https://1.2.3.4:5000" ],
1651 "type": "ephemeral"
1652 }
1653 }
1654 }"#,
1655 )
1656 .unwrap();
1657
1658 let network = config.get_config().get_network("staging").unwrap();
1659 if let ConfigNetwork::ConfigNetworkProvider(network_provider) = network {
1660 assert_eq!(network_provider.r#type, NetworkType::Ephemeral);
1661 } else {
1662 panic!("not a network provider");
1663 }
1664
1665 assert_eq!(
1666 config.get_config().get_network("staging").unwrap(),
1667 &ConfigNetwork::ConfigNetworkProvider(ConfigNetworkProvider {
1668 providers: vec![String::from("https://1.2.3.4:5000")],
1669 r#type: NetworkType::Ephemeral,
1670 playground: None,
1671 })
1672 );
1673 }
1674
1675 #[test]
1676 fn get_correct_initialization_values() {
1677 let config = Config::from_str(
1678 r#"{
1679 "canisters": {
1680 "test_project": {
1681 "initialization_values": {
1682 "compute_allocation" : "100",
1683 "memory_allocation": "8GB"
1684 }
1685 }
1686 }
1687 }"#,
1688 )
1689 .unwrap();
1690
1691 let config_interface = config.get_config();
1692 let compute_allocation = config_interface
1693 .get_compute_allocation("test_project")
1694 .unwrap()
1695 .unwrap();
1696 assert_eq!(100, compute_allocation);
1697
1698 let memory_allocation = config_interface
1699 .get_memory_allocation("test_project")
1700 .unwrap()
1701 .unwrap();
1702 assert_eq!("8GB".parse::<Byte>().unwrap(), memory_allocation);
1703
1704 let config_no_values = Config::from_str(
1705 r#"{
1706 "canisters": {
1707 "test_project_two": {
1708 }
1709 }
1710 }"#,
1711 )
1712 .unwrap();
1713 let config_interface = config_no_values.get_config();
1714 let compute_allocation = config_interface
1715 .get_compute_allocation("test_project_two")
1716 .unwrap();
1717 let memory_allocation = config_interface
1718 .get_memory_allocation("test_project_two")
1719 .unwrap();
1720 assert_eq!(None, compute_allocation);
1721 assert_eq!(None, memory_allocation);
1722 }
1723
1724 #[test]
1725 fn tech_stack_category_deterministic_serialization() {
1726 let first = build_and_serialize();
1727 for _ in 0..10 {
1729 let next = build_and_serialize();
1730 assert_eq!(first, next);
1731 }
1732 }
1733
1734 fn build_and_serialize() -> String {
1735 use super::TechStackCategoryMap;
1736 use std::collections::BTreeMap;
1737 let mut data: TechStackCategoryMap = BTreeMap::new();
1738 data.insert("typescript".into(), BTreeMap::new());
1739 data.insert("javascript".into(), BTreeMap::new());
1740 serde_json::to_string(&data).unwrap()
1741 }
1742}