Skip to main content

cabin_core/
config_source.rs

1//! Where an effective configuration value came from, across the
2//! full precedence chain that combines CLI flags, environment
3//! variables, config files, manifest declarations, and built-in
4//! defaults.
5//!
6//! `cabin metadata` reports every effective setting paired with one
7//! of these labels so users can audit a build without re-deriving
8//! the precedence by hand. Crates that produce effective values
9//! (cabin's orchestration layer; cabin-config's merge layer;
10//! the toolchain / wrapper resolvers) populate the matching variant
11//! and the metadata serializer renders the stable kebab-case form.
12
13use std::fmt;
14
15use serde::{Deserialize, Serialize};
16
17/// Stable, ordered enum describing every layer Cabin's effective
18/// configuration can come from.
19///
20/// The `cli`, `env`, and `*-config` variants are the only ones a
21/// caller assigns directly today; manifest-derived values stay on
22/// their dedicated tool/wrapper enums to preserve the existing
23/// metadata shape.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "kebab-case")]
26pub enum ConfigValueSource {
27    /// Hard-coded in Cabin (e.g., the default `dev` profile).
28    BuiltinDefault,
29    /// Declared in the package manifest (e.g.,
30    /// `[profile.<name>]`).
31    Manifest,
32    /// Declared in the user-level config file.
33    UserConfig,
34    /// Declared in the workspace-level config file.
35    WorkspaceConfig,
36    /// Declared in the package-local config file (non-workspace
37    /// projects).
38    PackageConfig,
39    /// Declared in the file pointed at by `CABIN_CONFIG`.
40    ExplicitConfig,
41    /// Provided through an environment variable (e.g., `CC`,
42    /// `CABIN_COMPILER_WRAPPER`).
43    Env,
44    /// Provided through a CLI flag (e.g., `--profile`,
45    /// `--cxx`).
46    Cli,
47}
48
49impl ConfigValueSource {
50    /// Stable lower-case identifier used in JSON output and error
51    /// messages.
52    pub const fn as_key(self) -> &'static str {
53        match self {
54            ConfigValueSource::BuiltinDefault => "builtin-default",
55            ConfigValueSource::Manifest => "manifest",
56            ConfigValueSource::UserConfig => "user-config",
57            ConfigValueSource::WorkspaceConfig => "workspace-config",
58            ConfigValueSource::PackageConfig => "package-config",
59            ConfigValueSource::ExplicitConfig => "explicit-config",
60            ConfigValueSource::Env => "env",
61            ConfigValueSource::Cli => "cli",
62        }
63    }
64}
65
66impl fmt::Display for ConfigValueSource {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str(self.as_key())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn keys_round_trip_with_serde() {
78        for source in [
79            ConfigValueSource::BuiltinDefault,
80            ConfigValueSource::Manifest,
81            ConfigValueSource::UserConfig,
82            ConfigValueSource::WorkspaceConfig,
83            ConfigValueSource::PackageConfig,
84            ConfigValueSource::ExplicitConfig,
85            ConfigValueSource::Env,
86            ConfigValueSource::Cli,
87        ] {
88            let json = serde_json::to_string(&source).unwrap();
89            let echoed: ConfigValueSource = serde_json::from_str(&json).unwrap();
90            assert_eq!(echoed, source);
91            assert_eq!(json.trim_matches('"'), source.as_key());
92        }
93    }
94}