v0.1.3-pre.009
This commit is contained in:
309
crates/ksp-config-lib/src/composite.rs
Normal file
309
crates/ksp-config-lib/src/composite.rs
Normal file
@@ -0,0 +1,309 @@
|
||||
// file: crates/ksp-config-lib/src/composite.rs
|
||||
// version: 1
|
||||
|
||||
/// One resolved document component selected by a composite profile.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ResolvedCompositeComponent {
|
||||
component_id: String,
|
||||
resolved: crate::ResolvedConfigProfile,
|
||||
}
|
||||
|
||||
impl ResolvedCompositeComponent {
|
||||
/// Returns the component identifier unique inside the selected composite profile.
|
||||
#[must_use]
|
||||
pub fn component_id(&self) -> &str {
|
||||
return self.component_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the referenced standard document after global/profile resolution.
|
||||
#[must_use]
|
||||
pub const fn resolved(&self) -> &crate::ResolvedConfigProfile {
|
||||
return &self.resolved;
|
||||
}
|
||||
}
|
||||
|
||||
/// Validated composite document resolved to one composite profile and all of its referenced standard documents.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ResolvedConfigComposite {
|
||||
file_id: crate::ConfigFileId,
|
||||
path: std::path::PathBuf,
|
||||
profile_id: String,
|
||||
selection_source: crate::ConfigProfileSelectionSource,
|
||||
components: std::collections::BTreeMap<String, ResolvedCompositeComponent>,
|
||||
}
|
||||
|
||||
impl ResolvedConfigComposite {
|
||||
/// Returns the logical composite file identifier.
|
||||
#[must_use]
|
||||
pub fn file_id(&self) -> &crate::ConfigFileId {
|
||||
return &self.file_id;
|
||||
}
|
||||
|
||||
/// Returns the physical path of the validated composite source.
|
||||
#[must_use]
|
||||
pub fn path(&self) -> &std::path::Path {
|
||||
return self.path.as_path();
|
||||
}
|
||||
|
||||
/// Returns the selected composite profile identifier.
|
||||
#[must_use]
|
||||
pub fn profile_id(&self) -> &str {
|
||||
return self.profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns whether the composite profile came from `default_profile` or an explicit caller selection.
|
||||
#[must_use]
|
||||
pub const fn selection_source(&self) -> crate::ConfigProfileSelectionSource {
|
||||
return self.selection_source;
|
||||
}
|
||||
|
||||
/// Returns all resolved components keyed by their composite-local `component_id`.
|
||||
#[must_use]
|
||||
pub const fn components(&self) -> &std::collections::BTreeMap<String, ResolvedCompositeComponent> {
|
||||
return &self.components;
|
||||
}
|
||||
|
||||
/// Returns one resolved component by its composite-local identifier.
|
||||
#[must_use]
|
||||
pub fn component(&self, component_id: &str) -> std::option::Option<&ResolvedCompositeComponent> {
|
||||
return self.components.get(component_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ConfigDocumentEngine {
|
||||
/// Loads, validates and resolves one registered composite Config document.
|
||||
///
|
||||
/// Composite descriptors are introduced only when a concrete consumer exists. The generic contract and schema are available from `0.1.3-pre.009` onward.
|
||||
/// Passing `None` selects the composite's `default_profile`; passing `Some(profile_id)` selects a composite profile explicitly.
|
||||
pub fn load_resolved_composite(
|
||||
&self,
|
||||
file_id: &crate::ConfigFileId,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ResolvedConfigComposite> {
|
||||
let document = self.load_validated_document(file_id);
|
||||
let document = match document {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !document.file_id().as_str().starts_with("cfg.composite.") {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_COMPOSITE_REFERENCE_INVALID, "requested Config document is not a composite")
|
||||
.with_context("file_id", document.file_id().as_str()),
|
||||
);
|
||||
}
|
||||
return resolve_composite_document(self, &document, requested_profile);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CompositeDocumentSource {
|
||||
format_version: u32,
|
||||
default_profile: String,
|
||||
profiles: std::vec::Vec<CompositeProfileSource>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CompositeProfileSource {
|
||||
profile_id: String,
|
||||
documents: std::vec::Vec<CompositeDocumentReferenceSource>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CompositeDocumentReferenceSource {
|
||||
component_id: String,
|
||||
file_id: String,
|
||||
profile_id: std::option::Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn validate_composite_document_contract(engine: &crate::ConfigDocumentEngine, document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
if !document.file_id().as_str().starts_with("cfg.composite.") {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
let source = parse_composite_source(document);
|
||||
let source = match source {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if source.format_version != 1 {
|
||||
return std::result::Result::Err(composite_semantic_error(document, "unsupported composite format_version"));
|
||||
}
|
||||
if source.default_profile.trim().is_empty() {
|
||||
return std::result::Result::Err(composite_semantic_error(document, "composite default_profile must not be empty"));
|
||||
}
|
||||
for (profile_index, profile) in source.profiles.iter().enumerate() {
|
||||
if profile.profile_id.trim().is_empty() {
|
||||
return std::result::Result::Err(
|
||||
composite_semantic_error(document, "composite profile_id must not be empty").with_context("profile_index", profile_index.to_string()),
|
||||
);
|
||||
}
|
||||
let mut component_ids = std::collections::BTreeSet::<String>::new();
|
||||
for (component_index, reference) in profile.documents.iter().enumerate() {
|
||||
if component_ids.contains(reference.component_id.as_str()) {
|
||||
return std::result::Result::Err(
|
||||
composite_semantic_error(document, "component_id values must be unique inside one composite profile")
|
||||
.with_context("profile_id", profile.profile_id.as_str())
|
||||
.with_context("component_index", component_index.to_string())
|
||||
.with_context("component_id", reference.component_id.as_str()),
|
||||
);
|
||||
}
|
||||
component_ids.insert(reference.component_id.clone());
|
||||
let validation = validate_reference(engine, document, profile, reference, component_index);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn resolve_composite_document(
|
||||
engine: &crate::ConfigDocumentEngine,
|
||||
document: &crate::ConfigJsonDocument,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ResolvedConfigComposite> {
|
||||
let source = parse_composite_source(document);
|
||||
let source = match source {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let (selected_profile_id, selection_source) = match requested_profile {
|
||||
std::option::Option::Some(value) => (value, crate::ConfigProfileSelectionSource::Explicit),
|
||||
std::option::Option::None => (source.default_profile.as_str(), crate::ConfigProfileSelectionSource::DefaultProfile),
|
||||
};
|
||||
let mut selected: std::option::Option<&CompositeProfileSource> = std::option::Option::None;
|
||||
for profile in &source.profiles {
|
||||
if profile.profile_id == selected_profile_id {
|
||||
selected = std::option::Option::Some(profile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let selected = match selected {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_NOT_FOUND, "requested composite Config profile does not exist")
|
||||
.with_context("file_id", document.file_id().as_str())
|
||||
.with_context("path", document.path().to_string_lossy().into_owned())
|
||||
.with_context("profile_id", selected_profile_id),
|
||||
);
|
||||
},
|
||||
};
|
||||
let mut components = std::collections::BTreeMap::<String, ResolvedCompositeComponent>::new();
|
||||
for reference in &selected.documents {
|
||||
let file_id = crate::ConfigFileId::new(reference.file_id.as_str());
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let resolved = crate::profile::load_resolved_profile_with_source(
|
||||
engine,
|
||||
&file_id,
|
||||
reference.profile_id.as_deref(),
|
||||
crate::ConfigProfileSelectionSource::Composite,
|
||||
);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let component = ResolvedCompositeComponent { component_id: reference.component_id.clone(), resolved };
|
||||
components.insert(reference.component_id.clone(), component);
|
||||
}
|
||||
return std::result::Result::Ok(ResolvedConfigComposite {
|
||||
file_id: document.file_id().clone(),
|
||||
path: document.path().to_path_buf(),
|
||||
profile_id: selected_profile_id.to_owned(),
|
||||
selection_source,
|
||||
components,
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_reference(
|
||||
engine: &crate::ConfigDocumentEngine,
|
||||
document: &crate::ConfigJsonDocument,
|
||||
profile: &CompositeProfileSource,
|
||||
reference: &CompositeDocumentReferenceSource,
|
||||
component_index: usize,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
if reference.component_id.trim().is_empty() {
|
||||
return std::result::Result::Err(composite_reference_error(document, profile, reference, component_index, "component_id must not be empty"));
|
||||
}
|
||||
let file_id = crate::ConfigFileId::new(reference.file_id.as_str());
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(composite_reference_error(document, profile, reference, component_index, "referenced file_id is invalid"));
|
||||
},
|
||||
};
|
||||
if !file_id.as_str().starts_with("cfg.std.") {
|
||||
return std::result::Result::Err(composite_reference_error(
|
||||
document,
|
||||
profile,
|
||||
reference,
|
||||
component_index,
|
||||
"composite references must target standard Config document file_ids",
|
||||
));
|
||||
}
|
||||
let descriptor = engine.registry().descriptor(&file_id);
|
||||
let descriptor = match descriptor {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(composite_reference_error(document, profile, reference, component_index, "referenced file_id is not registered"));
|
||||
},
|
||||
};
|
||||
if descriptor.kind() != crate::ConfigFileKind::Config {
|
||||
return std::result::Result::Err(composite_reference_error(
|
||||
document,
|
||||
profile,
|
||||
reference,
|
||||
component_index,
|
||||
"referenced file_id is not a Config document",
|
||||
));
|
||||
}
|
||||
let resolved =
|
||||
crate::profile::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
|
||||
if let std::result::Result::Err(error) = resolved {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn parse_composite_source(document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<CompositeDocumentSource> {
|
||||
let source = serde_json::from_value::<CompositeDocumentSource>(document.value().clone());
|
||||
return match source {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
composite_semantic_error(document, "schema-valid composite document cannot be decoded into the KSP source contract").with_source(error),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn composite_semantic_error(document: &crate::ConfigJsonDocument, reason: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID, "Config composite violates KSP semantic invariants")
|
||||
.with_context("file_id", document.file_id().as_str())
|
||||
.with_context("path", document.path().to_string_lossy().into_owned())
|
||||
.with_context("reason", reason);
|
||||
}
|
||||
|
||||
fn composite_reference_error(
|
||||
document: &crate::ConfigJsonDocument,
|
||||
profile: &CompositeProfileSource,
|
||||
reference: &CompositeDocumentReferenceSource,
|
||||
component_index: usize,
|
||||
reason: &'static str,
|
||||
) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_COMPOSITE_REFERENCE_INVALID, "Config composite contains an invalid document reference")
|
||||
.with_context("file_id", document.file_id().as_str())
|
||||
.with_context("path", document.path().to_string_lossy().into_owned())
|
||||
.with_context("profile_id", profile.profile_id.as_str())
|
||||
.with_context("component_index", component_index.to_string())
|
||||
.with_context("component_id", reference.component_id.as_str())
|
||||
.with_context("referenced_file_id", reference.file_id.as_str())
|
||||
.with_context("reason", reason);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/composite.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/document.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
/// A Config-managed JSON document that has passed syntax, schema and current semantic validation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -97,7 +97,7 @@ impl ConfigDocumentEngine {
|
||||
std::result::Result::Ok(()) => {},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
let semantic_validation = validate_document_semantics(&document);
|
||||
let semantic_validation = validate_document_semantics(self, &document);
|
||||
return match semantic_validation {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(document),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
@@ -207,11 +207,15 @@ fn validate_instance(document: &ConfigJsonDocument, schema: &ConfigJsonDocument)
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_document_semantics(document: &ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
fn validate_document_semantics(engine: &ConfigDocumentEngine, document: &ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
let profile_validation = crate::profile::validate_document_profile_contract(document);
|
||||
if let std::result::Result::Err(error) = profile_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let composite_validation = crate::composite::validate_composite_document_contract(engine, document);
|
||||
if let std::result::Result::Err(error) = composite_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if document.file_id().as_str() != crate::FILE_ID_STD_LOGGING {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/error.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Error code used when a Config bootstrap argument is missing its value.
|
||||
pub const ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "bootstrap_argument_missing_value");
|
||||
@@ -36,3 +36,6 @@ pub const ERROR_CODE_DOCUMENT_SEMANTIC_INVALID: ksp_core_lib::ErrorCode = ksp_co
|
||||
|
||||
/// Error code used when an explicitly requested Config profile does not exist in a validated document.
|
||||
pub const ERROR_CODE_PROFILE_NOT_FOUND: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "profile_not_found");
|
||||
|
||||
/// Error code used when a composite document references an invalid, unknown, or unsupported Config document.
|
||||
pub const ERROR_CODE_COMPOSITE_REFERENCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "composite_reference_invalid");
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// file: crates/ksp-config-lib/src/lib.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! KSP-owned application configuration facade.
|
||||
//!
|
||||
//! `0.1.3-pre.008` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading and standard-document profile resolution. The standard
|
||||
//! Logging document is the first registered runtime document. Composite resolution, environment substitution, sensitivity and persistence remain in later bounded
|
||||
//! prereleases.
|
||||
//! `0.1.3-pre.009` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading, standard-document profile resolution and generic composite
|
||||
//! resolution by stable `file_id`. The standard Logging document remains the first registered runtime document. Environment substitution, sensitivity and persistence
|
||||
//! remain in later bounded prereleases.
|
||||
|
||||
mod bootstrap;
|
||||
mod composite;
|
||||
mod document;
|
||||
mod error;
|
||||
mod profile;
|
||||
@@ -26,6 +27,10 @@ pub use self::bootstrap::ConfigBootstrapOptions;
|
||||
pub use self::bootstrap::DEFAULT_CFG_PATH;
|
||||
/// Default root containing KSP JSON schemas.
|
||||
pub use self::bootstrap::DEFAULT_SCHEMA_PATH;
|
||||
/// One resolved document component selected by a composite profile.
|
||||
pub use self::composite::ResolvedCompositeComponent;
|
||||
/// Validated composite document resolved to one profile and its referenced standard documents.
|
||||
pub use self::composite::ResolvedConfigComposite;
|
||||
/// Generic JSON/JSON Schema engine owned by Config.
|
||||
pub use self::document::ConfigDocumentEngine;
|
||||
/// A Config-managed JSON document after syntax, schema and current semantic validation.
|
||||
@@ -34,6 +39,8 @@ pub use self::document::ConfigJsonDocument;
|
||||
pub use self::error::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE;
|
||||
/// Error code used when a Config bootstrap path is empty, inaccessible, or resolves to an existing non-directory path.
|
||||
pub use self::error::ERROR_CODE_BOOTSTRAP_INVALID_PATH;
|
||||
/// Error code used when a composite document contains an invalid or unsupported document reference.
|
||||
pub use self::error::ERROR_CODE_COMPOSITE_REFERENCE_INVALID;
|
||||
/// Error code used when a schema-valid Config document violates KSP semantic invariants.
|
||||
pub use self::error::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID;
|
||||
/// Error code used when the same logical Config file identifier is registered more than once.
|
||||
@@ -70,10 +77,14 @@ pub use self::registry::ConfigFileId;
|
||||
pub use self::registry::ConfigFileKind;
|
||||
/// Registry of KSP-known logical Config files and their replaceable physical filenames.
|
||||
pub use self::registry::ConfigFileRegistry;
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub use self::registry::DEFAULT_COMPOSITE_SCHEMA_FILENAME;
|
||||
/// Default physical filename for the standard Logging configuration document.
|
||||
pub use self::registry::DEFAULT_STD_LOGGING_FILENAME;
|
||||
/// Default physical filename for the standard Logging JSON Schema document.
|
||||
pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
||||
/// Logical file identifier for the standard Logging configuration document.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/profile.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Origin of one top-level value in a resolved standard Config profile.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -17,6 +17,8 @@ pub enum ConfigProfileSelectionSource {
|
||||
DefaultProfile,
|
||||
/// A caller explicitly requested the profile by `profile_id`.
|
||||
Explicit,
|
||||
/// A composite document selected the referenced standard document profile.
|
||||
Composite,
|
||||
}
|
||||
|
||||
/// Validated standard document resolved to one profile while retaining global/profile provenance.
|
||||
@@ -86,7 +88,7 @@ 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 and composite overrides are intentionally not applied by this prerelease.
|
||||
/// Environment interpolation is intentionally not applied by this prerelease.
|
||||
pub fn load_resolved_profile(
|
||||
&self,
|
||||
file_id: &crate::ConfigFileId,
|
||||
@@ -97,10 +99,32 @@ impl crate::ConfigDocumentEngine {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return resolve_document_profile(&document, requested_profile);
|
||||
let source = match requested_profile {
|
||||
std::option::Option::Some(_) => ConfigProfileSelectionSource::Explicit,
|
||||
std::option::Option::None => ConfigProfileSelectionSource::DefaultProfile,
|
||||
};
|
||||
return resolve_document_profile(&document, requested_profile, source);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_resolved_profile_with_source(
|
||||
engine: &crate::ConfigDocumentEngine,
|
||||
file_id: &crate::ConfigFileId,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
explicit_source: ConfigProfileSelectionSource,
|
||||
) -> ksp_core_lib::Result<ResolvedConfigProfile> {
|
||||
let document = engine.load_validated_document(file_id);
|
||||
let document = match document {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source = match requested_profile {
|
||||
std::option::Option::Some(_) => explicit_source,
|
||||
std::option::Option::None => ConfigProfileSelectionSource::DefaultProfile,
|
||||
};
|
||||
return resolve_document_profile(&document, requested_profile, source);
|
||||
}
|
||||
|
||||
pub(crate) fn validate_document_profile_contract(document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
let root = match document.value().as_object() {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -158,7 +182,11 @@ pub(crate) fn validate_document_profile_contract(document: &crate::ConfigJsonDoc
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn resolve_document_profile(document: &crate::ConfigJsonDocument, requested_profile: std::option::Option<&str>) -> ksp_core_lib::Result<ResolvedConfigProfile> {
|
||||
fn resolve_document_profile(
|
||||
document: &crate::ConfigJsonDocument,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
explicit_source: ConfigProfileSelectionSource,
|
||||
) -> ksp_core_lib::Result<ResolvedConfigProfile> {
|
||||
let root = match document.value().as_object() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(profile_semantic_error(document, "profile resolution requires an object document")),
|
||||
@@ -168,7 +196,7 @@ fn resolve_document_profile(document: &crate::ConfigJsonDocument, requested_prof
|
||||
std::option::Option::None => return std::result::Result::Err(profile_semantic_error(document, "profile resolution requires default_profile")),
|
||||
};
|
||||
let (selected_profile, selection_source) = match requested_profile {
|
||||
std::option::Option::Some(value) => (value, ConfigProfileSelectionSource::Explicit),
|
||||
std::option::Option::Some(value) => (value, explicit_source),
|
||||
std::option::Option::None => (default_profile, ConfigProfileSelectionSource::DefaultProfile),
|
||||
};
|
||||
let profiles = match root.get("profiles").and_then(serde_json::Value::as_array) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/registry.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
/// Bootstrap argument used to replace a known Config filename mapping.
|
||||
pub const ARG_FILE_MAP: &str = "--filemap";
|
||||
@@ -7,10 +7,14 @@ pub const ARG_FILE_MAP: &str = "--filemap";
|
||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||
/// Default physical filename for the standard Logging configuration document.
|
||||
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
||||
/// Default physical filename for the standard Logging JSON Schema document.
|
||||
pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
||||
|
||||
/// Stable logical identifier for a Config-managed file.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
@@ -77,7 +81,7 @@ impl ConfigFileDescriptor {
|
||||
return self.schema_file_id.as_ref();
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub(crate) fn new(
|
||||
file_id: &'static str,
|
||||
kind: ConfigFileKind,
|
||||
filename: &'static str,
|
||||
@@ -131,7 +135,13 @@ impl ConfigFileRegistry {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return build_registry([logging, logging_schema]);
|
||||
let composite_schema =
|
||||
ConfigFileDescriptor::new(FILE_ID_SCHEMA_COMPOSITE, ConfigFileKind::Schema, DEFAULT_COMPOSITE_SCHEMA_FILENAME, std::option::Option::None);
|
||||
let composite_schema = match composite_schema {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return build_registry([logging, logging_schema, composite_schema]);
|
||||
}
|
||||
|
||||
/// Creates the default registry and applies repeatable `--filemap=<file_id>=<filename>` overrides from raw process arguments.
|
||||
@@ -208,7 +218,7 @@ impl ConfigFileRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_registry<const N: usize>(descriptors: [ConfigFileDescriptor; N]) -> ksp_core_lib::Result<ConfigFileRegistry> {
|
||||
pub(crate) fn build_registry<const N: usize>(descriptors: [ConfigFileDescriptor; N]) -> ksp_core_lib::Result<ConfigFileRegistry> {
|
||||
let mut registry = ConfigFileRegistry { descriptors: std::collections::BTreeMap::new() };
|
||||
for descriptor in descriptors {
|
||||
let file_id = descriptor.file_id.clone();
|
||||
|
||||
Reference in New Issue
Block a user