// file: crates/ksp-config-lib/src/sensitivity.rs // version: 1 /// Replacement used for secret environment fragments in safe diagnostic representations. pub const REDACTED_CONFIG_VALUE: &str = "********"; /// Sensitivity assigned to one Config value after environment resolution. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum ConfigSensitivity { /// Value may be exposed by a public projection when the consumer contract allows it. Public, /// Value is available to the runtime but is not generically public. Internal, /// Value must remain available to legitimate runtime/management consumers while being redacted from ordinary diagnostics. Secret, } impl ConfigSensitivity { /// Classifies one supported KSP/KSPB environment variable by its namespace. pub fn from_variable_name(variable_name: &str) -> ksp_core_lib::Result { let validation = crate::environment::validate_supported_variable_name(variable_name); if let std::result::Result::Err(error) = validation { return std::result::Result::Err(error); } if variable_name.starts_with("KSP_SECRET_") || variable_name.starts_with("KSPB_SECRET_") { return std::result::Result::Ok(Self::Secret); } if variable_name.starts_with("KSP_PUBLIC_") || variable_name.starts_with("KSPB_PUBLIC_") { return std::result::Result::Ok(Self::Public); } return std::result::Result::Ok(Self::Internal); } /// Returns the strongest of two sensitivities using `Secret > Internal > Public`. #[must_use] pub const fn strongest(self, other: Self) -> Self { if self as u8 >= other as u8 { return self; } return other; } /// Returns whether this sensitivity requires ordinary diagnostic redaction. #[must_use] pub const fn is_secret(self) -> bool { return matches!(self, Self::Secret); } } /// Provenance segment participating in one resolved Config value. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ConfigValueProvenance { /// Literal text/value came directly from the Config document. DocumentLiteral, /// Environment substitution came from the inherited process environment. EnvironmentProcess { /// Referenced variable name; never its value. variable_name: String, }, /// Environment substitution came from the local `.env` file. EnvironmentDotEnv { /// Referenced variable name; never its value. variable_name: String, }, /// Environment substitution used the placeholder/API fallback. EnvironmentFallback { /// Referenced variable name; never its value. variable_name: String, }, } impl ConfigValueProvenance { /// Returns the referenced variable name for environment provenance. #[must_use] pub fn variable_name(&self) -> std::option::Option<&str> { return match self { Self::DocumentLiteral => std::option::Option::None, Self::EnvironmentProcess { variable_name } | Self::EnvironmentDotEnv { variable_name } | Self::EnvironmentFallback { variable_name } => { std::option::Option::Some(variable_name.as_str()) }, }; } /// Returns the environment source represented by this provenance segment when applicable. #[must_use] pub const fn environment_source(&self) -> std::option::Option { return match self { Self::DocumentLiteral => std::option::Option::None, Self::EnvironmentProcess { .. } => std::option::Option::Some(crate::ConfigEnvironmentSource::Process), Self::EnvironmentDotEnv { .. } => std::option::Option::Some(crate::ConfigEnvironmentSource::DotEnv), Self::EnvironmentFallback { .. } => std::option::Option::Some(crate::ConfigEnvironmentSource::Fallback), }; } } /// One resolved Config string with real/safe representations, sensitivity and ordered provenance. #[derive(Clone, Eq, PartialEq)] pub struct ResolvedConfigText { value: String, safe_value: String, sensitivity: ConfigSensitivity, provenance: std::vec::Vec, } impl ResolvedConfigText { pub(crate) fn new(value: String, safe_value: String, sensitivity: ConfigSensitivity, provenance: std::vec::Vec) -> Self { return Self { value, safe_value, sensitivity, provenance }; } /// Returns the real runtime value. #[must_use] pub fn value(&self) -> &str { return self.value.as_str(); } /// Returns the representation safe for ordinary diagnostics. #[must_use] pub fn safe_value(&self) -> &str { return self.safe_value.as_str(); } /// Returns the strongest sensitivity contributed by referenced environment placeholders. #[must_use] pub const fn sensitivity(&self) -> ConfigSensitivity { return self.sensitivity; } /// Returns ordered provenance segments participating in the resolved string. #[must_use] pub fn provenance(&self) -> &[ConfigValueProvenance] { return self.provenance.as_slice(); } } impl std::fmt::Debug for ResolvedConfigText { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("ResolvedConfigText") .field("safe_value", &self.safe_value) .field("sensitivity", &self.sensitivity) .field("provenance", &self.provenance) .finish(); } } /// Recursively resolved JSON value preserving a real tree, a safe tree and provenance indexed by JSON Pointer. #[derive(Clone, Eq, PartialEq)] pub struct ResolvedConfigJson { value: serde_json::Value, safe_value: serde_json::Value, sensitivity: ConfigSensitivity, provenance: std::collections::BTreeMap>, } impl ResolvedConfigJson { pub(crate) fn new( value: serde_json::Value, safe_value: serde_json::Value, sensitivity: ConfigSensitivity, provenance: std::collections::BTreeMap>, ) -> Self { return Self { value, safe_value, sensitivity, provenance }; } /// Returns the real JSON tree intended for legitimate runtime consumers. #[must_use] pub const fn value(&self) -> &serde_json::Value { return &self.value; } /// Returns the JSON tree safe for ordinary diagnostics. #[must_use] pub const fn safe_value(&self) -> &serde_json::Value { return &self.safe_value; } /// Returns the strongest sensitivity found anywhere in the resolved JSON tree. #[must_use] pub const fn sensitivity(&self) -> ConfigSensitivity { return self.sensitivity; } /// Returns provenance indexed by RFC 6901 JSON Pointer strings. #[must_use] pub const fn provenance(&self) -> &std::collections::BTreeMap> { return &self.provenance; } /// Returns provenance for one JSON Pointer when the resolved value recorded that location. #[must_use] pub fn provenance_at(&self, json_pointer: &str) -> std::option::Option<&[ConfigValueProvenance]> { return self.provenance.get(json_pointer).map(std::vec::Vec::as_slice); } } impl std::fmt::Debug for ResolvedConfigJson { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter .debug_struct("ResolvedConfigJson") .field("safe_value", &self.safe_value) .field("sensitivity", &self.sensitivity) .field("provenance", &self.provenance) .finish(); } } #[cfg(test)] #[path = "../unit_tests/sensitivity.rs"] mod tests;