Files
khadhroony-solana-project/crates/ksp-config-lib/src/composite.rs

306 lines
13 KiB
Rust

// file: crates/ksp-config-lib/src/composite.rs
// version: 2
/// 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>,
}
/// Validates composite document contract.
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::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::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;