v0.1.4-pre.003
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/document.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// A Config-managed JSON document that has passed syntax, schema and current semantic validation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -85,6 +85,20 @@ impl ConfigDocumentEngine {
|
||||
return self.validate_document(document, &schema_file_id);
|
||||
}
|
||||
|
||||
pub(crate) fn validate_source_candidate(&self, file_id: &crate::ConfigFileId, source: &str) -> ksp_core_lib::Result<ConfigJsonDocument> {
|
||||
let path = self.registry.resolve_path(&self.bootstrap, file_id);
|
||||
let path = match path {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let value = serde_json::from_str::<serde_json::Value>(source);
|
||||
let value = match value {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(json_syntax_error(file_id, path.as_path(), error)),
|
||||
};
|
||||
return self.validate_candidate(file_id, value);
|
||||
}
|
||||
|
||||
pub(crate) fn validate_candidate(&self, file_id: &crate::ConfigFileId, value: serde_json::Value) -> ksp_core_lib::Result<ConfigJsonDocument> {
|
||||
let descriptor = self.registry.descriptor(file_id);
|
||||
let descriptor = match descriptor {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/management.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Raw source of one registered Config document read for explicit management/correction.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
@@ -625,6 +625,29 @@ impl ConfigManagement {
|
||||
};
|
||||
}
|
||||
|
||||
/// Validates and atomically persists a raw source candidate for one registered Config document.
|
||||
///
|
||||
/// The candidate is parsed, schema-validated and checked against KSP semantic invariants before any destination bytes are replaced. The raw source text is
|
||||
/// preserved exactly when persistence succeeds, allowing a management editor to repair an invalid document without bypassing Config ownership.
|
||||
pub fn save_source_candidate(&self, file_id: &crate::ConfigFileId, source: &str) -> ksp_core_lib::Result<ConfigDocumentChangeReport> {
|
||||
let descriptor = self.engine.registry().descriptor(file_id);
|
||||
let descriptor = match descriptor {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if descriptor.kind() != crate::ConfigFileKind::Config {
|
||||
return std::result::Result::Err(
|
||||
management_error("management source persistence requires a Config document").with_context("file_id", file_id.as_str()),
|
||||
);
|
||||
}
|
||||
let validated = self.engine.validate_source_candidate(file_id, source);
|
||||
let validated = match validated {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return persist_document_source(file_id, validated.path(), source.as_bytes());
|
||||
}
|
||||
|
||||
/// Loads the validated `std.logging.json` source into its typed management contract.
|
||||
pub fn load_logging_document(&self) -> ksp_core_lib::Result<LoggingConfigDocument> {
|
||||
let file_id = logging_file_id();
|
||||
@@ -675,27 +698,7 @@ impl ConfigManagement {
|
||||
},
|
||||
};
|
||||
serialized.push('\n');
|
||||
let existing = std::fs::read(validated.path());
|
||||
let existing = match existing {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => std::vec::Vec::new(),
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "existing Config document cannot be read before persistence")
|
||||
.with_context("file_id", file_id.as_str())
|
||||
.with_context("path", validated.path().to_string_lossy().into_owned())
|
||||
.with_source(error),
|
||||
);
|
||||
},
|
||||
};
|
||||
if existing.as_slice() == serialized.as_bytes() {
|
||||
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: false, reload_required: false });
|
||||
}
|
||||
let write = crate::persistence::atomic_write(validated.path(), serialized.as_bytes());
|
||||
if let std::result::Result::Err(error) = write {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: true, reload_required: true });
|
||||
return persist_document_source(&file_id, validated.path(), serialized.as_bytes());
|
||||
}
|
||||
|
||||
/// Returns safe desired/effective/shadow reports for every KSP/KSPB variable present in process or `.env` sources.
|
||||
@@ -999,6 +1002,30 @@ fn encode_dotenv_value(value: &str) -> String {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
fn persist_document_source(file_id: &crate::ConfigFileId, path: &std::path::Path, source: &[u8]) -> ksp_core_lib::Result<ConfigDocumentChangeReport> {
|
||||
let existing = std::fs::read(path);
|
||||
let existing = match existing {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => std::vec::Vec::new(),
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "existing Config document cannot be read before persistence")
|
||||
.with_context("file_id", file_id.as_str())
|
||||
.with_context("path", path.to_string_lossy().into_owned())
|
||||
.with_source(error),
|
||||
);
|
||||
},
|
||||
};
|
||||
if existing.as_slice() == source {
|
||||
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: false, reload_required: false });
|
||||
}
|
||||
let write = crate::persistence::atomic_write(path, source);
|
||||
if let std::result::Result::Err(error) = write {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: true, reload_required: true });
|
||||
}
|
||||
|
||||
fn management_error(reason: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_MANAGEMENT_OPERATION_INVALID, "Config management operation is invalid").with_context("reason", reason);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user