v0.1.3-pre.002
This commit is contained in:
14
crates/ksp-config-lib/Cargo.toml
Normal file
14
crates/ksp-config-lib/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
# file: crates/ksp-config-lib/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "ksp-config-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
205
crates/ksp-config-lib/src/bootstrap.rs
Normal file
205
crates/ksp-config-lib/src/bootstrap.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
// 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;
|
||||
8
crates/ksp-config-lib/src/error.rs
Normal file
8
crates/ksp-config-lib/src/error.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// file: crates/ksp-config-lib/src/error.rs
|
||||
// version: 1
|
||||
|
||||
/// 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");
|
||||
|
||||
/// Error code used when a Config bootstrap path is empty, inaccessible, or resolves to an existing non-directory path.
|
||||
pub const ERROR_CODE_BOOTSTRAP_INVALID_PATH: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "bootstrap_invalid_path");
|
||||
29
crates/ksp-config-lib/src/lib.rs
Normal file
29
crates/ksp-config-lib/src/lib.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// file: crates/ksp-config-lib/src/lib.rs
|
||||
// version: 1
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! KSP-owned application configuration facade.
|
||||
//!
|
||||
//! `0.1.3-pre.002` establishes the non-recursive bootstrap boundary used before any managed configuration document can be read. Configuration and schema
|
||||
//! roots have hardcoded KSP defaults and can only be replaced through explicit bootstrap arguments or the programmatic [`ConfigBootstrapOptions`] API.
|
||||
//! Document registries, JSON/schema loading, profiles, environment resolution and persistence are introduced by later bounded prereleases.
|
||||
|
||||
mod bootstrap;
|
||||
mod error;
|
||||
|
||||
/// Bootstrap argument used to replace the configuration document root.
|
||||
pub use self::bootstrap::ARG_CFG_PATH;
|
||||
/// Bootstrap argument used to replace the schema root.
|
||||
pub use self::bootstrap::ARG_SCHEMA_PATH;
|
||||
/// Non-recursive bootstrap options required before Config can resolve any managed document.
|
||||
pub use self::bootstrap::ConfigBootstrapOptions;
|
||||
/// Default root containing KSP runtime configuration documents.
|
||||
pub use self::bootstrap::DEFAULT_CFG_PATH;
|
||||
/// Default root containing KSP JSON schemas.
|
||||
pub use self::bootstrap::DEFAULT_SCHEMA_PATH;
|
||||
/// Error code used when a Config bootstrap argument is missing its value.
|
||||
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;
|
||||
40
crates/ksp-config-lib/tests/public_api.rs
Normal file
40
crates/ksp-config-lib/tests/public_api.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// file: crates/ksp-config-lib/tests/public_api.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn bootstrap_contract_is_available_from_crate_root() {
|
||||
let result = ksp_config_lib::ConfigBootstrapOptions::defaults();
|
||||
assert!(result.is_ok(), "default bootstrap options should be available: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(ksp_config_lib::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(ksp_config_lib::DEFAULT_SCHEMA_PATH));
|
||||
assert_eq!(ksp_config_lib::ARG_CFG_PATH, "--cfgpath");
|
||||
assert_eq!(ksp_config_lib::ARG_SCHEMA_PATH, "--schemapath");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmatic_bootstrap_paths_are_independent() {
|
||||
let result = ksp_config_lib::ConfigBootstrapOptions::from_paths("runtime-config", "runtime-schemas");
|
||||
assert!(result.is_ok(), "programmatic bootstrap paths should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("runtime-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("runtime-schemas"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_bootstrap_parser_is_available_from_crate_root() {
|
||||
let args = [
|
||||
std::ffi::OsString::from("consumer"),
|
||||
std::ffi::OsString::from("--cfgpath=consumer-config"),
|
||||
std::ffi::OsString::from("--schemapath"),
|
||||
std::ffi::OsString::from("consumer-schemas"),
|
||||
];
|
||||
let result = ksp_config_lib::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "public bootstrap parser should accept KSP path arguments: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("consumer-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("consumer-schemas"));
|
||||
}
|
||||
}
|
||||
142
crates/ksp-config-lib/unit_tests/bootstrap.rs
Normal file
142
crates/ksp-config-lib/unit_tests/bootstrap.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/bootstrap.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn defaults_use_hardcoded_ksp_roots() {
|
||||
let result = super::ConfigBootstrapOptions::defaults();
|
||||
assert!(result.is_ok(), "default bootstrap paths should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cfg_path_override_keeps_schema_default() {
|
||||
let defaults = super::ConfigBootstrapOptions::defaults();
|
||||
assert!(defaults.is_ok(), "default bootstrap paths should be valid: {defaults:?}");
|
||||
if let std::result::Result::Ok(options) = defaults {
|
||||
let result = options.with_cfg_path("custom-config");
|
||||
assert!(result.is_ok(), "cfg path override should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("custom-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_path_override_keeps_cfg_default() {
|
||||
let defaults = super::ConfigBootstrapOptions::defaults();
|
||||
assert!(defaults.is_ok(), "default bootstrap paths should be valid: {defaults:?}");
|
||||
if let std::result::Result::Ok(options) = defaults {
|
||||
let result = options.with_schema_path("custom-schemas");
|
||||
assert!(result.is_ok(), "schema path override should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("custom-schemas"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cfg_cli_override_keeps_schema_default() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath=cli-config")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "cfg CLI override should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("cli-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_cli_override_keeps_cfg_default() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--schemapath=cli-schemas")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "schema CLI override should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("cli-schemas"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_accepts_inline_and_separate_forms_and_last_value_wins() {
|
||||
let args = [
|
||||
std::ffi::OsString::from("ksp-app"),
|
||||
std::ffi::OsString::from("--cfgpath=first-config"),
|
||||
std::ffi::OsString::from("--unrelated"),
|
||||
std::ffi::OsString::from("--cfgpath"),
|
||||
std::ffi::OsString::from("second-config"),
|
||||
std::ffi::OsString::from("--schemapath=first-schemas"),
|
||||
std::ffi::OsString::from("--schemapath"),
|
||||
std::ffi::OsString::from("second-schemas"),
|
||||
];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "bootstrap arguments should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("second-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("second-schemas"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_reports_missing_separate_value() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "missing value must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_reports_another_option_as_missing_separate_value() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath"), std::ffi::OsString::from("--other-option")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "another option must not become a bootstrap path value");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_inline_path_is_rejected() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--schemapath=")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "empty path must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_INVALID_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_programmatic_paths_do_not_depend_on_default_roots() {
|
||||
let result = super::ConfigBootstrapOptions::from_paths("programmatic-config", "programmatic-schemas");
|
||||
assert!(result.is_ok(), "explicit programmatic paths should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("programmatic-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("programmatic-schemas"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_non_directory_path_is_rejected() {
|
||||
let fixture = unique_fixture_path("existing-file");
|
||||
let create = std::fs::write(fixture.as_path(), b"fixture");
|
||||
assert!(create.is_ok(), "fixture file should be creatable: {create:?}");
|
||||
let result = super::ConfigBootstrapOptions::from_paths(fixture.as_path(), "programmatic-schemas");
|
||||
let remove = std::fs::remove_file(fixture.as_path());
|
||||
assert!(remove.is_ok(), "fixture file should be removable: {remove:?}");
|
||||
assert!(result.is_err(), "existing file must not be accepted as a bootstrap directory");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_INVALID_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
fn unique_fixture_path(name: &str) -> std::path::PathBuf {
|
||||
let mut path = std::env::temp_dir();
|
||||
path.push(format!("ksp-config-lib-{name}-{}", std::process::id()));
|
||||
return path;
|
||||
}
|
||||
Reference in New Issue
Block a user