1use crate::{Config, extend, utils};
2use figment::{
3 Error, Figment, Metadata, Profile, Provider,
4 providers::{Env, Format, Toml},
5 value::{Dict, Map, Value},
6};
7use foundry_compilers::ProjectPathsConfig;
8use heck::ToSnakeCase;
9use std::{
10 cell::OnceCell,
11 path::{Path, PathBuf},
12};
13
14pub(crate) trait ProviderExt: Provider + Sized {
15 fn rename(
16 self,
17 from: impl Into<Profile>,
18 to: impl Into<Profile>,
19 ) -> RenameProfileProvider<Self> {
20 RenameProfileProvider::new(self, from, to)
21 }
22
23 fn wrap(
24 self,
25 wrapping_key: impl Into<Profile>,
26 profile: impl Into<Profile>,
27 ) -> WrapProfileProvider<Self> {
28 WrapProfileProvider::new(self, wrapping_key, profile)
29 }
30
31 fn strict_select(
32 self,
33 profiles: impl IntoIterator<Item = impl Into<Profile>>,
34 ) -> OptionalStrictProfileProvider<Self> {
35 OptionalStrictProfileProvider::new(self, profiles)
36 }
37
38 fn fallback(
39 self,
40 profile: impl Into<Profile>,
41 fallback: impl Into<Profile>,
42 ) -> FallbackProfileProvider<Self> {
43 FallbackProfileProvider::new(self, profile, fallback)
44 }
45
46 fn legacy_labels(self) -> LegacyLabelsProvider<Self> {
47 LegacyLabelsProvider(self)
48 }
49}
50
51impl<P: Provider> ProviderExt for P {}
52
53pub(crate) struct TomlFileProvider {
56 env_var: Option<&'static str>,
57 env_val: OnceCell<Option<String>>,
58 default: PathBuf,
59 cache: OnceCell<Result<Map<Profile, Dict>, Error>>,
60}
61
62impl TomlFileProvider {
63 pub(crate) const fn new(env_var: Option<&'static str>, default: PathBuf) -> Self {
64 Self { env_var, env_val: OnceCell::new(), default, cache: OnceCell::new() }
65 }
66
67 fn env_val(&self) -> Option<&str> {
68 self.env_val.get_or_init(|| self.env_var.and_then(Env::var)).as_deref()
69 }
70
71 pub(crate) fn file(&self) -> PathBuf {
72 self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone())
73 }
74
75 fn is_missing(&self) -> bool {
76 if let Some(file) = self.env_val() {
77 let path = Path::new(&file);
78 if !path.exists() {
79 return true;
80 }
81 }
82 false
83 }
84
85 fn read(&self) -> Result<Map<Profile, Dict>, Error> {
87 use serde::de::Error as _;
88
89 let local_path = self.file();
91 if !local_path.exists() {
92 if let Some(file) = self.env_val() {
93 return Err(Error::custom(format!(
94 "Config file `{}` set in env var `{}` does not exist",
95 file,
96 self.env_var.unwrap()
97 )));
98 }
99 return Ok(Map::new());
100 }
101
102 let local_provider = Toml::file(local_path.clone()).nested();
104
105 let local_path_str = local_path.to_string_lossy();
107 let local_content = std::fs::read_to_string(&local_path)
108 .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?;
109 let partial_config: extend::ExtendsPartialConfig = toml::from_str(&local_content)
110 .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?;
111
112 let selected_profile = Config::selected_profile();
114 let extends_config = partial_config.profile.as_ref().and_then(|profiles| {
115 let profile_str = selected_profile.to_string();
116 profiles.get(&profile_str).and_then(|cfg| cfg.extends.as_ref())
117 });
118
119 if let Some(extends_config) = extends_config {
121 let extends_path = extends_config.path();
122 let extends_strategy = extends_config.strategy();
123 let relative_base_path = PathBuf::from(extends_path);
124 let local_dir = local_path.parent().ok_or_else(|| {
125 Error::custom(format!(
126 "Could not determine parent directory of config file: {}",
127 local_path.display()
128 ))
129 })?;
130
131 let names_profile = partial_config
133 .profile
134 .as_ref()
135 .is_some_and(|profiles| profiles.contains_key(extends_path));
136 let base_path =
137 foundry_compilers::utils::canonicalize(local_dir.join(&relative_base_path))
138 .map_err(|e| {
139 let hint = if names_profile {
140 "; `extends` takes the path of another config file, and every \
141 profile already inherits from `[profile.default]`"
142 } else {
143 ""
144 };
145 Error::custom(format!(
146 "Failed to resolve inherited config path: {}: {e}{hint}",
147 relative_base_path.display()
148 ))
149 })?;
150
151 if !base_path.is_file() {
153 return Err(Error::custom(format!(
154 "Inherited config file does not exist or is not a file: {}",
155 base_path.display()
156 )));
157 }
158
159 if foundry_compilers::utils::canonicalize(&local_path).ok().as_ref() == Some(&base_path)
161 {
162 return Err(Error::custom(format!(
163 "Config file {} cannot inherit from itself.",
164 local_path.display()
165 )));
166 }
167
168 let base_path_str = base_path.to_string_lossy();
170 let base_content = std::fs::read_to_string(&base_path)
171 .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
172 let base_partial: extend::ExtendsPartialConfig = toml::from_str(&base_content)
173 .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
174
175 let base_extends = base_partial
177 .profile
178 .as_ref()
179 .and_then(|profiles| {
180 let profile_str = selected_profile.to_string();
181 profiles.get(&profile_str)
182 })
183 .and_then(|profile| profile.extends.as_ref());
184
185 if base_extends.is_some() {
187 return Err(Error::custom(format!(
188 "Nested inheritance is not allowed. Base file '{}' cannot have an 'extends' field in profile '{selected_profile}'.",
189 base_path.display()
190 )));
191 }
192
193 let base_provider = NormalizeSymbolicProvider::new(
196 NormalizeTracingProvider::new(
197 Toml::file(base_path).nested().legacy_labels(),
198 selected_profile.clone(),
199 ),
200 selected_profile.clone(),
201 );
202 let local_provider = NormalizeSymbolicProvider::new(
203 NormalizeTracingProvider::new(
204 local_provider.legacy_labels(),
205 selected_profile.clone(),
206 ),
207 selected_profile.clone(),
208 );
209
210 match extends_strategy {
212 extend::ExtendStrategy::ExtendArrays => {
213 Figment::new().merge(base_provider).admerge(local_provider).data()
218 }
219 extend::ExtendStrategy::ReplaceArrays => {
220 Figment::new().merge(base_provider).merge(local_provider).data()
224 }
225 extend::ExtendStrategy::NoCollision => {
226 let base_data = base_provider.data()?;
228 let local_data = local_provider.data()?;
229
230 let profile_key = Profile::new("profile");
231 if let (Some(local_profiles), Some(base_profiles)) =
232 (local_data.get(&profile_key), base_data.get(&profile_key))
233 {
234 let profile_str = selected_profile.to_string();
236 let base_dict = base_profiles.get(&profile_str).and_then(|v| v.as_dict());
237 let local_dict = local_profiles.get(&profile_str).and_then(|v| v.as_dict());
238
239 if let (Some(local_dict), Some(base_dict)) = (local_dict, base_dict) {
241 let collisions: Vec<&String> = local_dict
242 .keys()
243 .filter(|key| {
244 *key != "extends" && base_dict.contains_key(*key)
246 })
247 .collect();
248
249 if !collisions.is_empty() {
250 return Err(Error::custom(format!(
251 "Key collision detected in profile '{profile_str}' when extending '{extends_path}'. \
252 Conflicting keys: {collisions:?}. Use 'extends.strategy' or 'extends_strategy' to specify how to handle conflicts."
253 )));
254 }
255 }
256 }
257
258 Figment::new().merge(base_provider).merge(local_provider).data()
260 }
261 }
262 } else {
263 local_provider.data()
265 }
266 }
267}
268
269struct NormalizeSymbolicProvider<P> {
270 provider: P,
271 selected_profile: Profile,
272}
273
274impl<P> NormalizeSymbolicProvider<P> {
275 const fn new(provider: P, selected_profile: Profile) -> Self {
276 Self { provider, selected_profile }
277 }
278}
279
280struct NormalizeTracingProvider<P> {
281 provider: P,
282 selected_profile: Profile,
283}
284
285impl<P> NormalizeTracingProvider<P> {
286 const fn new(provider: P, selected_profile: Profile) -> Self {
287 Self { provider, selected_profile }
288 }
289}
290
291impl<P: Provider> Provider for NormalizeTracingProvider<P> {
292 fn metadata(&self) -> Metadata {
293 self.provider.metadata()
294 }
295
296 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
297 let mut data = self.provider.data()?;
298 normalize_tracing_section(&mut data, &self.selected_profile);
299 Ok(data)
300 }
301
302 fn profile(&self) -> Option<Profile> {
303 self.provider.profile()
304 }
305}
306
307fn normalize_tracing_section(data: &mut Map<Profile, Dict>, selected_profile: &Profile) {
311 let Some(tracing) = data.remove(&Profile::new("tracing")) else { return };
312
313 let profiles = data.entry(Profile::new(Config::PROFILE_SECTION)).or_default();
314 let profile =
315 profiles.entry(selected_profile.to_string()).or_insert_with(|| Value::from(Dict::new()));
316 let Value::Dict(_, profile) = profile else { return };
317
318 match (profile.get_mut("tracing"), tracing) {
319 (Some(Value::Dict(_, profile_tracing)), tracing) => {
320 merge_missing(profile_tracing, tracing);
321 }
322 (None, tracing) => {
323 profile.insert("tracing".to_string(), Value::from(tracing));
324 }
325 _ => {}
326 }
327}
328
329impl<P: Provider> Provider for NormalizeSymbolicProvider<P> {
330 fn metadata(&self) -> Metadata {
331 self.provider.metadata()
332 }
333
334 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
335 let mut data = self.provider.data()?;
336 normalize_symbolic_section(&mut data, &self.selected_profile);
337 Ok(data)
338 }
339
340 fn profile(&self) -> Option<Profile> {
341 self.provider.profile()
342 }
343}
344
345fn normalize_symbolic_section(data: &mut Map<Profile, Dict>, selected_profile: &Profile) {
351 let Some(symbolic) = data.remove(&Profile::new("symbolic")) else { return };
352
353 let profiles = data.entry(Profile::new(Config::PROFILE_SECTION)).or_default();
354 let profile =
355 profiles.entry(selected_profile.to_string()).or_insert_with(|| Value::from(Dict::new()));
356 let Value::Dict(_, profile) = profile else { return };
357
358 match (profile.get_mut("symbolic"), symbolic) {
359 (Some(Value::Dict(_, profile_symbolic)), symbolic) => {
360 merge_missing(profile_symbolic, symbolic);
361 }
362 (None, symbolic) => {
363 profile.insert("symbolic".to_string(), Value::from(symbolic));
364 }
365 _ => {}
366 }
367}
368
369fn merge_missing(target: &mut Dict, fallback: Dict) {
371 for (key, value) in fallback {
372 match (target.get_mut(&key), value) {
373 (Some(Value::Dict(_, target)), Value::Dict(_, fallback)) => {
374 merge_missing(target, fallback);
375 }
376 (None, value) => {
377 target.insert(key, value);
378 }
379 _ => {}
380 }
381 }
382}
383
384impl Provider for TomlFileProvider {
385 fn metadata(&self) -> Metadata {
386 if self.is_missing() {
387 Metadata::named("TOML file provider")
388 } else {
389 Toml::file(self.file()).nested().metadata()
390 }
391 }
392
393 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
394 self.cache.get_or_init(|| self.read()).clone()
395 }
396}
397
398pub(crate) struct ForcedSnakeCaseData<P>(pub(crate) P);
404
405impl<P: Provider> Provider for ForcedSnakeCaseData<P> {
406 fn metadata(&self) -> Metadata {
407 self.0.metadata()
408 }
409
410 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
411 let mut map = self.0.data()?;
412 for (profile, dict) in &mut map {
413 if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
414 continue;
416 }
417
418 if profile.as_str().as_str() == Config::PROFILE_SECTION {
419 let dict2 = std::mem::take(dict);
422 *dict = dict2
423 .into_iter()
424 .map(|(profile_name, v)| {
425 let v = snake_case_profile_keys(v);
427 (profile_name, v)
428 })
429 .collect();
430 continue;
431 }
432
433 let dict2 = std::mem::take(dict);
434 *dict = dict2.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect();
435 }
436 Ok(map)
437 }
438
439 fn profile(&self) -> Option<Profile> {
440 self.0.profile()
441 }
442}
443
444fn snake_case_profile_keys(value: Value) -> Value {
446 match value {
447 Value::Dict(tag, dict) => {
448 let new_dict = dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect();
449 Value::Dict(tag, new_dict)
450 }
451 other => other,
452 }
453}
454
455pub(crate) struct BackwardsCompatTomlProvider<P>(pub(crate) P);
457
458impl<P: Provider> Provider for BackwardsCompatTomlProvider<P> {
459 fn metadata(&self) -> Metadata {
460 self.0.metadata()
461 }
462
463 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
464 let mut map = self.0.data()?;
465 let solc_env = std::env::var("FOUNDRY_SOLC_VERSION")
466 .or_else(|_| std::env::var("DAPP_SOLC_VERSION"))
467 .map(Value::from)
468 .ok();
469 for (profile, dict) in &mut map {
470 if profile.as_str().as_str() == Config::PROFILE_SECTION {
471 for value in dict.values_mut() {
472 if let Value::Dict(_, dict) = value {
473 rewrite_legacy_profile_keys(dict, solc_env.as_ref());
474 }
475 }
476 } else if !Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
477 rewrite_legacy_profile_keys(dict, solc_env.as_ref());
478 }
479 }
480 normalize_legacy_labels(&mut map);
481 Ok(map)
482 }
483
484 fn profile(&self) -> Option<Profile> {
485 self.0.profile()
486 }
487}
488
489fn rewrite_legacy_profile_keys(dict: &mut Dict, solc_env: Option<&Value>) {
491 if let Some(v) = solc_env {
492 dict.insert("solc".to_string(), v.clone());
494 } else if let Some(v) = dict.remove("solc_version") {
495 dict.entry("solc".to_string()).or_insert(v);
497 }
498 if let Some(v) = dict.remove("deny_warnings") {
499 dict.entry("deny".to_string()).or_insert(v);
500 }
501}
502
503pub(crate) struct IgnoreRootProvider<P>(pub(crate) P);
507
508impl<P: Provider> Provider for IgnoreRootProvider<P> {
509 fn metadata(&self) -> Metadata {
510 self.0.metadata()
511 }
512
513 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
514 let mut map = self.0.data()?;
515 for (profile, dict) in &mut map {
516 if profile.as_str().as_str() != Config::PROFILE_SECTION {
518 dict.remove("root");
519 }
520 }
521 Ok(map)
522 }
523
524 fn profile(&self) -> Option<Profile> {
525 self.0.profile()
526 }
527}
528
529pub(crate) struct LegacyLabelsProvider<P>(pub(crate) P);
531
532impl<P: Provider> Provider for LegacyLabelsProvider<P> {
533 fn metadata(&self) -> Metadata {
534 self.0.metadata()
535 }
536
537 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
538 let mut map = self.0.data()?;
539 normalize_legacy_labels(&mut map);
540 Ok(map)
541 }
542
543 fn profile(&self) -> Option<Profile> {
544 self.0.profile()
545 }
546}
547
548pub(crate) fn normalize_legacy_labels(map: &mut Map<Profile, Dict>) {
549 if let Some(labels) = map.get(&Profile::new("labels")).cloned() {
550 merge_tracing_labels(&labels, map.entry(Profile::new("tracing")).or_default());
551 }
552
553 for (profile, dict) in map {
554 if profile.as_str().as_str() == Config::PROFILE_SECTION {
555 for value in dict.values_mut() {
556 if let Value::Dict(_, profile) = value {
557 normalize_legacy_labels_in_profile(profile);
558 }
559 }
560 } else if !Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
561 normalize_legacy_labels_in_profile(dict);
562 }
563 }
564}
565
566pub(crate) fn normalize_legacy_labels_in_profile(dict: &mut Dict) {
567 let Some(Value::Dict(_, labels)) = dict.get("labels").cloned() else { return };
568 let tracing = dict.entry("tracing".to_string()).or_insert_with(|| Dict::new().into());
569 if let Value::Dict(_, tracing) = tracing {
570 merge_tracing_labels(&labels, tracing);
571 }
572}
573
574fn merge_tracing_labels(legacy: &Dict, tracing: &mut Dict) {
575 match tracing.get("labels") {
576 Some(Value::Dict(_, configured)) => {
577 let mut labels = legacy.clone();
578 labels.extend(configured.clone());
579 tracing.insert("labels".to_string(), labels.into());
580 }
581 Some(_) => {}
582 None => {
583 tracing.insert("labels".to_string(), legacy.clone().into());
584 }
585 }
586}
587
588pub(crate) struct DappHardhatDirProvider<'a> {
590 pub(crate) root: &'a Path,
591 pub(crate) detect_src: bool,
592}
593
594impl Provider for DappHardhatDirProvider<'_> {
595 fn metadata(&self) -> Metadata {
596 Metadata::named("Dapp Hardhat dir compat")
597 }
598
599 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
600 let mut dict = Dict::new();
601 if self.detect_src {
602 dict.insert(
603 "src".to_string(),
604 ProjectPathsConfig::find_source_dir(self.root)
605 .file_name()
606 .unwrap()
607 .to_string_lossy()
608 .to_string()
609 .into(),
610 );
611 }
612 dict.insert(
613 "out".to_string(),
614 ProjectPathsConfig::find_artifacts_dir(self.root)
615 .file_name()
616 .unwrap()
617 .to_string_lossy()
618 .to_string()
619 .into(),
620 );
621
622 let mut libs = vec![];
627 let node_modules = self.root.join("node_modules");
628 let lib = self.root.join("lib");
629 if node_modules.exists() {
630 if lib.exists() {
631 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
632 }
633 libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string());
634 } else {
635 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
636 }
637
638 dict.insert("libs".to_string(), libs.into());
639
640 Ok(Map::from([(Config::selected_profile(), dict)]))
641 }
642}
643
644pub(crate) struct DappEnvCompatProvider;
646
647impl Provider for DappEnvCompatProvider {
648 fn metadata(&self) -> Metadata {
649 Metadata::named("Dapp env compat")
650 }
651
652 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
653 use serde::de::Error as _;
654 use std::env;
655
656 let mut dict = Dict::new();
657 if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
658 dict.insert(
659 "block_number".to_string(),
660 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
661 );
662 }
663 if let Ok(val) = env::var("DAPP_TEST_ADDRESS") {
664 dict.insert("sender".to_string(), val.into());
665 }
666 if let Ok(val) = env::var("DAPP_FORK_BLOCK") {
667 dict.insert(
668 "fork_block_number".to_string(),
669 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
670 );
671 } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
672 dict.insert(
673 "fork_block_number".to_string(),
674 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
675 );
676 }
677 if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") {
678 dict.insert(
679 "block_timestamp".to_string(),
680 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
681 );
682 }
683 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") {
684 dict.insert(
685 "optimizer_runs".to_string(),
686 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
687 );
688 }
689 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") {
690 let val = val.parse::<u8>().map_err(figment::Error::custom)?;
692 if val > 1 {
693 return Err(
694 format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into()
695 );
696 }
697 dict.insert("optimizer".to_string(), (val == 1).into());
698 }
699
700 if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) {
702 dict.insert("libraries".to_string(), utils::to_array_value(&val)?);
703 }
704
705 let mut fuzz_dict = Dict::new();
706 if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") {
707 fuzz_dict.insert(
708 "runs".to_string(),
709 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
710 );
711 }
712 dict.insert("fuzz".to_string(), fuzz_dict.into());
713
714 let mut invariant_dict = Dict::new();
715 if let Ok(val) = env::var("DAPP_TEST_DEPTH") {
716 invariant_dict.insert(
717 "depth".to_string(),
718 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
719 );
720 }
721 dict.insert("invariant".to_string(), invariant_dict.into());
722
723 Ok(Map::from([(Config::selected_profile(), dict)]))
724 }
725}
726
727pub(crate) struct RenameProfileProvider<P> {
743 provider: P,
744 from: Profile,
745 to: Profile,
746}
747
748impl<P> RenameProfileProvider<P> {
749 pub(crate) fn new(provider: P, from: impl Into<Profile>, to: impl Into<Profile>) -> Self {
750 Self { provider, from: from.into(), to: to.into() }
751 }
752}
753
754impl<P: Provider> Provider for RenameProfileProvider<P> {
755 fn metadata(&self) -> Metadata {
756 self.provider.metadata()
757 }
758
759 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
760 let mut data = self.provider.data()?;
761 if let Some(data) = data.remove(&self.from) {
762 return Ok(Map::from([(self.to.clone(), data)]));
763 }
764 Ok(Default::default())
765 }
766
767 fn profile(&self) -> Option<Profile> {
768 Some(self.to.clone())
769 }
770}
771
772struct UnwrapProfileProvider<P> {
788 provider: P,
789 wrapping_key: Profile,
790 profile: Profile,
791}
792
793impl<P> UnwrapProfileProvider<P> {
794 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
795 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
796 }
797}
798
799impl<P: Provider> Provider for UnwrapProfileProvider<P> {
800 fn metadata(&self) -> Metadata {
801 self.provider.metadata()
802 }
803
804 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
805 let mut data = self.provider.data()?;
806 if let Some(profiles) = data.remove(&self.wrapping_key) {
807 for (profile_str, profile_val) in profiles {
808 let profile = Profile::new(&profile_str);
809 if profile != self.profile {
810 continue;
811 }
812 match profile_val {
813 Value::Dict(_, dict) => return Ok(profile.collect(dict)),
814 bad_val => {
815 let mut err = Error::from(figment::error::Kind::InvalidType(
816 bad_val.to_actual(),
817 "dict".into(),
818 ));
819 err.metadata = Some(self.provider.metadata());
820 err.profile = Some(self.profile.clone());
821 return Err(err);
822 }
823 }
824 }
825 }
826 Ok(Default::default())
827 }
828
829 fn profile(&self) -> Option<Profile> {
830 Some(self.profile.clone())
831 }
832}
833
834pub(crate) struct WrapProfileProvider<P> {
850 provider: P,
851 wrapping_key: Profile,
852 profile: Profile,
853}
854
855impl<P> WrapProfileProvider<P> {
856 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
857 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
858 }
859}
860
861impl<P: Provider> Provider for WrapProfileProvider<P> {
862 fn metadata(&self) -> Metadata {
863 self.provider.metadata()
864 }
865
866 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
867 if let Some(inner) = self.provider.data()?.remove(&self.profile) {
868 let value = Value::from(inner);
869 let mut dict = Dict::new();
870 dict.insert(self.profile.as_str().as_str().to_snake_case(), value);
871 Ok(self.wrapping_key.collect(dict))
872 } else {
873 Ok(Default::default())
874 }
875 }
876
877 fn profile(&self) -> Option<Profile> {
878 Some(self.profile.clone())
879 }
880}
881
882pub(crate) struct OptionalStrictProfileProvider<P> {
905 provider: P,
906 profiles: Vec<Profile>,
907}
908
909impl<P> OptionalStrictProfileProvider<P> {
910 pub const PROFILE_PROFILE: Profile = Profile::const_new("profile");
911
912 pub fn new(provider: P, profiles: impl IntoIterator<Item = impl Into<Profile>>) -> Self {
913 Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() }
914 }
915}
916
917impl<P: Provider> Provider for OptionalStrictProfileProvider<P> {
918 fn metadata(&self) -> Metadata {
919 self.provider.metadata()
920 }
921
922 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
923 let mut figment = Figment::from(&self.provider);
924 for profile in &self.profiles {
925 figment = figment.merge(UnwrapProfileProvider::new(
926 &self.provider,
927 Self::PROFILE_PROFILE,
928 profile.clone(),
929 ));
930 }
931 figment.data().map_err(|err| {
932 if let Err(root_err) = self.provider.data() {
937 return root_err;
938 }
939 err
940 })
941 }
942
943 fn profile(&self) -> Option<Profile> {
944 self.profiles.last().cloned()
945 }
946}
947
948pub struct FallbackProfileProvider<P> {
951 provider: P,
952 profile: Profile,
953 fallback: Profile,
954}
955
956impl<P> FallbackProfileProvider<P> {
957 pub fn new(provider: P, profile: impl Into<Profile>, fallback: impl Into<Profile>) -> Self {
959 Self { provider, profile: profile.into(), fallback: fallback.into() }
960 }
961}
962
963impl<P: Provider> Provider for FallbackProfileProvider<P> {
964 fn metadata(&self) -> Metadata {
965 self.provider.metadata()
966 }
967
968 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
969 let mut data = self.provider.data()?;
970 let invariant_corpus_random_sequence_weight_configured = self.profile == "invariant"
971 && data
972 .get(&self.profile)
973 .is_some_and(|inner| inner.contains_key("corpus_random_sequence_weight"));
974 let mark_invariant_corpus_random_sequence_weight_configured =
975 |inner: &mut Dict| -> Result<(), Error> {
976 if invariant_corpus_random_sequence_weight_configured {
977 inner.insert(
978 "corpus_random_sequence_weight_configured".to_string(),
979 Value::serialize(true)?,
980 );
981 }
982 Ok(())
983 };
984
985 if let Some(fallback) = data.remove(&self.fallback) {
986 let mut inner = data.remove(&self.profile).unwrap_or_default();
987 for (k, v) in fallback {
988 inner.entry(k).or_insert(v);
989 }
990 mark_invariant_corpus_random_sequence_weight_configured(&mut inner)?;
991 Ok(self.profile.collect(inner))
992 } else {
993 if let Some(inner) = data.get_mut(&self.profile) {
994 mark_invariant_corpus_random_sequence_weight_configured(inner)?;
995 }
996 Ok(data)
997 }
998 }
999
1000 fn profile(&self) -> Option<Profile> {
1001 Some(self.profile.clone())
1002 }
1003}