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}