0.5.1-pre.005
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// file: ks-config/src/lib.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Khadhroony Bot3 workspace configuration contract and loading helpers.
|
||||
//! Khadhroony Solana application configuration contract and loading helpers.
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -37,12 +37,6 @@ pub use self::settings::HttpEndpointConfig;
|
||||
pub use self::settings::ListenerConfig;
|
||||
/// Exposes the log listener configuration type.
|
||||
pub use self::settings::LogListenerConfig;
|
||||
/// Exposes the logging target configuration type.
|
||||
pub use self::settings::LogTargetConfig;
|
||||
/// Exposes the logging target filter configuration type.
|
||||
pub use self::settings::LogTargetFilterConfig;
|
||||
/// Exposes the logging configuration type.
|
||||
pub use self::settings::LoggingConfig;
|
||||
/// Exposes the PostgreSQL configuration type.
|
||||
pub use self::settings::PostgresConfig;
|
||||
/// Exposes the profile configuration type.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// file: ks-config/src/settings.rs
|
||||
// version: 19
|
||||
// version: 20
|
||||
|
||||
//! Typed configuration models shared by applications and workers.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
const CONFIG_JSON_SCHEMA: &str = include_str!("../../config/schema.config.json");
|
||||
const CONFIG_JSON_SCHEMA: &str = include_str!("../../config/schemas/app.config.schema.json");
|
||||
|
||||
/// Root configuration containing every named profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
@@ -28,8 +28,6 @@ pub struct ProfileConfig {
|
||||
pub name: std::string::String,
|
||||
/// Application metadata.
|
||||
pub app: AppSectionConfig,
|
||||
/// Logging configuration.
|
||||
pub logging: LoggingConfig,
|
||||
/// Database configuration.
|
||||
pub database: DatabaseConfig,
|
||||
/// Local data directories.
|
||||
@@ -59,61 +57,6 @@ pub struct AppSectionConfig {
|
||||
pub auto_reconnect_default: bool,
|
||||
}
|
||||
|
||||
/// Logging configuration shared by apps and worker processes.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/LoggingConfig.ts"
|
||||
)]
|
||||
pub struct LoggingConfig {
|
||||
/// Default log level.
|
||||
pub default_level: std::string::String,
|
||||
/// Output targets.
|
||||
pub targets: std::vec::Vec<LogTargetConfig>,
|
||||
/// Target-specific filters.
|
||||
pub target_filters: std::vec::Vec<LogTargetFilterConfig>,
|
||||
}
|
||||
|
||||
/// Single logging output target.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/LogTargetConfig.ts"
|
||||
)]
|
||||
pub struct LogTargetConfig {
|
||||
/// Output target name.
|
||||
pub name: std::string::String,
|
||||
/// Enables this target.
|
||||
pub enabled: bool,
|
||||
/// Sink kind, such as console or file.
|
||||
pub sink: std::string::String,
|
||||
/// Minimum level for this target.
|
||||
pub level: std::string::String,
|
||||
/// File path for file sinks or an empty string for console sinks.
|
||||
pub path: std::string::String,
|
||||
/// Rotation mode for file sinks.
|
||||
pub rotation: std::string::String,
|
||||
/// Message format, such as human or json.
|
||||
pub format: std::string::String,
|
||||
/// Enables ANSI escape sequences for this target.
|
||||
pub ansi: bool,
|
||||
/// Included tracing targets or globs.
|
||||
pub targets: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Per-target logging filter.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/LogTargetFilterConfig.ts"
|
||||
)]
|
||||
pub struct LogTargetFilterConfig {
|
||||
/// Tracing target or crate prefix.
|
||||
pub target: std::string::String,
|
||||
/// Level assigned to the target.
|
||||
pub level: std::string::String,
|
||||
}
|
||||
|
||||
/// Database backend configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
@@ -650,10 +593,6 @@ fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
match validate_logging(&profile.logging) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
match validate_database(&profile.database) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -685,109 +624,6 @@ fn validate_app_section(config: &AppSectionConfig) -> ks_core::Result<()> {
|
||||
return require_non_empty(&config.environment, "app.environment");
|
||||
}
|
||||
|
||||
fn validate_logging(config: &LoggingConfig) -> ks_core::Result<()> {
|
||||
match validate_log_level(&config.default_level, "logging.default_level") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
if config.targets.is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_targets_empty",
|
||||
"at least one logging target is required",
|
||||
));
|
||||
}
|
||||
let mut enabled_count = 0_u32;
|
||||
let mut names = std::collections::BTreeSet::<std::string::String>::new();
|
||||
for target in &config.targets {
|
||||
if target.enabled {
|
||||
enabled_count += 1;
|
||||
}
|
||||
if !names.insert(target.name.clone()) {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_target_duplicate",
|
||||
target.name.clone(),
|
||||
));
|
||||
}
|
||||
match validate_log_target(target) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
for filter in &config.target_filters {
|
||||
match require_non_empty(&filter.target, "logging.target_filters.target") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
match validate_log_level(&filter.level, "logging.target_filters.level") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
if enabled_count == 0 {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_no_enabled_targets",
|
||||
"at least one logging target must be enabled",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_log_target(config: &LogTargetConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.name, "logging.targets.name") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
match validate_log_level(&config.level, "logging.targets.level") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
if config.sink != "console" && config.sink != "file" {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_sink_invalid",
|
||||
config.sink.clone(),
|
||||
));
|
||||
}
|
||||
if config.rotation != "none"
|
||||
&& config.rotation != "never"
|
||||
&& config.rotation != "daily"
|
||||
&& config.rotation != "hourly"
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_rotation_invalid",
|
||||
config.rotation.clone(),
|
||||
));
|
||||
}
|
||||
if config.format != "human"
|
||||
&& config.format != "compact"
|
||||
&& config.format != "pretty"
|
||||
&& config.format != "json"
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_format_invalid",
|
||||
config.format.clone(),
|
||||
));
|
||||
}
|
||||
if config.sink == "file" && config.path.trim().is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_file_path_empty",
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
if config.sink == "console" && !config.path.trim().is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"logging_console_path_not_empty",
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
for target in &config.targets {
|
||||
match require_non_empty(target, "logging.targets.targets") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_database(config: &DatabaseConfig) -> ks_core::Result<()> {
|
||||
if config.backend != "postgres" && config.backend != "sqlite" {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
@@ -1179,22 +1015,6 @@ fn validate_wallet_execution_pair(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_log_level(value: &str, field_name: &str) -> ks_core::Result<()> {
|
||||
if value == "trace"
|
||||
|| value == "debug"
|
||||
|| value == "info"
|
||||
|| value == "warn"
|
||||
|| value == "error"
|
||||
|| value == "off"
|
||||
{
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"log_level_invalid",
|
||||
format!("{field_name}: {value}"),
|
||||
));
|
||||
}
|
||||
|
||||
fn require_non_empty(value: &str, field_name: &str) -> ks_core::Result<()> {
|
||||
if value.trim().is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
@@ -1225,13 +1045,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
const EXAMPLE_CONFIG: &str = include_str!("../../config/example.config.json");
|
||||
const DEFAULT_CONFIG: &str = include_str!("../../config/app.config.json");
|
||||
const EXAMPLE_CONFIG: &str = include_str!("../../config/example.app.config.json");
|
||||
|
||||
fn parse_example_value() -> serde_json::Value {
|
||||
let result = serde_json::from_str::<serde_json::Value>(EXAMPLE_CONFIG);
|
||||
fn parse_default_value() -> serde_json::Value {
|
||||
let result = serde_json::from_str::<serde_json::Value>(DEFAULT_CONFIG);
|
||||
match result {
|
||||
std::result::Result::Ok(value) => return value,
|
||||
std::result::Result::Err(error) => panic!("example config must be valid JSON: {error}"),
|
||||
std::result::Result::Err(error) => panic!("default app config must be valid JSON: {error}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,57 +1064,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn tracing_crate_names() -> std::vec::Vec<std::string::String> {
|
||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = match manifest_dir.parent() {
|
||||
std::option::Option::Some(path) => path,
|
||||
std::option::Option::None => panic!("workspace root must exist"),
|
||||
};
|
||||
let read_result = std::fs::read_dir(workspace_root);
|
||||
let entries = match read_result {
|
||||
std::result::Result::Ok(entries) => entries,
|
||||
std::result::Result::Err(error) => panic!("workspace must be readable: {error}"),
|
||||
};
|
||||
let mut names = std::vec::Vec::<std::string::String>::new();
|
||||
for entry_result in entries {
|
||||
let entry = match entry_result {
|
||||
std::result::Result::Ok(entry) => entry,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("workspace entry must be readable: {error}")
|
||||
},
|
||||
};
|
||||
let cargo_path = entry.path().join("Cargo.toml");
|
||||
if !cargo_path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let cargo_result = std::fs::read_to_string(&cargo_path);
|
||||
let cargo_toml = match cargo_result {
|
||||
std::result::Result::Ok(content) => content,
|
||||
std::result::Result::Err(error) => panic!("Cargo.toml must be readable: {error}"),
|
||||
};
|
||||
if !cargo_toml.lines().any(|line| return line.trim() == "tracing.workspace = true") {
|
||||
continue;
|
||||
}
|
||||
let mut in_package = false;
|
||||
for line in cargo_toml.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed == "[package]" {
|
||||
in_package = true;
|
||||
continue;
|
||||
}
|
||||
if in_package && trimmed.starts_with('[') {
|
||||
break;
|
||||
}
|
||||
if in_package && trimmed.starts_with("name = ") {
|
||||
names.push(trimmed.trim_start_matches("name = ").trim_matches('"').to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
names.sort();
|
||||
return names;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_text_is_valid_json() {
|
||||
let result = super::config_json_schema_value();
|
||||
@@ -1301,110 +1071,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_validates_against_schema() {
|
||||
let result = super::validate_config_json_schema(EXAMPLE_CONFIG);
|
||||
assert!(result.is_ok());
|
||||
fn default_and_example_app_configs_validate_against_schema() {
|
||||
assert!(super::validate_config_json_schema(DEFAULT_CONFIG).is_ok());
|
||||
assert!(super::validate_config_json_schema(EXAMPLE_CONFIG).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_routes_global_and_operational_crate_files() {
|
||||
let config_result = super::parse_config_json(EXAMPLE_CONFIG);
|
||||
let config = match config_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config must parse: {error}"),
|
||||
};
|
||||
let operational_crates = tracing_crate_names();
|
||||
assert!(!operational_crates.is_empty());
|
||||
for profile in config.profiles {
|
||||
let directory = match profile.name.as_str() {
|
||||
"local_devnet" => "devnet",
|
||||
"mainnet_research" => "mainnet_research",
|
||||
"mainnet" => "mainnet",
|
||||
value => panic!("unexpected example profile: {value}"),
|
||||
};
|
||||
for (suffix, level, format) in [
|
||||
("debug.log", "debug", "human"),
|
||||
("info.log", "info", "human"),
|
||||
("error.jsonl", "error", "json"),
|
||||
] {
|
||||
let expected_path = format!("logs/{directory}/{suffix}");
|
||||
assert!(profile.logging.targets.iter().any(|target| {
|
||||
return target.enabled
|
||||
&& target.path == expected_path
|
||||
&& target.level == level
|
||||
&& target.format == format
|
||||
&& target.targets == std::vec!["*".to_string()];
|
||||
}));
|
||||
}
|
||||
assert!(profile.logging.targets.iter().any(|target| {
|
||||
return target.enabled
|
||||
&& target.path == format!("logs/{directory}/app.log")
|
||||
&& target.level == "debug"
|
||||
&& target.format == "human"
|
||||
&& target.targets == std::vec!["kb-app-demo-desktop".to_string()];
|
||||
}));
|
||||
for crate_name in &operational_crates {
|
||||
for (suffix, level, format) in [
|
||||
("debug.log", "debug", "human"),
|
||||
("info.log", "info", "human"),
|
||||
("error.jsonl", "error", "json"),
|
||||
] {
|
||||
let route_directory = if crate_name == "ks-pipeline-demo-scenarios" {
|
||||
"ks-pipeline"
|
||||
} else {
|
||||
crate_name.as_str()
|
||||
};
|
||||
let expected_path = format!("logs/{directory}/{route_directory}/{suffix}");
|
||||
let route_exists = profile.logging.targets.iter().any(|target| {
|
||||
return target.enabled
|
||||
&& target.path == expected_path
|
||||
&& target.level == level
|
||||
&& target.format == format
|
||||
&& target.targets.iter().any(|value| return value == crate_name);
|
||||
});
|
||||
assert!(
|
||||
route_exists,
|
||||
"profile {} is missing the canonical {} route for {} at {}",
|
||||
profile.name, level, crate_name, expected_path
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_uses_canonical_wallet_tracing_routes_without_embedded_secrets() {
|
||||
let config_result = super::parse_config_json(EXAMPLE_CONFIG);
|
||||
let config = match config_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config must parse: {error}"),
|
||||
};
|
||||
assert!(!EXAMPLE_CONFIG.contains("api-key=95e73621"));
|
||||
assert!(!EXAMPLE_CONFIG.contains("\"ks_wallet\""));
|
||||
for profile in config.profiles {
|
||||
assert!(profile.logging.targets.iter().any(|target| {
|
||||
return target.enabled && target.targets == std::vec!["ks-wallet".to_string()];
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_uses_canonical_pipeline_tracing_routes() {
|
||||
let source = include_str!("../../config/example.config.json");
|
||||
assert!(source.contains("\"ks-pipeline\""));
|
||||
assert!(source.contains("logs/devnet/ks-pipeline/debug.log"));
|
||||
assert!(source.contains("logs/mainnet_research/ks-pipeline/info.log"));
|
||||
assert!(source.contains("logs/mainnet/ks-pipeline/error.jsonl"));
|
||||
assert!(!source.contains("ks_pipeline"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_parses_and_resolves_active_profile() {
|
||||
let config_result = super::parse_config_json(EXAMPLE_CONFIG);
|
||||
fn default_app_config_parses_and_resolves_active_profile() {
|
||||
let config_result = super::parse_config_json(DEFAULT_CONFIG);
|
||||
assert!(config_result.is_ok());
|
||||
let config = match config_result {
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Err(error) => panic!("example config must parse: {error}"),
|
||||
std::result::Result::Err(error) => panic!("default app config must parse: {error}"),
|
||||
};
|
||||
let active_result = super::active_profile(&config);
|
||||
assert!(active_result.is_ok());
|
||||
@@ -1417,18 +1095,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_serializes_and_roundtrips() {
|
||||
let config_result = super::parse_config_json(EXAMPLE_CONFIG);
|
||||
fn default_app_config_serializes_and_roundtrips() {
|
||||
let config_result = super::parse_config_json(DEFAULT_CONFIG);
|
||||
assert!(config_result.is_ok());
|
||||
let config = match config_result {
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Err(error) => panic!("example config must parse: {error}"),
|
||||
std::result::Result::Err(error) => panic!("default app config must parse: {error}"),
|
||||
};
|
||||
let serialized_result = super::serialize_config_json_pretty(&config);
|
||||
assert!(serialized_result.is_ok());
|
||||
let serialized = match serialized_result {
|
||||
std::result::Result::Ok(serialized) => serialized,
|
||||
std::result::Result::Err(error) => panic!("example config must serialize: {error}"),
|
||||
std::result::Result::Err(error) => panic!("default app config must serialize: {error}"),
|
||||
};
|
||||
let reparsed_result = super::parse_config_json(&serialized);
|
||||
assert!(reparsed_result.is_ok());
|
||||
@@ -1441,7 +1119,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_missing_active_profile() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["active_profile"] = serde_json::Value::String("missing_profile".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::parse_config_json(&raw_json);
|
||||
@@ -1449,8 +1127,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_config_separates_runtime_postgres_profiles() {
|
||||
let value = parse_example_value();
|
||||
fn default_app_config_separates_runtime_postgres_profiles() {
|
||||
let value = parse_default_value();
|
||||
assert_eq!(
|
||||
value["profiles"][0]["database"]["postgres"]["url"],
|
||||
serde_json::Value::String("${KS_SECRET_POSTGRES_DEVNET_URL}".to_string())
|
||||
@@ -1467,7 +1145,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn schema_rejects_profile_enabled_field() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["enabled"] = serde_json::Value::Bool(true);
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::validate_config_json_schema(&raw_json);
|
||||
@@ -1476,7 +1154,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_duplicate_profile_names() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][1]["name"] = serde_json::Value::String("local_devnet".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::parse_config_json(&raw_json);
|
||||
@@ -1485,7 +1163,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn schema_rejects_unknown_endpoint_secret_field() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["secret_env_field"] =
|
||||
serde_json::Value::String("KS_SECRET_HELIUS_API_KEY".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1495,7 +1173,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn schema_rejects_runtime_idls_directory_field() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["data"]["idls_directory"] =
|
||||
serde_json::Value::String("idls".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1505,7 +1183,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn schema_rejects_unsupported_transport_surface() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["advanced_streams"] = serde_json::json!([]);
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::validate_config_json_schema(&raw_json);
|
||||
@@ -1514,7 +1192,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_invalid_http_endpoint_url() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["url"] =
|
||||
serde_json::Value::String("wss://api.devnet.solana.com".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1524,7 +1202,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_invalid_ws_endpoint_url() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["ws_endpoints"][0]["url"] =
|
||||
serde_json::Value::String("https://api.devnet.solana.com".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1532,19 +1210,9 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_file_logging_target_without_path() {
|
||||
let mut value = parse_example_value();
|
||||
value["profiles"][0]["logging"]["targets"][1]["path"] =
|
||||
serde_json::Value::String("".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::parse_config_json(&raw_json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_invalid_temporary_wallet_alias() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["wallet"]["temporary_wallet_alias"] =
|
||||
serde_json::Value::String("../wallet".to_string());
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1554,7 +1222,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_persistent_disabled_temporary_wallet() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["wallet"]["temporary_wallet_enabled"] = serde_json::Value::Bool(false);
|
||||
let raw_json = value_to_json(&value);
|
||||
let result = super::parse_config_json(&raw_json);
|
||||
@@ -1563,7 +1231,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_enabled_cluster_without_spend_limit() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["execution"]["devnet_max_spend_lamports"] =
|
||||
serde_json::Value::Number(serde_json::Number::from(0));
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1579,7 +1247,7 @@ mod tests {
|
||||
"confirmation_poll_interval_ms",
|
||||
"confirmation_max_attempts",
|
||||
] {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["execution"][field] =
|
||||
serde_json::Value::Number(serde_json::Number::from(0));
|
||||
let raw_json = value_to_json(&value);
|
||||
@@ -1590,7 +1258,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_http_role_with_subscriptions() {
|
||||
let mut value = parse_example_value();
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["roles"][0]["max_subscriptions"] =
|
||||
serde_json::Value::Number(serde_json::Number::from(1));
|
||||
let raw_json = value_to_json(&value);
|
||||
|
||||
Reference in New Issue
Block a user