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 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 base_path =
132 foundry_compilers::utils::canonicalize(local_dir.join(&relative_base_path))
133 .map_err(|e| {
134 Error::custom(format!(
135 "Failed to resolve inherited config path: {}: {e}",
136 relative_base_path.display()
137 ))
138 })?;
139
140 if !base_path.is_file() {
142 return Err(Error::custom(format!(
143 "Inherited config file does not exist or is not a file: {}",
144 base_path.display()
145 )));
146 }
147
148 if foundry_compilers::utils::canonicalize(&local_path).ok().as_ref() == Some(&base_path)
150 {
151 return Err(Error::custom(format!(
152 "Config file {} cannot inherit from itself.",
153 local_path.display()
154 )));
155 }
156
157 let base_path_str = base_path.to_string_lossy();
159 let base_content = std::fs::read_to_string(&base_path)
160 .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
161 let base_partial: extend::ExtendsPartialConfig = toml::from_str(&base_content)
162 .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
163
164 let base_extends = base_partial
166 .profile
167 .as_ref()
168 .and_then(|profiles| {
169 let profile_str = selected_profile.to_string();
170 profiles.get(&profile_str)
171 })
172 .and_then(|profile| profile.extends.as_ref());
173
174 if base_extends.is_some() {
176 return Err(Error::custom(format!(
177 "Nested inheritance is not allowed. Base file '{}' cannot have an 'extends' field in profile '{selected_profile}'.",
178 base_path.display()
179 )));
180 }
181
182 let base_provider = NormalizeSymbolicProvider::new(
185 NormalizeTracingProvider::new(
186 Toml::file(base_path).nested().legacy_labels(),
187 selected_profile.clone(),
188 ),
189 selected_profile.clone(),
190 );
191 let local_provider = NormalizeSymbolicProvider::new(
192 NormalizeTracingProvider::new(
193 local_provider.legacy_labels(),
194 selected_profile.clone(),
195 ),
196 selected_profile.clone(),
197 );
198
199 match extends_strategy {
201 extend::ExtendStrategy::ExtendArrays => {
202 Figment::new().merge(base_provider).admerge(local_provider).data()
207 }
208 extend::ExtendStrategy::ReplaceArrays => {
209 Figment::new().merge(base_provider).merge(local_provider).data()
213 }
214 extend::ExtendStrategy::NoCollision => {
215 let base_data = base_provider.data()?;
217 let local_data = local_provider.data()?;
218
219 let profile_key = Profile::new("profile");
220 if let (Some(local_profiles), Some(base_profiles)) =
221 (local_data.get(&profile_key), base_data.get(&profile_key))
222 {
223 let profile_str = selected_profile.to_string();
225 let base_dict = base_profiles.get(&profile_str).and_then(|v| v.as_dict());
226 let local_dict = local_profiles.get(&profile_str).and_then(|v| v.as_dict());
227
228 if let (Some(local_dict), Some(base_dict)) = (local_dict, base_dict) {
230 let collisions: Vec<&String> = local_dict
231 .keys()
232 .filter(|key| {
233 *key != "extends" && base_dict.contains_key(*key)
235 })
236 .collect();
237
238 if !collisions.is_empty() {
239 return Err(Error::custom(format!(
240 "Key collision detected in profile '{profile_str}' when extending '{extends_path}'. \
241 Conflicting keys: {collisions:?}. Use 'extends.strategy' or 'extends_strategy' to specify how to handle conflicts."
242 )));
243 }
244 }
245 }
246
247 Figment::new().merge(base_provider).merge(local_provider).data()
249 }
250 }
251 } else {
252 local_provider.data()
254 }
255 }
256}
257
258struct NormalizeSymbolicProvider<P> {
259 provider: P,
260 selected_profile: Profile,
261}
262
263impl<P> NormalizeSymbolicProvider<P> {
264 const fn new(provider: P, selected_profile: Profile) -> Self {
265 Self { provider, selected_profile }
266 }
267}
268
269struct NormalizeTracingProvider<P> {
270 provider: P,
271 selected_profile: Profile,
272}
273
274impl<P> NormalizeTracingProvider<P> {
275 const fn new(provider: P, selected_profile: Profile) -> Self {
276 Self { provider, selected_profile }
277 }
278}
279
280impl<P: Provider> Provider for NormalizeTracingProvider<P> {
281 fn metadata(&self) -> Metadata {
282 self.provider.metadata()
283 }
284
285 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
286 let mut data = self.provider.data()?;
287 normalize_tracing_section(&mut data, &self.selected_profile);
288 Ok(data)
289 }
290
291 fn profile(&self) -> Option<Profile> {
292 self.provider.profile()
293 }
294}
295
296fn normalize_tracing_section(data: &mut Map<Profile, Dict>, selected_profile: &Profile) {
300 let Some(tracing) = data.remove(&Profile::new("tracing")) else { return };
301
302 let profiles = data.entry(Profile::new(Config::PROFILE_SECTION)).or_default();
303 let profile =
304 profiles.entry(selected_profile.to_string()).or_insert_with(|| Value::from(Dict::new()));
305 let Value::Dict(_, profile) = profile else { return };
306
307 match (profile.get_mut("tracing"), tracing) {
308 (Some(Value::Dict(_, profile_tracing)), tracing) => {
309 merge_missing(profile_tracing, tracing);
310 }
311 (None, tracing) => {
312 profile.insert("tracing".to_string(), Value::from(tracing));
313 }
314 _ => {}
315 }
316}
317
318impl<P: Provider> Provider for NormalizeSymbolicProvider<P> {
319 fn metadata(&self) -> Metadata {
320 self.provider.metadata()
321 }
322
323 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
324 let mut data = self.provider.data()?;
325 normalize_symbolic_section(&mut data, &self.selected_profile);
326 Ok(data)
327 }
328
329 fn profile(&self) -> Option<Profile> {
330 self.provider.profile()
331 }
332}
333
334fn normalize_symbolic_section(data: &mut Map<Profile, Dict>, selected_profile: &Profile) {
340 let Some(symbolic) = data.remove(&Profile::new("symbolic")) else { return };
341
342 let profiles = data.entry(Profile::new(Config::PROFILE_SECTION)).or_default();
343 let profile =
344 profiles.entry(selected_profile.to_string()).or_insert_with(|| Value::from(Dict::new()));
345 let Value::Dict(_, profile) = profile else { return };
346
347 match (profile.get_mut("symbolic"), symbolic) {
348 (Some(Value::Dict(_, profile_symbolic)), symbolic) => {
349 merge_missing(profile_symbolic, symbolic);
350 }
351 (None, symbolic) => {
352 profile.insert("symbolic".to_string(), Value::from(symbolic));
353 }
354 _ => {}
355 }
356}
357
358fn merge_missing(target: &mut Dict, fallback: Dict) {
360 for (key, value) in fallback {
361 match (target.get_mut(&key), value) {
362 (Some(Value::Dict(_, target)), Value::Dict(_, fallback)) => {
363 merge_missing(target, fallback);
364 }
365 (None, value) => {
366 target.insert(key, value);
367 }
368 _ => {}
369 }
370 }
371}
372
373impl Provider for TomlFileProvider {
374 fn metadata(&self) -> Metadata {
375 if self.is_missing() {
376 Metadata::named("TOML file provider")
377 } else {
378 Toml::file(self.file()).nested().metadata()
379 }
380 }
381
382 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
383 self.cache.get_or_init(|| self.read()).clone()
384 }
385}
386
387pub(crate) struct ForcedSnakeCaseData<P>(pub(crate) P);
393
394impl<P: Provider> Provider for ForcedSnakeCaseData<P> {
395 fn metadata(&self) -> Metadata {
396 self.0.metadata()
397 }
398
399 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
400 let mut map = self.0.data()?;
401 for (profile, dict) in &mut map {
402 if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
403 continue;
405 }
406
407 if profile.as_str().as_str() == Config::PROFILE_SECTION {
408 let dict2 = std::mem::take(dict);
411 *dict = dict2
412 .into_iter()
413 .map(|(profile_name, v)| {
414 let v = snake_case_profile_keys(v);
416 (profile_name, v)
417 })
418 .collect();
419 continue;
420 }
421
422 let dict2 = std::mem::take(dict);
423 *dict = dict2.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect();
424 }
425 Ok(map)
426 }
427
428 fn profile(&self) -> Option<Profile> {
429 self.0.profile()
430 }
431}
432
433fn snake_case_profile_keys(value: Value) -> Value {
435 match value {
436 Value::Dict(tag, dict) => {
437 let new_dict = dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect();
438 Value::Dict(tag, new_dict)
439 }
440 other => other,
441 }
442}
443
444pub(crate) struct BackwardsCompatTomlProvider<P>(pub(crate) P);
446
447impl<P: Provider> Provider for BackwardsCompatTomlProvider<P> {
448 fn metadata(&self) -> Metadata {
449 self.0.metadata()
450 }
451
452 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
453 let mut map = self.0.data()?;
454 let solc_env = std::env::var("FOUNDRY_SOLC_VERSION")
455 .or_else(|_| std::env::var("DAPP_SOLC_VERSION"))
456 .map(Value::from)
457 .ok();
458 for (profile, dict) in &mut map {
459 if profile.as_str().as_str() == Config::PROFILE_SECTION {
460 for value in dict.values_mut() {
461 if let Value::Dict(_, dict) = value {
462 rewrite_legacy_profile_keys(dict, solc_env.as_ref());
463 }
464 }
465 } else if !Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
466 rewrite_legacy_profile_keys(dict, solc_env.as_ref());
467 }
468 }
469 normalize_legacy_labels(&mut map);
470 Ok(map)
471 }
472
473 fn profile(&self) -> Option<Profile> {
474 self.0.profile()
475 }
476}
477
478fn rewrite_legacy_profile_keys(dict: &mut Dict, solc_env: Option<&Value>) {
480 if let Some(v) = solc_env {
481 dict.insert("solc".to_string(), v.clone());
483 } else if let Some(v) = dict.remove("solc_version") {
484 dict.entry("solc".to_string()).or_insert(v);
486 }
487 if let Some(v) = dict.remove("deny_warnings") {
488 dict.entry("deny".to_string()).or_insert(v);
489 }
490}
491
492pub(crate) struct IgnoreRootProvider<P>(pub(crate) P);
496
497impl<P: Provider> Provider for IgnoreRootProvider<P> {
498 fn metadata(&self) -> Metadata {
499 self.0.metadata()
500 }
501
502 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
503 let mut map = self.0.data()?;
504 for (profile, dict) in &mut map {
505 if profile.as_str().as_str() != Config::PROFILE_SECTION {
507 dict.remove("root");
508 }
509 }
510 Ok(map)
511 }
512
513 fn profile(&self) -> Option<Profile> {
514 self.0.profile()
515 }
516}
517
518pub(crate) struct LegacyLabelsProvider<P>(pub(crate) P);
520
521impl<P: Provider> Provider for LegacyLabelsProvider<P> {
522 fn metadata(&self) -> Metadata {
523 self.0.metadata()
524 }
525
526 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
527 let mut map = self.0.data()?;
528 normalize_legacy_labels(&mut map);
529 Ok(map)
530 }
531
532 fn profile(&self) -> Option<Profile> {
533 self.0.profile()
534 }
535}
536
537pub(crate) fn normalize_legacy_labels(map: &mut Map<Profile, Dict>) {
538 if let Some(labels) = map.get(&Profile::new("labels")).cloned() {
539 merge_tracing_labels(&labels, map.entry(Profile::new("tracing")).or_default());
540 }
541
542 for (profile, dict) in map {
543 if profile.as_str().as_str() == Config::PROFILE_SECTION {
544 for value in dict.values_mut() {
545 if let Value::Dict(_, profile) = value {
546 normalize_legacy_labels_in_profile(profile);
547 }
548 }
549 } else if !Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
550 normalize_legacy_labels_in_profile(dict);
551 }
552 }
553}
554
555pub(crate) fn normalize_legacy_labels_in_profile(dict: &mut Dict) {
556 let Some(Value::Dict(_, labels)) = dict.get("labels").cloned() else { return };
557 let tracing = dict.entry("tracing".to_string()).or_insert_with(|| Dict::new().into());
558 if let Value::Dict(_, tracing) = tracing {
559 merge_tracing_labels(&labels, tracing);
560 }
561}
562
563fn merge_tracing_labels(legacy: &Dict, tracing: &mut Dict) {
564 match tracing.get("labels") {
565 Some(Value::Dict(_, configured)) => {
566 let mut labels = legacy.clone();
567 labels.extend(configured.clone());
568 tracing.insert("labels".to_string(), labels.into());
569 }
570 Some(_) => {}
571 None => {
572 tracing.insert("labels".to_string(), legacy.clone().into());
573 }
574 }
575}
576
577pub(crate) struct DappHardhatDirProvider<'a> {
579 pub(crate) root: &'a Path,
580 pub(crate) detect_src: bool,
581}
582
583impl Provider for DappHardhatDirProvider<'_> {
584 fn metadata(&self) -> Metadata {
585 Metadata::named("Dapp Hardhat dir compat")
586 }
587
588 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
589 let mut dict = Dict::new();
590 if self.detect_src {
591 dict.insert(
592 "src".to_string(),
593 ProjectPathsConfig::find_source_dir(self.root)
594 .file_name()
595 .unwrap()
596 .to_string_lossy()
597 .to_string()
598 .into(),
599 );
600 }
601 dict.insert(
602 "out".to_string(),
603 ProjectPathsConfig::find_artifacts_dir(self.root)
604 .file_name()
605 .unwrap()
606 .to_string_lossy()
607 .to_string()
608 .into(),
609 );
610
611 let mut libs = vec![];
616 let node_modules = self.root.join("node_modules");
617 let lib = self.root.join("lib");
618 if node_modules.exists() {
619 if lib.exists() {
620 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
621 }
622 libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string());
623 } else {
624 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
625 }
626
627 dict.insert("libs".to_string(), libs.into());
628
629 Ok(Map::from([(Config::selected_profile(), dict)]))
630 }
631}
632
633pub(crate) struct DappEnvCompatProvider;
635
636impl Provider for DappEnvCompatProvider {
637 fn metadata(&self) -> Metadata {
638 Metadata::named("Dapp env compat")
639 }
640
641 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
642 use serde::de::Error as _;
643 use std::env;
644
645 let mut dict = Dict::new();
646 if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
647 dict.insert(
648 "block_number".to_string(),
649 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
650 );
651 }
652 if let Ok(val) = env::var("DAPP_TEST_ADDRESS") {
653 dict.insert("sender".to_string(), val.into());
654 }
655 if let Ok(val) = env::var("DAPP_FORK_BLOCK") {
656 dict.insert(
657 "fork_block_number".to_string(),
658 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
659 );
660 } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
661 dict.insert(
662 "fork_block_number".to_string(),
663 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
664 );
665 }
666 if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") {
667 dict.insert(
668 "block_timestamp".to_string(),
669 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
670 );
671 }
672 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") {
673 dict.insert(
674 "optimizer_runs".to_string(),
675 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
676 );
677 }
678 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") {
679 let val = val.parse::<u8>().map_err(figment::Error::custom)?;
681 if val > 1 {
682 return Err(
683 format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into()
684 );
685 }
686 dict.insert("optimizer".to_string(), (val == 1).into());
687 }
688
689 if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) {
691 dict.insert("libraries".to_string(), utils::to_array_value(&val)?);
692 }
693
694 let mut fuzz_dict = Dict::new();
695 if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") {
696 fuzz_dict.insert(
697 "runs".to_string(),
698 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
699 );
700 }
701 dict.insert("fuzz".to_string(), fuzz_dict.into());
702
703 let mut invariant_dict = Dict::new();
704 if let Ok(val) = env::var("DAPP_TEST_DEPTH") {
705 invariant_dict.insert(
706 "depth".to_string(),
707 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
708 );
709 }
710 dict.insert("invariant".to_string(), invariant_dict.into());
711
712 Ok(Map::from([(Config::selected_profile(), dict)]))
713 }
714}
715
716pub(crate) struct RenameProfileProvider<P> {
732 provider: P,
733 from: Profile,
734 to: Profile,
735}
736
737impl<P> RenameProfileProvider<P> {
738 pub(crate) fn new(provider: P, from: impl Into<Profile>, to: impl Into<Profile>) -> Self {
739 Self { provider, from: from.into(), to: to.into() }
740 }
741}
742
743impl<P: Provider> Provider for RenameProfileProvider<P> {
744 fn metadata(&self) -> Metadata {
745 self.provider.metadata()
746 }
747
748 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
749 let mut data = self.provider.data()?;
750 if let Some(data) = data.remove(&self.from) {
751 return Ok(Map::from([(self.to.clone(), data)]));
752 }
753 Ok(Default::default())
754 }
755
756 fn profile(&self) -> Option<Profile> {
757 Some(self.to.clone())
758 }
759}
760
761struct UnwrapProfileProvider<P> {
777 provider: P,
778 wrapping_key: Profile,
779 profile: Profile,
780}
781
782impl<P> UnwrapProfileProvider<P> {
783 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
784 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
785 }
786}
787
788impl<P: Provider> Provider for UnwrapProfileProvider<P> {
789 fn metadata(&self) -> Metadata {
790 self.provider.metadata()
791 }
792
793 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
794 let mut data = self.provider.data()?;
795 if let Some(profiles) = data.remove(&self.wrapping_key) {
796 for (profile_str, profile_val) in profiles {
797 let profile = Profile::new(&profile_str);
798 if profile != self.profile {
799 continue;
800 }
801 match profile_val {
802 Value::Dict(_, dict) => return Ok(profile.collect(dict)),
803 bad_val => {
804 let mut err = Error::from(figment::error::Kind::InvalidType(
805 bad_val.to_actual(),
806 "dict".into(),
807 ));
808 err.metadata = Some(self.provider.metadata());
809 err.profile = Some(self.profile.clone());
810 return Err(err);
811 }
812 }
813 }
814 }
815 Ok(Default::default())
816 }
817
818 fn profile(&self) -> Option<Profile> {
819 Some(self.profile.clone())
820 }
821}
822
823pub(crate) struct WrapProfileProvider<P> {
839 provider: P,
840 wrapping_key: Profile,
841 profile: Profile,
842}
843
844impl<P> WrapProfileProvider<P> {
845 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
846 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
847 }
848}
849
850impl<P: Provider> Provider for WrapProfileProvider<P> {
851 fn metadata(&self) -> Metadata {
852 self.provider.metadata()
853 }
854
855 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
856 if let Some(inner) = self.provider.data()?.remove(&self.profile) {
857 let value = Value::from(inner);
858 let mut dict = Dict::new();
859 dict.insert(self.profile.as_str().as_str().to_snake_case(), value);
860 Ok(self.wrapping_key.collect(dict))
861 } else {
862 Ok(Default::default())
863 }
864 }
865
866 fn profile(&self) -> Option<Profile> {
867 Some(self.profile.clone())
868 }
869}
870
871pub(crate) struct OptionalStrictProfileProvider<P> {
894 provider: P,
895 profiles: Vec<Profile>,
896}
897
898impl<P> OptionalStrictProfileProvider<P> {
899 pub const PROFILE_PROFILE: Profile = Profile::const_new("profile");
900
901 pub fn new(provider: P, profiles: impl IntoIterator<Item = impl Into<Profile>>) -> Self {
902 Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() }
903 }
904}
905
906impl<P: Provider> Provider for OptionalStrictProfileProvider<P> {
907 fn metadata(&self) -> Metadata {
908 self.provider.metadata()
909 }
910
911 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
912 let mut figment = Figment::from(&self.provider);
913 for profile in &self.profiles {
914 figment = figment.merge(UnwrapProfileProvider::new(
915 &self.provider,
916 Self::PROFILE_PROFILE,
917 profile.clone(),
918 ));
919 }
920 figment.data().map_err(|err| {
921 if let Err(root_err) = self.provider.data() {
926 return root_err;
927 }
928 err
929 })
930 }
931
932 fn profile(&self) -> Option<Profile> {
933 self.profiles.last().cloned()
934 }
935}
936
937pub struct FallbackProfileProvider<P> {
940 provider: P,
941 profile: Profile,
942 fallback: Profile,
943}
944
945impl<P> FallbackProfileProvider<P> {
946 pub fn new(provider: P, profile: impl Into<Profile>, fallback: impl Into<Profile>) -> Self {
948 Self { provider, profile: profile.into(), fallback: fallback.into() }
949 }
950}
951
952impl<P: Provider> Provider for FallbackProfileProvider<P> {
953 fn metadata(&self) -> Metadata {
954 self.provider.metadata()
955 }
956
957 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
958 let mut data = self.provider.data()?;
959 let invariant_corpus_random_sequence_weight_configured = self.profile == "invariant"
960 && data
961 .get(&self.profile)
962 .is_some_and(|inner| inner.contains_key("corpus_random_sequence_weight"));
963 let mark_invariant_corpus_random_sequence_weight_configured =
964 |inner: &mut Dict| -> Result<(), Error> {
965 if invariant_corpus_random_sequence_weight_configured {
966 inner.insert(
967 "corpus_random_sequence_weight_configured".to_string(),
968 Value::serialize(true)?,
969 );
970 }
971 Ok(())
972 };
973
974 if let Some(fallback) = data.remove(&self.fallback) {
975 let mut inner = data.remove(&self.profile).unwrap_or_default();
976 for (k, v) in fallback {
977 inner.entry(k).or_insert(v);
978 }
979 mark_invariant_corpus_random_sequence_weight_configured(&mut inner)?;
980 Ok(self.profile.collect(inner))
981 } else {
982 if let Some(inner) = data.get_mut(&self.profile) {
983 mark_invariant_corpus_random_sequence_weight_configured(inner)?;
984 }
985 Ok(data)
986 }
987 }
988
989 fn profile(&self) -> Option<Profile> {
990 Some(self.profile.clone())
991 }
992}