Skip to main content

foundry_config/providers/
ext.rs

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
47impl<P: Provider> ProviderExt for P {}
48
49/// A convenience provider to retrieve a toml file.
50/// This will return an error if the env var is set but the file does not exist
51pub(crate) struct TomlFileProvider {
52    env_var: Option<&'static str>,
53    env_val: OnceCell<Option<String>>,
54    default: PathBuf,
55    cache: OnceCell<Result<Map<Profile, Dict>, Error>>,
56}
57
58impl TomlFileProvider {
59    pub(crate) fn new(env_var: Option<&'static str>, default: PathBuf) -> Self {
60        Self { env_var, env_val: OnceCell::new(), default, cache: OnceCell::new() }
61    }
62
63    fn env_val(&self) -> Option<&str> {
64        self.env_val.get_or_init(|| self.env_var.and_then(Env::var)).as_deref()
65    }
66
67    fn file(&self) -> PathBuf {
68        self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone())
69    }
70
71    fn is_missing(&self) -> bool {
72        if let Some(file) = self.env_val() {
73            let path = Path::new(&file);
74            if !path.exists() {
75                return true;
76            }
77        }
78        false
79    }
80
81    /// Reads and processes the TOML configuration file, handling inheritance if configured.
82    fn read(&self) -> Result<Map<Profile, Dict>, Error> {
83        use serde::de::Error as _;
84
85        // Get the config file path and validate it exists
86        let local_path = self.file();
87        if !local_path.exists() {
88            if let Some(file) = self.env_val() {
89                return Err(Error::custom(format!(
90                    "Config file `{}` set in env var `{}` does not exist",
91                    file,
92                    self.env_var.unwrap()
93                )));
94            }
95            return Ok(Map::new());
96        }
97
98        // Create a provider for the local config file
99        let local_provider = Toml::file(local_path.clone()).nested();
100
101        // Parse the local config to check for extends field
102        let local_path_str = local_path.to_string_lossy();
103        let local_content = std::fs::read_to_string(&local_path)
104            .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?;
105        let partial_config: extend::ExtendsPartialConfig = toml::from_str(&local_content)
106            .map_err(|e| Error::custom(e.to_string()).with_path(&local_path_str))?;
107
108        // Check if the currently active profile has an 'extends' field
109        let selected_profile = Config::selected_profile();
110        let extends_config = partial_config.profile.as_ref().and_then(|profiles| {
111            let profile_str = selected_profile.to_string();
112            profiles.get(&profile_str).and_then(|cfg| cfg.extends.as_ref())
113        });
114
115        // If inheritance is configured, load and merge the base config
116        if let Some(extends_config) = extends_config {
117            let extends_path = extends_config.path();
118            let extends_strategy = extends_config.strategy();
119            let relative_base_path = PathBuf::from(extends_path);
120            let local_dir = local_path.parent().ok_or_else(|| {
121                Error::custom(format!(
122                    "Could not determine parent directory of config file: {}",
123                    local_path.display()
124                ))
125            })?;
126
127            let base_path =
128                foundry_compilers::utils::canonicalize(local_dir.join(&relative_base_path))
129                    .map_err(|e| {
130                        Error::custom(format!(
131                            "Failed to resolve inherited config path: {}: {e}",
132                            relative_base_path.display()
133                        ))
134                    })?;
135
136            // Validate the base config file exists
137            if !base_path.is_file() {
138                return Err(Error::custom(format!(
139                    "Inherited config file does not exist or is not a file: {}",
140                    base_path.display()
141                )));
142            }
143
144            // Prevent self-inheritance which would cause infinite recursion
145            if foundry_compilers::utils::canonicalize(&local_path).ok().as_ref() == Some(&base_path)
146            {
147                return Err(Error::custom(format!(
148                    "Config file {} cannot inherit from itself.",
149                    local_path.display()
150                )));
151            }
152
153            // Parse the base config to check for nested inheritance
154            let base_path_str = base_path.to_string_lossy();
155            let base_content = std::fs::read_to_string(&base_path)
156                .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
157            let base_partial: extend::ExtendsPartialConfig = toml::from_str(&base_content)
158                .map_err(|e| Error::custom(e.to_string()).with_path(&base_path_str))?;
159
160            // Check if the base file's same profile also has extends (nested inheritance)
161            let base_extends = base_partial
162                .profile
163                .as_ref()
164                .and_then(|profiles| {
165                    let profile_str = selected_profile.to_string();
166                    profiles.get(&profile_str)
167                })
168                .and_then(|profile| profile.extends.as_ref());
169
170            // Prevent nested inheritance to avoid complexity and potential cycles
171            if base_extends.is_some() {
172                return Err(Error::custom(format!(
173                    "Nested inheritance is not allowed. Base file '{}' cannot have an 'extends' field in profile '{selected_profile}'.",
174                    base_path.display()
175                )));
176            }
177
178            // Load base configuration as a Figment provider
179            let base_provider = Toml::file(base_path).nested();
180
181            // Apply the selected merge strategy
182            match extends_strategy {
183                extend::ExtendStrategy::ExtendArrays => {
184                    // Using 'admerge' strategy:
185                    // - Arrays are concatenated (base elements + local elements)
186                    // - Other values are replaced (local values override base values)
187                    // - The extends field is preserved in the final configuration
188                    Figment::new().merge(base_provider).admerge(local_provider).data()
189                }
190                extend::ExtendStrategy::ReplaceArrays => {
191                    // Using 'merge' strategy:
192                    // - Arrays are replaced entirely (local arrays replace base arrays)
193                    // - Other values are replaced (local values override base values)
194                    Figment::new().merge(base_provider).merge(local_provider).data()
195                }
196                extend::ExtendStrategy::NoCollision => {
197                    // Check for key collisions between base and local configs
198                    let base_data = base_provider.data()?;
199                    let local_data = local_provider.data()?;
200
201                    let profile_key = Profile::new("profile");
202                    if let (Some(local_profiles), Some(base_profiles)) =
203                        (local_data.get(&profile_key), base_data.get(&profile_key))
204                    {
205                        // Extract dicts for the selected profile
206                        let profile_str = selected_profile.to_string();
207                        let base_dict = base_profiles.get(&profile_str).and_then(|v| v.as_dict());
208                        let local_dict = local_profiles.get(&profile_str).and_then(|v| v.as_dict());
209
210                        // Find colliding keys
211                        if let (Some(local_dict), Some(base_dict)) = (local_dict, base_dict) {
212                            let collisions: Vec<&String> = local_dict
213                                .keys()
214                                .filter(|key| {
215                                    // Ignore the "extends" key as it's expected
216                                    *key != "extends" && base_dict.contains_key(*key)
217                                })
218                                .collect();
219
220                            if !collisions.is_empty() {
221                                return Err(Error::custom(format!(
222                                    "Key collision detected in profile '{profile_str}' when extending '{extends_path}'. \
223                                    Conflicting keys: {collisions:?}. Use 'extends.strategy' or 'extends_strategy' to specify how to handle conflicts."
224                                )));
225                            }
226                        }
227                    }
228
229                    // Safe to merge the configs without collisions
230                    Figment::new().merge(base_provider).merge(local_provider).data()
231                }
232            }
233        } else {
234            // No inheritance - return the local config as-is
235            local_provider.data()
236        }
237    }
238}
239
240impl Provider for TomlFileProvider {
241    fn metadata(&self) -> Metadata {
242        if self.is_missing() {
243            Metadata::named("TOML file provider")
244        } else {
245            Toml::file(self.file()).nested().metadata()
246        }
247    }
248
249    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
250        self.cache.get_or_init(|| self.read()).clone()
251    }
252}
253
254/// A Provider that ensures all keys are snake case if they're not standalone sections, See
255/// `Config::STANDALONE_SECTIONS`
256///
257/// For the `[profile]` section, profile names (like `ci-venom`) are preserved as-is,
258/// but the config keys within each profile are still converted to snake_case.
259pub(crate) struct ForcedSnakeCaseData<P>(pub(crate) P);
260
261impl<P: Provider> Provider for ForcedSnakeCaseData<P> {
262    fn metadata(&self) -> Metadata {
263        self.0.metadata()
264    }
265
266    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
267        let mut map = self.0.data()?;
268        for (profile, dict) in &mut map {
269            if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
270                // don't force snake case for keys in standalone sections
271                continue;
272            }
273
274            if profile.as_str().as_str() == Config::PROFILE_SECTION {
275                // For the `[profile]` section, we need to preserve profile names (the keys)
276                // but snake_case the config keys within each profile's dict.
277                let dict2 = std::mem::take(dict);
278                *dict = dict2
279                    .into_iter()
280                    .map(|(profile_name, v)| {
281                        // Keep the profile name exactly as-is (e.g., "ci-venom" stays "ci-venom")
282                        let v = snake_case_value_keys(v);
283                        (profile_name, v)
284                    })
285                    .collect();
286                continue;
287            }
288
289            let dict2 = std::mem::take(dict);
290            *dict = dict2.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect();
291        }
292        Ok(map)
293    }
294
295    fn profile(&self) -> Option<Profile> {
296        self.0.profile()
297    }
298}
299
300/// Recursively converts all keys in a Value (if it's a Dict) to snake_case.
301fn snake_case_value_keys(value: Value) -> Value {
302    match value {
303        Value::Dict(tag, dict) => {
304            let new_dict = dict
305                .into_iter()
306                .map(|(k, v)| (k.to_snake_case(), snake_case_value_keys(v)))
307                .collect();
308            Value::Dict(tag, new_dict)
309        }
310        Value::Array(tag, arr) => {
311            let new_arr = arr.into_iter().map(snake_case_value_keys).collect();
312            Value::Array(tag, new_arr)
313        }
314        other => other,
315    }
316}
317
318/// A Provider that handles breaking changes in toml files
319pub(crate) struct BackwardsCompatTomlProvider<P>(pub(crate) P);
320
321impl<P: Provider> Provider for BackwardsCompatTomlProvider<P> {
322    fn metadata(&self) -> Metadata {
323        self.0.metadata()
324    }
325
326    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
327        let mut map = Map::new();
328        let solc_env = std::env::var("FOUNDRY_SOLC_VERSION")
329            .or_else(|_| std::env::var("DAPP_SOLC_VERSION"))
330            .map(Value::from)
331            .ok();
332        for (profile, mut dict) in self.0.data()? {
333            if let Some(v) = solc_env.clone() {
334                // ENV var takes precedence over config file
335                dict.insert("solc".to_string(), v);
336            } else if let Some(v) = dict.remove("solc_version") {
337                // only insert older variant if not already included
338                if !dict.contains_key("solc") {
339                    dict.insert("solc".to_string(), v);
340                }
341            }
342            if let Some(v) = dict.remove("deny_warnings")
343                && !dict.contains_key("deny")
344            {
345                dict.insert("deny".to_string(), v);
346            }
347
348            map.insert(profile, dict);
349        }
350        Ok(map)
351    }
352
353    fn profile(&self) -> Option<Profile> {
354        self.0.profile()
355    }
356}
357
358/// A provider that sets the `src` and `output` path depending on their existence.
359pub(crate) struct DappHardhatDirProvider<'a>(pub(crate) &'a Path);
360
361impl Provider for DappHardhatDirProvider<'_> {
362    fn metadata(&self) -> Metadata {
363        Metadata::named("Dapp Hardhat dir compat")
364    }
365
366    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
367        let mut dict = Dict::new();
368        dict.insert(
369            "src".to_string(),
370            ProjectPathsConfig::find_source_dir(self.0)
371                .file_name()
372                .unwrap()
373                .to_string_lossy()
374                .to_string()
375                .into(),
376        );
377        dict.insert(
378            "out".to_string(),
379            ProjectPathsConfig::find_artifacts_dir(self.0)
380                .file_name()
381                .unwrap()
382                .to_string_lossy()
383                .to_string()
384                .into(),
385        );
386
387        // detect libs folders:
388        //   if `lib` _and_ `node_modules` exists: include both
389        //   if only `node_modules` exists: include `node_modules`
390        //   include `lib` otherwise
391        let mut libs = vec![];
392        let node_modules = self.0.join("node_modules");
393        let lib = self.0.join("lib");
394        if node_modules.exists() {
395            if lib.exists() {
396                libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
397            }
398            libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string());
399        } else {
400            libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
401        }
402
403        dict.insert("libs".to_string(), libs.into());
404
405        Ok(Map::from([(Config::selected_profile(), dict)]))
406    }
407}
408
409/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_
410pub(crate) struct DappEnvCompatProvider;
411
412impl Provider for DappEnvCompatProvider {
413    fn metadata(&self) -> Metadata {
414        Metadata::named("Dapp env compat")
415    }
416
417    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
418        use serde::de::Error as _;
419        use std::env;
420
421        let mut dict = Dict::new();
422        if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
423            dict.insert(
424                "block_number".to_string(),
425                val.parse::<u64>().map_err(figment::Error::custom)?.into(),
426            );
427        }
428        if let Ok(val) = env::var("DAPP_TEST_ADDRESS") {
429            dict.insert("sender".to_string(), val.into());
430        }
431        if let Ok(val) = env::var("DAPP_FORK_BLOCK") {
432            dict.insert(
433                "fork_block_number".to_string(),
434                val.parse::<u64>().map_err(figment::Error::custom)?.into(),
435            );
436        } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
437            dict.insert(
438                "fork_block_number".to_string(),
439                val.parse::<u64>().map_err(figment::Error::custom)?.into(),
440            );
441        }
442        if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") {
443            dict.insert(
444                "block_timestamp".to_string(),
445                val.parse::<u64>().map_err(figment::Error::custom)?.into(),
446            );
447        }
448        if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") {
449            dict.insert(
450                "optimizer_runs".to_string(),
451                val.parse::<u64>().map_err(figment::Error::custom)?.into(),
452            );
453        }
454        if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") {
455            // Activate Solidity optimizer (0 or 1)
456            let val = val.parse::<u8>().map_err(figment::Error::custom)?;
457            if val > 1 {
458                return Err(
459                    format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into()
460                );
461            }
462            dict.insert("optimizer".to_string(), (val == 1).into());
463        }
464
465        // libraries in env vars either as `[..]` or single string separated by comma
466        if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) {
467            dict.insert("libraries".to_string(), utils::to_array_value(&val)?);
468        }
469
470        let mut fuzz_dict = Dict::new();
471        if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") {
472            fuzz_dict.insert(
473                "runs".to_string(),
474                val.parse::<u32>().map_err(figment::Error::custom)?.into(),
475            );
476        }
477        dict.insert("fuzz".to_string(), fuzz_dict.into());
478
479        let mut invariant_dict = Dict::new();
480        if let Ok(val) = env::var("DAPP_TEST_DEPTH") {
481            invariant_dict.insert(
482                "depth".to_string(),
483                val.parse::<u32>().map_err(figment::Error::custom)?.into(),
484            );
485        }
486        dict.insert("invariant".to_string(), invariant_dict.into());
487
488        Ok(Map::from([(Config::selected_profile(), dict)]))
489    }
490}
491
492/// Renames a profile from `from` to `to`.
493///
494/// For example given:
495///
496/// ```toml
497/// [from]
498/// key = "value"
499/// ```
500///
501/// RenameProfileProvider will output
502///
503/// ```toml
504/// [to]
505/// key = "value"
506/// ```
507pub(crate) struct RenameProfileProvider<P> {
508    provider: P,
509    from: Profile,
510    to: Profile,
511}
512
513impl<P> RenameProfileProvider<P> {
514    pub(crate) fn new(provider: P, from: impl Into<Profile>, to: impl Into<Profile>) -> Self {
515        Self { provider, from: from.into(), to: to.into() }
516    }
517}
518
519impl<P: Provider> Provider for RenameProfileProvider<P> {
520    fn metadata(&self) -> Metadata {
521        self.provider.metadata()
522    }
523
524    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
525        let mut data = self.provider.data()?;
526        if let Some(data) = data.remove(&self.from) {
527            return Ok(Map::from([(self.to.clone(), data)]));
528        }
529        Ok(Default::default())
530    }
531
532    fn profile(&self) -> Option<Profile> {
533        Some(self.to.clone())
534    }
535}
536
537/// Unwraps a profile reducing the key depth
538///
539/// For example given:
540///
541/// ```toml
542/// [wrapping_key.profile]
543/// key = "value"
544/// ```
545///
546/// UnwrapProfileProvider will output:
547///
548/// ```toml
549/// [profile]
550/// key = "value"
551/// ```
552struct UnwrapProfileProvider<P> {
553    provider: P,
554    wrapping_key: Profile,
555    profile: Profile,
556}
557
558impl<P> UnwrapProfileProvider<P> {
559    pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
560        Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
561    }
562}
563
564impl<P: Provider> Provider for UnwrapProfileProvider<P> {
565    fn metadata(&self) -> Metadata {
566        self.provider.metadata()
567    }
568
569    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
570        let mut data = self.provider.data()?;
571        if let Some(profiles) = data.remove(&self.wrapping_key) {
572            for (profile_str, profile_val) in profiles {
573                let profile = Profile::new(&profile_str);
574                if profile != self.profile {
575                    continue;
576                }
577                match profile_val {
578                    Value::Dict(_, dict) => return Ok(profile.collect(dict)),
579                    bad_val => {
580                        let mut err = Error::from(figment::error::Kind::InvalidType(
581                            bad_val.to_actual(),
582                            "dict".into(),
583                        ));
584                        err.metadata = Some(self.provider.metadata());
585                        err.profile = Some(self.profile.clone());
586                        return Err(err);
587                    }
588                }
589            }
590        }
591        Ok(Default::default())
592    }
593
594    fn profile(&self) -> Option<Profile> {
595        Some(self.profile.clone())
596    }
597}
598
599/// Wraps a profile in another profile
600///
601/// For example given:
602///
603/// ```toml
604/// [profile]
605/// key = "value"
606/// ```
607///
608/// WrapProfileProvider will output:
609///
610/// ```toml
611/// [wrapping_key.profile]
612/// key = "value"
613/// ```
614pub(crate) struct WrapProfileProvider<P> {
615    provider: P,
616    wrapping_key: Profile,
617    profile: Profile,
618}
619
620impl<P> WrapProfileProvider<P> {
621    pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
622        Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
623    }
624}
625
626impl<P: Provider> Provider for WrapProfileProvider<P> {
627    fn metadata(&self) -> Metadata {
628        self.provider.metadata()
629    }
630
631    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
632        if let Some(inner) = self.provider.data()?.remove(&self.profile) {
633            let value = Value::from(inner);
634            let mut dict = Dict::new();
635            dict.insert(self.profile.as_str().as_str().to_snake_case(), value);
636            Ok(self.wrapping_key.collect(dict))
637        } else {
638            Ok(Default::default())
639        }
640    }
641
642    fn profile(&self) -> Option<Profile> {
643        Some(self.profile.clone())
644    }
645}
646
647/// Extracts the profile from the `profile` key and using the original key as backup, merging
648/// values where necessary
649///
650/// For example given:
651///
652/// ```toml
653/// [profile.cool]
654/// key = "value"
655///
656/// [cool]
657/// key2 = "value2"
658/// ```
659///
660/// OptionalStrictProfileProvider will output:
661///
662/// ```toml
663/// [cool]
664/// key = "value"
665/// key2 = "value2"
666/// ```
667///
668/// And emit a deprecation warning
669pub(crate) struct OptionalStrictProfileProvider<P> {
670    provider: P,
671    profiles: Vec<Profile>,
672}
673
674impl<P> OptionalStrictProfileProvider<P> {
675    pub const PROFILE_PROFILE: Profile = Profile::const_new("profile");
676
677    pub fn new(provider: P, profiles: impl IntoIterator<Item = impl Into<Profile>>) -> Self {
678        Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() }
679    }
680}
681
682impl<P: Provider> Provider for OptionalStrictProfileProvider<P> {
683    fn metadata(&self) -> Metadata {
684        self.provider.metadata()
685    }
686
687    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
688        let mut figment = Figment::from(&self.provider);
689        for profile in &self.profiles {
690            figment = figment.merge(UnwrapProfileProvider::new(
691                &self.provider,
692                Self::PROFILE_PROFILE,
693                profile.clone(),
694            ));
695        }
696        figment.data().map_err(|err| {
697            // figment does tag metadata and tries to map metadata to an error, since we use a new
698            // figment in this provider this new figment does not know about the metadata of the
699            // provider and can't map the metadata to the error. Therefore we return the root error
700            // if this error originated in the provider's data.
701            if let Err(root_err) = self.provider.data() {
702                return root_err;
703            }
704            err
705        })
706    }
707
708    fn profile(&self) -> Option<Profile> {
709        self.profiles.last().cloned()
710    }
711}
712
713/// Extracts the profile from the `profile` key and sets unset values according to the fallback
714/// provider
715pub struct FallbackProfileProvider<P> {
716    provider: P,
717    profile: Profile,
718    fallback: Profile,
719}
720
721impl<P> FallbackProfileProvider<P> {
722    /// Creates a new fallback profile provider.
723    pub fn new(provider: P, profile: impl Into<Profile>, fallback: impl Into<Profile>) -> Self {
724        Self { provider, profile: profile.into(), fallback: fallback.into() }
725    }
726}
727
728impl<P: Provider> Provider for FallbackProfileProvider<P> {
729    fn metadata(&self) -> Metadata {
730        self.provider.metadata()
731    }
732
733    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
734        let mut data = self.provider.data()?;
735        if let Some(fallback) = data.remove(&self.fallback) {
736            let mut inner = data.remove(&self.profile).unwrap_or_default();
737            for (k, v) in fallback {
738                inner.entry(k).or_insert(v);
739            }
740            Ok(self.profile.collect(inner))
741        } else {
742            Ok(data)
743        }
744    }
745
746    fn profile(&self) -> Option<Profile> {
747        Some(self.profile.clone())
748    }
749}