v0.1.3-pre.011

This commit is contained in:
2026-08-16 04:35:23 +02:00
parent 695068acea
commit 274428bd2f
13 changed files with 1005 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/environment.rs
// version: 1
// version: 2
/// Default local environment file read by Config from the process launch directory.
pub const DEFAULT_DOTENV_PATH: &str = ".env";
@@ -21,14 +21,13 @@ pub enum ConfigEnvironmentSource {
Fallback,
}
/// One resolved Config environment variable and its winning source.
///
/// This type intentionally does not implement `Debug`: environment values may contain secrets. Sensitivity-aware safe representations are added by the next
/// bounded Config prerelease.
/// One resolved Config environment variable with real/safe values, sensitivity and its winning source.
#[derive(Clone, Eq, PartialEq)]
pub struct ConfigEnvironmentValue {
variable_name: String,
value: String,
safe_value: String,
sensitivity: crate::ConfigSensitivity,
source: ConfigEnvironmentSource,
}
@@ -45,11 +44,41 @@ impl ConfigEnvironmentValue {
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 sensitivity derived from the variable namespace.
#[must_use]
pub const fn sensitivity(&self) -> crate::ConfigSensitivity {
return self.sensitivity;
}
/// Returns the source that won process > `.env` > fallback resolution.
#[must_use]
pub const fn source(&self) -> ConfigEnvironmentSource {
return self.source;
}
/// Returns provenance without embedding the resolved value.
#[must_use]
pub fn provenance(&self) -> crate::ConfigValueProvenance {
return environment_provenance(self.variable_name.as_str(), self.source);
}
}
impl std::fmt::Debug for ConfigEnvironmentValue {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("ConfigEnvironmentValue")
.field("variable_name", &self.variable_name)
.field("safe_value", &self.safe_value)
.field("sensitivity", &self.sensitivity)
.field("source", &self.source)
.finish();
}
}
/// Config-owned snapshot of KSP/KSPB process environment values and the local `.env` file.
@@ -84,25 +113,13 @@ impl ConfigEnvironment {
return std::result::Result::Err(error);
}
if let std::option::Option::Some(value) = self.process.get(variable_name) {
return std::result::Result::Ok(ConfigEnvironmentValue {
variable_name: variable_name.to_owned(),
value: value.clone(),
source: ConfigEnvironmentSource::Process,
});
return resolved_environment_value(variable_name, value.as_str(), ConfigEnvironmentSource::Process);
}
if let std::option::Option::Some(value) = self.dotenv.get(variable_name) {
return std::result::Result::Ok(ConfigEnvironmentValue {
variable_name: variable_name.to_owned(),
value: value.clone(),
source: ConfigEnvironmentSource::DotEnv,
});
return resolved_environment_value(variable_name, value.as_str(), ConfigEnvironmentSource::DotEnv);
}
if let std::option::Option::Some(value) = fallback {
return std::result::Result::Ok(ConfigEnvironmentValue {
variable_name: variable_name.to_owned(),
value: value.to_owned(),
source: ConfigEnvironmentSource::Fallback,
});
return resolved_environment_value(variable_name, value, ConfigEnvironmentSource::Fallback);
}
emit_missing_variable_warning(variable_name);
return std::result::Result::Err(missing_variable_error(variable_name));
@@ -110,20 +127,49 @@ impl ConfigEnvironment {
/// Resolves `${NAME}` and `${NAME:-fallback}` placeholders embedded in one UTF-8 string.
///
/// Multiple placeholders are supported. Fallback text is literal in this prerelease and is not recursively interpreted as another placeholder expression.
/// This compatibility helper returns only the real runtime string. Use [`Self::resolve_text_detailed`] when safe value, sensitivity or provenance are
/// needed.
pub fn resolve_text(&self, source: &str) -> ksp_core_lib::Result<String> {
let mut output = String::new();
let resolved = self.resolve_text_detailed(source);
return match resolved {
std::result::Result::Ok(value) => std::result::Result::Ok(value.value().to_owned()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Resolves one UTF-8 string while preserving real/safe representations, strongest sensitivity and ordered provenance.
///
/// Multiple placeholders are supported. Fallback text is literal and inherits the sensitivity of the referenced variable. Literal-only strings are
/// classified as `Internal`; when placeholders are present, the result sensitivity is the strongest placeholder sensitivity.
pub fn resolve_text_detailed(&self, source: &str) -> ksp_core_lib::Result<crate::ResolvedConfigText> {
let mut value = String::new();
let mut safe_value = String::new();
let mut provenance = std::vec::Vec::<crate::ConfigValueProvenance>::new();
let mut sensitivity = crate::ConfigSensitivity::Public;
let mut saw_placeholder = false;
let mut remaining = source;
loop {
let start = remaining.find("${");
let start = match start {
std::option::Option::Some(value) => value,
std::option::Option::None => {
output.push_str(remaining);
return std::result::Result::Ok(output);
if !remaining.is_empty() {
value.push_str(remaining);
safe_value.push_str(remaining);
provenance.push(crate::ConfigValueProvenance::DocumentLiteral);
}
if !saw_placeholder {
sensitivity = crate::ConfigSensitivity::Internal;
}
return std::result::Result::Ok(crate::ResolvedConfigText::new(value, safe_value, sensitivity, provenance));
},
};
output.push_str(&remaining[..start]);
let literal = &remaining[..start];
if !literal.is_empty() {
value.push_str(literal);
safe_value.push_str(literal);
provenance.push(crate::ConfigValueProvenance::DocumentLiteral);
}
let expression_and_tail = &remaining[start + 2..];
let end = expression_and_tail.find('}');
let end = match end {
@@ -141,45 +187,48 @@ impl ConfigEnvironment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
output.push_str(resolved.value());
saw_placeholder = true;
sensitivity = sensitivity.strongest(resolved.sensitivity());
value.push_str(resolved.value());
safe_value.push_str(resolved.safe_value());
provenance.push(resolved.provenance());
remaining = &expression_and_tail[end + 1..];
}
}
/// Recursively resolves environment placeholders in JSON string values while preserving keys and non-string JSON values.
/// Recursively resolves environment placeholders in JSON values and returns only the real runtime tree.
///
/// Use [`Self::resolve_json_detailed`] when safe value, sensitivity or per-location provenance are needed.
pub fn resolve_json(&self, source: &serde_json::Value) -> ksp_core_lib::Result<serde_json::Value> {
return match source {
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => std::result::Result::Ok(source.clone()),
serde_json::Value::String(value) => {
let resolved = self.resolve_text(value.as_str());
match resolved {
std::result::Result::Ok(value) => std::result::Result::Ok(serde_json::Value::String(value)),
std::result::Result::Err(error) => std::result::Result::Err(error),
}
},
serde_json::Value::Array(values) => resolve_array(self, values),
serde_json::Value::Object(values) => {
let resolved = self.resolve_map(values);
match resolved {
std::result::Result::Ok(value) => std::result::Result::Ok(serde_json::Value::Object(value)),
std::result::Result::Err(error) => std::result::Result::Err(error),
}
},
let resolved = self.resolve_json_detailed(source);
return match resolved {
std::result::Result::Ok(value) => std::result::Result::Ok(value.value().clone()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Recursively resolves environment placeholders in one JSON object map while leaving the map keys unchanged.
/// Recursively resolves environment placeholders in JSON values while preserving real/safe trees, strongest sensitivity and JSON-Pointer provenance.
pub fn resolve_json_detailed(&self, source: &serde_json::Value) -> ksp_core_lib::Result<crate::ResolvedConfigJson> {
let mut provenance = std::collections::BTreeMap::<String, std::vec::Vec<crate::ConfigValueProvenance>>::new();
let resolved = resolve_json_node(self, source, "", &mut provenance);
let (value, safe_value, sensitivity) = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ResolvedConfigJson::new(value, safe_value, sensitivity, provenance));
}
/// Recursively resolves environment placeholders in one JSON object map while leaving map keys unchanged.
pub fn resolve_map(&self, source: &serde_json::Map<String, serde_json::Value>) -> ksp_core_lib::Result<serde_json::Map<String, serde_json::Value>> {
let mut output = serde_json::Map::<String, serde_json::Value>::new();
for (key, value) in source {
let resolved = self.resolve_json(value);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
output.insert(key.clone(), resolved);
}
return std::result::Result::Ok(output);
let resolved = self.resolve_json(&serde_json::Value::Object(source.clone()));
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return match resolved {
serde_json::Value::Object(value) => std::result::Result::Ok(value),
_ => std::result::Result::Err(invalid_placeholder_error("resolved JSON object changed shape")),
};
}
fn load_from_dotenv_path(dotenv_path: &std::path::Path) -> ksp_core_lib::Result<Self> {
@@ -350,20 +399,117 @@ fn parse_placeholder_expression(expression: &str) -> ksp_core_lib::Result<(&str,
return std::result::Result::Ok((variable_name, fallback));
}
fn resolve_array(environment: &ConfigEnvironment, source: &[serde_json::Value]) -> ksp_core_lib::Result<serde_json::Value> {
let mut output = std::vec::Vec::<serde_json::Value>::with_capacity(source.len());
for value in source {
let resolved = environment.resolve_json(value);
let resolved = match resolved {
fn resolved_environment_value(variable_name: &str, value: &str, source: ConfigEnvironmentSource) -> ksp_core_lib::Result<ConfigEnvironmentValue> {
let sensitivity = crate::ConfigSensitivity::from_variable_name(variable_name);
let sensitivity = match sensitivity {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let safe_value = if sensitivity.is_secret() { crate::REDACTED_CONFIG_VALUE.to_owned() } else { value.to_owned() };
return std::result::Result::Ok(ConfigEnvironmentValue {
variable_name: variable_name.to_owned(),
value: value.to_owned(),
safe_value,
sensitivity,
source,
});
}
fn environment_provenance(variable_name: &str, source: ConfigEnvironmentSource) -> crate::ConfigValueProvenance {
return match source {
ConfigEnvironmentSource::Process => crate::ConfigValueProvenance::EnvironmentProcess { variable_name: variable_name.to_owned() },
ConfigEnvironmentSource::DotEnv => crate::ConfigValueProvenance::EnvironmentDotEnv { variable_name: variable_name.to_owned() },
ConfigEnvironmentSource::Fallback => crate::ConfigValueProvenance::EnvironmentFallback { variable_name: variable_name.to_owned() },
};
}
fn resolve_json_node(
environment: &ConfigEnvironment,
source: &serde_json::Value,
pointer: &str,
provenance: &mut std::collections::BTreeMap<String, std::vec::Vec<crate::ConfigValueProvenance>>,
) -> ksp_core_lib::Result<(serde_json::Value, serde_json::Value, crate::ConfigSensitivity)> {
return match source {
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
provenance.insert(pointer.to_owned(), vec![crate::ConfigValueProvenance::DocumentLiteral]);
std::result::Result::Ok((source.clone(), source.clone(), crate::ConfigSensitivity::Internal))
},
serde_json::Value::String(value) => {
let resolved = environment.resolve_text_detailed(value.as_str());
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
provenance.insert(pointer.to_owned(), resolved.provenance().to_vec());
std::result::Result::Ok((
serde_json::Value::String(resolved.value().to_owned()),
serde_json::Value::String(resolved.safe_value().to_owned()),
resolved.sensitivity(),
))
},
serde_json::Value::Array(values) => resolve_json_array(environment, values, pointer, provenance),
serde_json::Value::Object(values) => resolve_json_object(environment, values, pointer, provenance),
};
}
fn resolve_json_array(
environment: &ConfigEnvironment,
source: &[serde_json::Value],
pointer: &str,
provenance: &mut std::collections::BTreeMap<String, std::vec::Vec<crate::ConfigValueProvenance>>,
) -> ksp_core_lib::Result<(serde_json::Value, serde_json::Value, crate::ConfigSensitivity)> {
let mut value = std::vec::Vec::<serde_json::Value>::with_capacity(source.len());
let mut safe_value = std::vec::Vec::<serde_json::Value>::with_capacity(source.len());
let mut sensitivity = crate::ConfigSensitivity::Public;
if source.is_empty() {
sensitivity = crate::ConfigSensitivity::Internal;
}
for (index, item) in source.iter().enumerate() {
let child_pointer = format!("{pointer}/{index}");
let resolved = resolve_json_node(environment, item, child_pointer.as_str(), provenance);
let (resolved_value, resolved_safe_value, resolved_sensitivity) = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
output.push(resolved);
value.push(resolved_value);
safe_value.push(resolved_safe_value);
sensitivity = sensitivity.strongest(resolved_sensitivity);
}
return std::result::Result::Ok(serde_json::Value::Array(output));
return std::result::Result::Ok((serde_json::Value::Array(value), serde_json::Value::Array(safe_value), sensitivity));
}
fn validate_supported_variable_name(variable_name: &str) -> ksp_core_lib::Result<()> {
fn resolve_json_object(
environment: &ConfigEnvironment,
source: &serde_json::Map<String, serde_json::Value>,
pointer: &str,
provenance: &mut std::collections::BTreeMap<String, std::vec::Vec<crate::ConfigValueProvenance>>,
) -> ksp_core_lib::Result<(serde_json::Value, serde_json::Value, crate::ConfigSensitivity)> {
let mut value = serde_json::Map::<String, serde_json::Value>::new();
let mut safe_value = serde_json::Map::<String, serde_json::Value>::new();
let mut sensitivity = crate::ConfigSensitivity::Public;
if source.is_empty() {
sensitivity = crate::ConfigSensitivity::Internal;
}
for (key, item) in source {
let escaped_key = escape_json_pointer_token(key.as_str());
let child_pointer = format!("{pointer}/{escaped_key}");
let resolved = resolve_json_node(environment, item, child_pointer.as_str(), provenance);
let (resolved_value, resolved_safe_value, resolved_sensitivity) = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
value.insert(key.clone(), resolved_value);
safe_value.insert(key.clone(), resolved_safe_value);
sensitivity = sensitivity.strongest(resolved_sensitivity);
}
return std::result::Result::Ok((serde_json::Value::Object(value), serde_json::Value::Object(safe_value), sensitivity));
}
fn escape_json_pointer_token(value: &str) -> String {
return value.replace('~', "~0").replace('/', "~1");
}
pub(crate) fn validate_supported_variable_name(variable_name: &str) -> ksp_core_lib::Result<()> {
if !has_supported_namespace(variable_name) {
return std::result::Result::Err(invalid_variable_error(variable_name, "variable must use the KSP_ or KSPB_ namespace"));
}

View File

@@ -1,14 +1,15 @@
// file: crates/ksp-config-lib/src/lib.rs
// version: 6
// version: 7
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! KSP-owned application configuration facade.
//!
//! `0.1.3-pre.010` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading, standard-document profile resolution, generic composite
//! `0.1.3-pre.011` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading, standard-document profile resolution, generic composite
//! resolution and KSP/KSPB environment resolution through process + `.env` + fallback precedence. The standard Logging document remains the first registered
//! runtime document. Sensitivity-aware safe values and persistence remain in later bounded prereleases.
//! runtime document. Environment-derived values now preserve real/safe representations, sensitivity and provenance; persistence remains in a later bounded
//! prerelease.
mod bootstrap;
mod composite;
@@ -17,6 +18,7 @@ mod environment;
mod error;
mod profile;
mod registry;
mod sensitivity;
/// Bootstrap argument used to replace the configuration document root.
pub use self::bootstrap::ARG_CFG_PATH;
@@ -40,7 +42,7 @@ pub use self::document::ConfigJsonDocument;
pub use self::environment::ConfigEnvironment;
/// Source that supplied one resolved Config environment variable.
pub use self::environment::ConfigEnvironmentSource;
/// One resolved Config environment variable and its winning source.
/// One resolved Config environment variable with real/safe values, sensitivity and its winning source.
pub use self::environment::ConfigEnvironmentValue;
/// Versioned environment contract template expected at the repository/runtime root.
pub use self::environment::DEFAULT_DOTENV_EXAMPLE_PATH;
@@ -112,3 +114,13 @@ pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
/// Logical file identifier for the standard Logging configuration document.
pub use self::registry::FILE_ID_STD_LOGGING;
/// Sensitivity assigned to one Config value after environment resolution.
pub use self::sensitivity::ConfigSensitivity;
/// Provenance segment participating in one resolved Config value.
pub use self::sensitivity::ConfigValueProvenance;
/// Replacement used for secret environment fragments in safe diagnostic representations.
pub use self::sensitivity::REDACTED_CONFIG_VALUE;
/// Recursively resolved JSON value preserving real/safe trees and provenance.
pub use self::sensitivity::ResolvedConfigJson;
/// One resolved Config string preserving real/safe representations and provenance.
pub use self::sensitivity::ResolvedConfigText;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/profile.rs
// version: 3
// version: 4
/// Origin of one top-level value in a resolved standard Config profile.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -83,9 +83,31 @@ impl ResolvedConfigProfile {
return self.origins.get(key).copied();
}
/// Resolves environment placeholders in the effective view while preserving this source profile and its Global/Profile provenance unchanged.
/// Resolves environment placeholders in the effective view and returns only the real runtime map.
///
/// Use [`Self::resolve_effective_environment_detailed`] when safe value, sensitivity and environment provenance are required. Global/Profile provenance on
/// this source profile remains unchanged in both cases.
pub fn resolve_effective_environment(&self, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<serde_json::Map<String, serde_json::Value>> {
return environment.resolve_map(&self.effective);
let resolved = self.resolve_effective_environment_detailed(environment);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return match resolved.value() {
serde_json::Value::Object(value) => std::result::Result::Ok(value.clone()),
_ => std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID,
"resolved Config profile effective view changed JSON shape",
)),
};
}
/// Resolves environment placeholders while preserving real/safe JSON trees, strongest sensitivity and JSON-Pointer environment provenance.
///
/// Top-level Global/Profile provenance remains available through [`Self::origin`]; the returned value adds literal/process/`.env`/fallback provenance for
/// the environment-resolution stage.
pub fn resolve_effective_environment_detailed(&self, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<crate::ResolvedConfigJson> {
return environment.resolve_json_detailed(&serde_json::Value::Object(self.effective.clone()));
}
}
@@ -93,8 +115,8 @@ impl crate::ConfigDocumentEngine {
/// Loads, validates and resolves one standard Config document to its default or explicitly requested profile.
///
/// Passing `None` selects the autonomous `default_profile` declared by the document. Passing `Some(profile_id)` selects that profile explicitly.
/// Environment interpolation is intentionally not applied implicitly by profile selection. Call `ResolvedConfigProfile::resolve_effective_environment` with
/// a Config-owned environment snapshot when an effective runtime view is required.
/// Environment interpolation is intentionally not applied implicitly by profile selection. Call `ResolvedConfigProfile::resolve_effective_environment` for
/// a real runtime map or `ResolvedConfigProfile::resolve_effective_environment_detailed` when safe value, sensitivity and provenance are also required.
pub fn load_resolved_profile(
&self,
file_id: &crate::ConfigFileId,

View File

@@ -0,0 +1,209 @@
// 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<Self> {
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<crate::ConfigEnvironmentSource> {
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<ConfigValueProvenance>,
}
impl ResolvedConfigText {
pub(crate) fn new(value: String, safe_value: String, sensitivity: ConfigSensitivity, provenance: std::vec::Vec<ConfigValueProvenance>) -> 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<String, std::vec::Vec<ConfigValueProvenance>>,
}
impl ResolvedConfigJson {
pub(crate) fn new(
value: serde_json::Value,
safe_value: serde_json::Value,
sensitivity: ConfigSensitivity,
provenance: std::collections::BTreeMap<String, std::vec::Vec<ConfigValueProvenance>>,
) -> 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<String, std::vec::Vec<ConfigValueProvenance>> {
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;