Files
khadhroony-solana-project/crates/ksp-config-lib/src/bootstrap.rs
2026-08-15 08:40:11 +02:00

206 lines
9.3 KiB
Rust

// file: crates/ksp-config-lib/src/bootstrap.rs
// version: 1
/// Default root containing KSP runtime configuration documents.
pub const DEFAULT_CFG_PATH: &str = "config";
/// Default root containing KSP JSON schemas.
pub const DEFAULT_SCHEMA_PATH: &str = "config/schemas";
/// Bootstrap argument used to replace the configuration document root.
pub const ARG_CFG_PATH: &str = "--cfgpath";
/// Bootstrap argument used to replace the schema root.
pub const ARG_SCHEMA_PATH: &str = "--schemapath";
/// Non-recursive bootstrap options required before Config can resolve any managed document.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigBootstrapOptions {
cfg_path: std::path::PathBuf,
schema_path: std::path::PathBuf,
}
impl ConfigBootstrapOptions {
/// Creates bootstrap options using the KSP hardcoded configuration and schema roots.
pub fn defaults() -> ksp_core_lib::Result<Self> {
return Self::from_paths(crate::DEFAULT_CFG_PATH, crate::DEFAULT_SCHEMA_PATH);
}
/// Creates bootstrap options from explicit programmatic configuration and schema roots.
pub fn from_paths(
cfg_path: impl std::convert::Into<std::path::PathBuf>,
schema_path: impl std::convert::Into<std::path::PathBuf>,
) -> ksp_core_lib::Result<Self> {
let cfg_path = validate_bootstrap_path(crate::ARG_CFG_PATH, cfg_path.into());
let cfg_path = match cfg_path {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let schema_path = validate_bootstrap_path(crate::ARG_SCHEMA_PATH, schema_path.into());
return match schema_path {
std::result::Result::Ok(value) => std::result::Result::Ok(Self { cfg_path, schema_path: value }),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Parses the KSP-owned bootstrap path arguments from a raw process argument slice.
///
/// Both `--cfgpath=value` / `--cfgpath value` and `--schemapath=value` / `--schemapath value` are accepted. Unrelated arguments are ignored so an
/// application can pass its complete argument vector. When the same bootstrap path is specified more than once, the last explicit value wins.
pub fn from_args(args: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
let mut options = Self::defaults_unchecked();
let mut index: usize = 0;
while index < args.len() {
let argument = &args[index];
if argument.as_os_str() == std::ffi::OsStr::new(crate::ARG_CFG_PATH) {
let parsed = parse_separate_path_argument(args, index, crate::ARG_CFG_PATH);
match parsed {
std::result::Result::Ok((path, next_index)) => {
options.cfg_path = path;
index = next_index;
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
} else if argument.as_os_str() == std::ffi::OsStr::new(crate::ARG_SCHEMA_PATH) {
let parsed = parse_separate_path_argument(args, index, crate::ARG_SCHEMA_PATH);
match parsed {
std::result::Result::Ok((path, next_index)) => {
options.schema_path = path;
index = next_index;
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
} else {
let inline = parse_inline_path_argument(argument);
match inline {
std::option::Option::Some((kind, path)) => match kind {
BootstrapPathKind::Config => options.cfg_path = path,
BootstrapPathKind::Schema => options.schema_path = path,
},
std::option::Option::None => {},
}
}
index += 1;
}
return Self::from_paths(options.cfg_path, options.schema_path);
}
/// Returns the root used for managed runtime configuration documents.
#[must_use]
pub fn cfg_path(&self) -> &std::path::Path {
return self.cfg_path.as_path();
}
/// Returns the root used for managed JSON schemas.
#[must_use]
pub fn schema_path(&self) -> &std::path::Path {
return self.schema_path.as_path();
}
/// Replaces the configuration document root after applying bootstrap path validation.
pub fn with_cfg_path(self, path: impl std::convert::Into<std::path::PathBuf>) -> ksp_core_lib::Result<Self> {
let validated = validate_bootstrap_path(crate::ARG_CFG_PATH, path.into());
return match validated {
std::result::Result::Ok(cfg_path) => std::result::Result::Ok(Self { cfg_path, schema_path: self.schema_path }),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Replaces the schema root after applying bootstrap path validation.
pub fn with_schema_path(self, path: impl std::convert::Into<std::path::PathBuf>) -> ksp_core_lib::Result<Self> {
let validated = validate_bootstrap_path(crate::ARG_SCHEMA_PATH, path.into());
return match validated {
std::result::Result::Ok(schema_path) => std::result::Result::Ok(Self { cfg_path: self.cfg_path, schema_path }),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
fn defaults_unchecked() -> Self {
return Self {
cfg_path: std::path::PathBuf::from(crate::DEFAULT_CFG_PATH),
schema_path: std::path::PathBuf::from(crate::DEFAULT_SCHEMA_PATH),
};
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum BootstrapPathKind {
Config,
Schema,
}
fn parse_inline_path_argument(argument: &std::ffi::OsStr) -> std::option::Option<(BootstrapPathKind, std::path::PathBuf)> {
let text = match argument.to_str() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let cfg_prefix = "--cfgpath=";
let schema_prefix = "--schemapath=";
if let std::option::Option::Some(value) = text.strip_prefix(cfg_prefix) {
return std::option::Option::Some((BootstrapPathKind::Config, std::path::PathBuf::from(value)));
}
if let std::option::Option::Some(value) = text.strip_prefix(schema_prefix) {
return std::option::Option::Some((BootstrapPathKind::Schema, std::path::PathBuf::from(value)));
}
return std::option::Option::None;
}
fn parse_separate_path_argument(args: &[std::ffi::OsString], index: usize, argument_name: &'static str) -> ksp_core_lib::Result<(std::path::PathBuf, usize)> {
let value_index = index + 1;
if value_index >= args.len() {
return std::result::Result::Err(missing_argument_value_error(argument_name));
}
let value = &args[value_index];
let option_like = match value.to_str() {
std::option::Option::Some(text) => text.starts_with("--"),
std::option::Option::None => false,
};
if option_like {
return std::result::Result::Err(missing_argument_value_error(argument_name));
}
return std::result::Result::Ok((std::path::PathBuf::from(value.as_os_str()), value_index));
}
fn validate_bootstrap_path(argument_name: &'static str, path: std::path::PathBuf) -> ksp_core_lib::Result<std::path::PathBuf> {
if path.as_os_str().is_empty() {
return std::result::Result::Err(invalid_path_error(argument_name, &path, "path is empty"));
}
let metadata = std::fs::metadata(path.as_path());
return match metadata {
std::result::Result::Ok(value) => {
if value.is_dir() {
std::result::Result::Ok(path)
} else {
std::result::Result::Err(invalid_path_error(argument_name, &path, "existing path is not a directory"))
}
},
std::result::Result::Err(error) => {
if error.kind() == std::io::ErrorKind::NotFound {
std::result::Result::Ok(path)
} else {
std::result::Result::Err(invalid_path_source_error(argument_name, &path, error))
}
},
};
}
fn missing_argument_value_error(argument_name: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE, "Config bootstrap argument requires a path value")
.with_context("argument", argument_name);
}
fn invalid_path_error(argument_name: &'static str, path: &std::path::Path, reason: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_BOOTSTRAP_INVALID_PATH, "Config bootstrap path is invalid")
.with_context("argument", argument_name)
.with_context("path", path.to_string_lossy().into_owned())
.with_context("reason", reason);
}
fn invalid_path_source_error(argument_name: &'static str, path: &std::path::Path, source: std::io::Error) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_BOOTSTRAP_INVALID_PATH, "Config bootstrap path cannot be inspected")
.with_context("argument", argument_name)
.with_context("path", path.to_string_lossy().into_owned())
.with_source(source);
}
#[cfg(test)]
#[path = "../unit_tests/bootstrap.rs"]
mod tests;