v0.5.3-pre.002

This commit is contained in:
2026-08-11 22:22:40 +02:00
parent 01d78b5845
commit 8448ad1079
134 changed files with 4518 additions and 3595 deletions

View File

@@ -1,8 +1,19 @@
<!-- file: ks-config/CHANGELOG.md -->
<!-- version: 24 -->
<!-- version: 26 -->
# CHANGELOG — ks-config
## `0.5.3-pre.002-delta-fix-001`
- aligne tous les contrats/types publics de `ks-config` sur la règle workspace imposant `crate::Item` depuis leur crate propriétaire, y compris dans leur module de déclaration et dans les tests ;
- aligne les appels aux fonctions publiques réexportées sur le crate-root et ajoute le canari `KH_CFG015` pour empêcher le retour d'une référence locale non qualifiée à un type public de configuration.
## `0.5.3-pre.002`
- remplace les contrats runtime `PostgresConfig`/`SqliteConfig` par `DatabaseConfig { enabled, backend, backend_options }` afin que `ks-config` ne connaisse plus les paramètres d'un moteur non sélectionné ;
- limite la validation store à la structure backend-agnostique et laisse `ks-store` interpréter/valider les options concrètes, sans modifier les conventions `KS_SECRET_*` ni la résolution des placeholders ;
- aligne `store.config.json`, son exemple, le schéma spécialisé, le schéma runtime et les fixtures de configuration sur ce contrat opaque.
## `0.5.2-pre.006-delta-fix-007`
- corrige le test de séparation des répertoires wallet afin quil résolve dabord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/README.md -->
<!-- version: 13 -->
<!-- version: 14 -->
# ks-config
@@ -59,6 +59,8 @@ Les valeurs globales telles que `logs_directory` et `wallets_directory` ne sont
Depuis `0.5.1`, `ks-config` ne dépend plus de TS-RS et ne produit plus de bindings TypeScript. Ses contrats source/runtime peuvent contenir des secrets résolus : ils ne dérivent donc ni `serde::Serialize` ni `Debug`. Les applications propriétaires construisent leurs DTO publics et diagnostics bornés champ par champ.
Depuis `0.5.3-pre.002`, `DatabaseConfig` ne modélise plus PostgreSQL/SQLite : il conserve uniquement `enabled`, le code `backend` et un objet `backend_options` opaque. `ks-config` charge, compose et résout les placeholders ; la validation des paramètres du moteur et la création de la connexion appartiennent exclusivement à `ks-store`.
La section `application` d'une composition est volontairement opaque pour `ks-config`. Chaque binaire propriétaire définit son schéma et le valide avec `validate_json_value_against_schema`; le desktop utilise `config/schemas/kb-app-demo-desktop.application.config.schema.json`.
## Documents

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/USAGE.md -->
<!-- version: 12 -->
<!-- version: 13 -->
# Utilisation de ks-config
@@ -88,7 +88,7 @@ Les endpoints WebSocket source sélectionnent une classe `ws_endpoint_defaults`;
## Store
`store.config.json` possède les paramètres de backend et les profils PostgreSQL/SQLite. Les helpers principaux sont `read_store_json_file_with_environment`, `default_store_profile` et `store_profile`.
`store.config.json` sélectionne un backend par profil et transporte ses paramètres sous `backend_options`. Ce bloc reste opaque pour `ks-config` : seuls le code backend non vide et la forme objet sont validés ici. Les helpers principaux sont `read_store_json_file_with_environment`, `default_store_profile` et `store_profile`; `ks-store` interprète ensuite les options du backend sélectionné et crée la connexion.
## Wallet

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/composition.rs
// version: 3
// version: 5
//! Binary composition configuration and resolution into the shared runtime profile contract.
@@ -18,9 +18,9 @@ pub struct CompositionConfigDocument {
/// Active binary composition profile name.
pub active_profile: std::string::String,
/// Shared configuration documents referenced by this composition.
pub sources: CompositionConfigSources,
pub sources: crate::CompositionConfigSources,
/// Named binary composition profiles.
pub profiles: std::vec::Vec<CompositionProfileConfig>,
pub profiles: std::vec::Vec<crate::CompositionProfileConfig>,
}
/// Paths of shared configuration documents consumed by one binary composition.
@@ -65,7 +65,7 @@ pub struct CompositionProfileConfig {
#[derive(Clone, Eq, PartialEq)]
pub struct ComposedAppConfig {
/// Parsed binary composition document.
pub composition: CompositionConfigDocument,
pub composition: crate::CompositionConfigDocument,
/// Resolved runtime application configuration.
pub app_config: crate::AppConfig,
/// Resolved logging source path.
@@ -101,7 +101,7 @@ pub fn composition_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw binary composition JSON against its embedded schema.
pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match composition_json_schema_value() {
let schema = match crate::composition_json_schema_value() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -133,12 +133,12 @@ pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> {
}
/// Parses and validates one binary composition document.
pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConfigDocument> {
match validate_composition_json_schema(raw_json) {
pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<crate::CompositionConfigDocument> {
match crate::validate_composition_json_schema(raw_json) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let document = match serde_json::from_str::<CompositionConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::CompositionConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -147,7 +147,7 @@ pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConf
));
},
};
return match validate_composition_document(&document) {
return match crate::validate_composition_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -156,7 +156,7 @@ pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConf
/// Reads and parses one binary composition document from a filesystem path.
pub fn read_composition_json_file(
path: &std::path::Path,
) -> ks_core::Result<CompositionConfigDocument> {
) -> ks_core::Result<crate::CompositionConfigDocument> {
let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -166,14 +166,14 @@ pub fn read_composition_json_file(
));
},
};
return parse_composition_json(&raw_json);
return crate::parse_composition_json(&raw_json);
}
/// Loads workspace environment values, resolves placeholders and parses one binary composition document.
pub fn read_composition_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<CompositionConfigDocument> {
) -> ks_core::Result<crate::CompositionConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -187,14 +187,14 @@ pub fn read_composition_json_file_with_environment(
));
},
};
return parse_composition_json(&crate::resolve_environment_placeholders(&raw_json));
return crate::parse_composition_json(&crate::resolve_environment_placeholders(&raw_json));
}
/// Returns one named binary composition profile.
pub fn composition_profile<'a>(
document: &'a CompositionConfigDocument,
document: &'a crate::CompositionConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a CompositionProfileConfig> {
) -> ks_core::Result<&'a crate::CompositionProfileConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -208,9 +208,9 @@ pub fn composition_profile<'a>(
/// Returns the active binary composition profile.
pub fn active_composition_profile(
document: &CompositionConfigDocument,
) -> ks_core::Result<&CompositionProfileConfig> {
return composition_profile(document, document.active_profile.as_str());
document: &crate::CompositionConfigDocument,
) -> ks_core::Result<&crate::CompositionProfileConfig> {
return crate::composition_profile(document, document.active_profile.as_str());
}
/// Resolves a source path relative to the workspace root unless already absolute.
@@ -238,7 +238,7 @@ pub fn resolve_profile_selection<'a>(
/// Resolves all shared source selections into the runtime application contract.
pub fn compose_app_config(
document: &CompositionConfigDocument,
document: &crate::CompositionConfigDocument,
transport: &crate::TransportConfigDocument,
listeners: &crate::ListenersConfigDocument,
store: &crate::StoreConfigDocument,
@@ -247,23 +247,23 @@ pub fn compose_app_config(
) -> ks_core::Result<crate::AppConfig> {
let mut profiles = std::vec::Vec::<crate::ProfileConfig>::new();
for profile in &document.profiles {
let transport_name = resolve_profile_selection(
let transport_name = crate::resolve_profile_selection(
profile.transport_profile.as_deref(),
transport.default_profile.as_str(),
);
let listeners_name = resolve_profile_selection(
let listeners_name = crate::resolve_profile_selection(
profile.listeners_profile.as_deref(),
listeners.default_profile.as_str(),
);
let store_name = resolve_profile_selection(
let store_name = crate::resolve_profile_selection(
profile.store_profile.as_deref(),
store.default_profile.as_str(),
);
let wallet_name = resolve_profile_selection(
let wallet_name = crate::resolve_profile_selection(
profile.wallet_profile.as_deref(),
wallet.default_profile.as_str(),
);
let execution_name = resolve_profile_selection(
let execution_name = crate::resolve_profile_selection(
profile.execution_profile.as_deref(),
execution.default_profile.as_str(),
);
@@ -313,24 +313,34 @@ pub fn compose_app_config(
pub fn read_composed_app_config_with_environment(
composition_path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<ComposedAppConfig> {
let composition =
match read_composition_json_file_with_environment(composition_path, workspace_root) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let logging_path =
resolve_composition_source_path(workspace_root, composition.sources.logging.as_str());
let transport_path =
resolve_composition_source_path(workspace_root, composition.sources.transport.as_str());
let listeners_path =
resolve_composition_source_path(workspace_root, composition.sources.listeners.as_str());
) -> ks_core::Result<crate::ComposedAppConfig> {
let composition = match crate::read_composition_json_file_with_environment(
composition_path,
workspace_root,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let logging_path = crate::resolve_composition_source_path(
workspace_root,
composition.sources.logging.as_str(),
);
let transport_path = crate::resolve_composition_source_path(
workspace_root,
composition.sources.transport.as_str(),
);
let listeners_path = crate::resolve_composition_source_path(
workspace_root,
composition.sources.listeners.as_str(),
);
let store_path =
resolve_composition_source_path(workspace_root, composition.sources.store.as_str());
crate::resolve_composition_source_path(workspace_root, composition.sources.store.as_str());
let wallet_path =
resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str());
let execution_path =
resolve_composition_source_path(workspace_root, composition.sources.execution.as_str());
crate::resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str());
let execution_path = crate::resolve_composition_source_path(
workspace_root,
composition.sources.execution.as_str(),
);
let transport =
match crate::read_transport_json_file_with_environment(&transport_path, workspace_root) {
std::result::Result::Ok(value) => value,
@@ -359,13 +369,18 @@ pub fn read_composed_app_config_with_environment(
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let app_config =
match compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution)
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ComposedAppConfig {
let app_config = match crate::compose_app_config(
&composition,
&transport,
&listeners,
&store,
&wallet,
&execution,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ComposedAppConfig {
composition,
app_config,
logging_path,
@@ -421,9 +436,9 @@ pub fn read_default_shared_app_config_with_environment(
} else {
"shared_default".to_string()
};
let composition = CompositionConfigDocument {
let composition = crate::CompositionConfigDocument {
active_profile: profile_name.clone(),
sources: CompositionConfigSources {
sources: crate::CompositionConfigSources {
logging: DEFAULT_LOGGING_CONFIG_PATH.to_string(),
transport: DEFAULT_TRANSPORT_CONFIG_PATH.to_string(),
listeners: DEFAULT_LISTENERS_CONFIG_PATH.to_string(),
@@ -431,7 +446,7 @@ pub fn read_default_shared_app_config_with_environment(
wallet: DEFAULT_WALLET_CONFIG_PATH.to_string(),
execution: DEFAULT_EXECUTION_CONFIG_PATH.to_string(),
},
profiles: vec![CompositionProfileConfig {
profiles: vec![crate::CompositionProfileConfig {
name: profile_name.clone(),
application: std::option::Option::None,
logging_profile: std::option::Option::None,
@@ -442,11 +457,20 @@ pub fn read_default_shared_app_config_with_environment(
execution_profile: std::option::Option::None,
}],
};
return compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution);
return crate::compose_app_config(
&composition,
&transport,
&listeners,
&store,
&wallet,
&execution,
);
}
/// Validates a typed binary composition document before referenced documents are loaded.
pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks_core::Result<()> {
pub fn validate_composition_document(
document: &crate::CompositionConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.active_profile, "composition.active_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -498,7 +522,7 @@ pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks
));
}
}
return match active_composition_profile(document) {
return match crate::active_composition_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -507,7 +531,7 @@ pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks
fn validate_logging_references_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
composition: &CompositionConfigDocument,
composition: &crate::CompositionConfigDocument,
) -> ks_core::Result<()> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
@@ -566,7 +590,7 @@ fn validate_logging_references_with_environment(
}
for profile in &composition.profiles {
let selected =
resolve_profile_selection(profile.logging_profile.as_deref(), default_profile);
crate::resolve_profile_selection(profile.logging_profile.as_deref(), default_profile);
if !names.contains(selected) {
return std::result::Result::Err(ks_core::Error::new(
"composition_logging_profile_not_found",
@@ -586,17 +610,17 @@ mod tests {
#[test]
fn default_and_example_compositions_validate() {
assert!(super::parse_composition_json(DEFAULT_COMPOSITION).is_ok());
assert!(super::parse_composition_json(EXAMPLE_COMPOSITION).is_ok());
assert!(crate::parse_composition_json(DEFAULT_COMPOSITION).is_ok());
assert!(crate::parse_composition_json(EXAMPLE_COMPOSITION).is_ok());
}
#[test]
fn local_devnet_composition_uses_shared_defaults() {
let composition = match super::parse_composition_json(DEFAULT_COMPOSITION) {
let composition = match crate::parse_composition_json(DEFAULT_COMPOSITION) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default composition must parse: {error}"),
};
let profile = match super::composition_profile(&composition, "local_devnet") {
let profile = match crate::composition_profile(&composition, "local_devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("local Devnet composition must resolve: {error}")

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/environment.rs
// version: 6
// version: 8
//! Environment-file loading and configuration placeholder resolution.
@@ -16,7 +16,7 @@ pub struct EnvironmentLoadReport {
/// fallback text declared with `${NAME:-fallback}` placeholders.
pub fn load_workspace_environment(
workspace_root: &std::path::Path,
) -> ks_core::Result<EnvironmentLoadReport> {
) -> ks_core::Result<crate::EnvironmentLoadReport> {
let explicit_path = std::env::var("KS_ENV_FILE").ok();
let selected_path = match explicit_path {
std::option::Option::Some(path) if !path.trim().is_empty() => {
@@ -26,10 +26,10 @@ pub fn load_workspace_environment(
_ => workspace_root.join(".env"),
};
if !selected_path.exists() {
return std::result::Result::Ok(EnvironmentLoadReport { loaded_path: None });
return std::result::Result::Ok(crate::EnvironmentLoadReport { loaded_path: None });
}
return match dotenvy::from_path(&selected_path) {
std::result::Result::Ok(()) => std::result::Result::Ok(EnvironmentLoadReport {
std::result::Result::Ok(()) => std::result::Result::Ok(crate::EnvironmentLoadReport {
loaded_path: std::option::Option::Some(selected_path),
}),
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
@@ -82,14 +82,14 @@ mod tests {
#[test]
fn fallback_is_used_when_variable_is_absent() {
let resolved =
super::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}");
crate::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}");
assert_eq!(resolved, "fallback");
}
#[test]
fn unresolved_required_placeholder_is_preserved() {
let resolved =
super::resolve_environment_placeholders("prefix-${KS_CONFIG_TEST_MISSING}-suffix");
crate::resolve_environment_placeholders("prefix-${KS_CONFIG_TEST_MISSING}-suffix");
assert_eq!(resolved, "prefix-${KS_CONFIG_TEST_MISSING}-suffix");
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/execution.rs
// version: 3
// version: 5
//! Independent execution policy configuration document loading and validation.
@@ -12,7 +12,7 @@ pub struct ExecutionConfigDocument {
/// Default execution profile used when a composition does not override it.
pub default_profile: std::string::String,
/// Named execution policy profiles.
pub profiles: std::vec::Vec<ExecutionProfileConfig>,
pub profiles: std::vec::Vec<crate::ExecutionProfileConfig>,
}
/// Named execution policy profile.
@@ -31,7 +31,7 @@ pub fn execution_json_schema_text() -> &'static str {
}
/// Parses and validates one execution configuration document.
pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDocument> {
pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<crate::ExecutionConfigDocument> {
let schema = match serde_json::from_str::<serde_json::Value>(EXECUTION_JSON_SCHEMA) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -65,7 +65,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
"execution configuration does not satisfy its schema",
));
}
let document = match serde_json::from_str::<ExecutionConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::ExecutionConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -74,7 +74,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
));
},
};
return match validate_execution_document(&document) {
return match crate::validate_execution_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -84,7 +84,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
pub fn read_execution_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<ExecutionConfigDocument> {
) -> ks_core::Result<crate::ExecutionConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -98,14 +98,14 @@ pub fn read_execution_json_file_with_environment(
));
},
};
return parse_execution_json(&crate::resolve_environment_placeholders(&raw_json));
return crate::parse_execution_json(&crate::resolve_environment_placeholders(&raw_json));
}
/// Returns one named execution profile.
pub fn execution_profile<'a>(
document: &'a ExecutionConfigDocument,
document: &'a crate::ExecutionConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a ExecutionProfileConfig> {
) -> ks_core::Result<&'a crate::ExecutionProfileConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -119,13 +119,15 @@ pub fn execution_profile<'a>(
/// Returns the default execution profile.
pub fn default_execution_profile(
document: &ExecutionConfigDocument,
) -> ks_core::Result<&ExecutionProfileConfig> {
return execution_profile(document, document.default_profile.as_str());
document: &crate::ExecutionConfigDocument,
) -> ks_core::Result<&crate::ExecutionProfileConfig> {
return crate::execution_profile(document, document.default_profile.as_str());
}
/// Validates one typed execution configuration document.
pub fn validate_execution_document(document: &ExecutionConfigDocument) -> ks_core::Result<()> {
pub fn validate_execution_document(
document: &crate::ExecutionConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "execution.default_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -153,7 +155,7 @@ pub fn validate_execution_document(document: &ExecutionConfigDocument) -> ks_cor
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match default_execution_profile(document) {
return match crate::default_execution_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -167,7 +169,7 @@ mod tests {
#[test]
fn default_and_example_execution_configs_validate() {
assert!(super::parse_execution_json(DEFAULT_EXECUTION).is_ok());
assert!(super::parse_execution_json(EXAMPLE_EXECUTION).is_ok());
assert!(crate::parse_execution_json(DEFAULT_EXECUTION).is_ok());
assert!(crate::parse_execution_json(EXAMPLE_EXECUTION).is_ok());
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/lib.rs
// version: 10
// version: 11
//! Khadhroony Solana shared configuration contracts and composition helpers.
#![warn(missing_docs)]
@@ -125,16 +125,12 @@ pub use self::settings::HttpEndpointConfig;
pub use self::settings::ListenerConfig;
/// Exposes the log listener configuration type.
pub use self::settings::LogListenerConfig;
/// Exposes the PostgreSQL configuration type.
pub use self::settings::PostgresConfig;
/// Exposes the profile configuration type.
pub use self::settings::ProfileConfig;
/// Exposes the program listener configuration type.
pub use self::settings::ProgramListenerConfig;
/// Exposes the Solana configuration type.
pub use self::settings::SolanaConfig;
/// Exposes the SQLite configuration type.
pub use self::settings::SqliteConfig;
/// Exposes the wallet configuration type.
pub use self::settings::WalletConfig;
/// Exposes the WebSocket endpoint configuration type.

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/listeners.rs
// version: 3
// version: 5
//! Independent Solana listener configuration document loading and validation.
@@ -12,7 +12,7 @@ pub struct ListenersConfigDocument {
/// Default listener profile used by consumers that do not provide an explicit selection.
pub default_profile: std::string::String,
/// Named listener profiles available in this document.
pub profiles: std::vec::Vec<ListenersProfileConfig>,
pub profiles: std::vec::Vec<crate::ListenersProfileConfig>,
}
/// Named listener profile.
@@ -51,7 +51,7 @@ pub fn listeners_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw listener JSON against the embedded listener schema.
pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match listeners_json_schema_value() {
let schema = match crate::listeners_json_schema_value() {
std::result::Result::Ok(schema) => schema,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -83,12 +83,12 @@ pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> {
}
/// Parses and validates one independent listener configuration document.
pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDocument> {
match validate_listeners_json_schema(raw_json) {
pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<crate::ListenersConfigDocument> {
match crate::validate_listeners_json_schema(raw_json) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let document = match serde_json::from_str::<ListenersConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::ListenersConfigDocument>(raw_json) {
std::result::Result::Ok(document) => document,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -97,7 +97,7 @@ pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDo
));
},
};
return match validate_listeners_document(&document) {
return match crate::validate_listeners_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -106,7 +106,7 @@ pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDo
/// Reads and parses one listener configuration document from a filesystem path.
pub fn read_listeners_json_file(
path: &std::path::Path,
) -> ks_core::Result<ListenersConfigDocument> {
) -> ks_core::Result<crate::ListenersConfigDocument> {
let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(content) => content,
std::result::Result::Err(error) => {
@@ -116,14 +116,14 @@ pub fn read_listeners_json_file(
));
},
};
return parse_listeners_json(&raw_json);
return crate::parse_listeners_json(&raw_json);
}
/// Loads workspace environment values, resolves placeholders and parses listener configuration.
pub fn read_listeners_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<ListenersConfigDocument> {
) -> ks_core::Result<crate::ListenersConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -138,14 +138,14 @@ pub fn read_listeners_json_file_with_environment(
},
};
let resolved = crate::resolve_environment_placeholders(&raw_json);
return parse_listeners_json(&resolved);
return crate::parse_listeners_json(&resolved);
}
/// Returns one named listener profile.
pub fn listeners_profile<'a>(
document: &'a ListenersConfigDocument,
document: &'a crate::ListenersConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a ListenersProfileConfig> {
) -> ks_core::Result<&'a crate::ListenersProfileConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -159,13 +159,13 @@ pub fn listeners_profile<'a>(
/// Returns the default listener profile declared by the document.
pub fn default_listeners_profile(
document: &ListenersConfigDocument,
) -> ks_core::Result<&ListenersProfileConfig> {
return listeners_profile(document, document.default_profile.as_str());
document: &crate::ListenersConfigDocument,
) -> ks_core::Result<&crate::ListenersProfileConfig> {
return crate::listeners_profile(document, document.default_profile.as_str());
}
/// Converts one named listener profile into the resolved runtime listener contract.
pub fn resolved_listener_config(profile: &ListenersProfileConfig) -> crate::ListenerConfig {
pub fn resolved_listener_config(profile: &crate::ListenersProfileConfig) -> crate::ListenerConfig {
return crate::ListenerConfig {
enabled: profile.enabled,
default_commitment: profile.default_commitment.clone(),
@@ -176,7 +176,9 @@ pub fn resolved_listener_config(profile: &ListenersProfileConfig) -> crate::List
}
/// Validates a typed listener configuration document.
pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_core::Result<()> {
pub fn validate_listeners_document(
document: &crate::ListenersConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "default_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -199,13 +201,13 @@ pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_cor
profile.name.clone(),
));
}
let resolved = resolved_listener_config(profile);
let resolved = crate::resolved_listener_config(profile);
match crate::validate_listeners(&resolved) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match default_listeners_profile(document) {
return match crate::default_listeners_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -219,7 +221,7 @@ mod tests {
#[test]
fn default_and_example_listener_configs_validate() {
assert!(super::parse_listeners_json(DEFAULT_LISTENERS).is_ok());
assert!(super::parse_listeners_json(EXAMPLE_LISTENERS).is_ok());
assert!(crate::parse_listeners_json(DEFAULT_LISTENERS).is_ok());
assert!(crate::parse_listeners_json(EXAMPLE_LISTENERS).is_ok());
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/schema.rs
// version: 1
// version: 2
//! Generic JSON Schema validation helpers for binary-owned configuration fragments.
@@ -42,7 +42,7 @@ mod tests {
fn validation_error_never_echoes_instance_secret() {
let schema = r#"{"type":"object","additionalProperties":false,"required":["value"],"properties":{"value":{"type":"integer"}}}"#;
let instance = serde_json::json!({"value": "SECRET-CANARY"});
let result = super::validate_json_value_against_schema(
let result = crate::validate_json_value_against_schema(
schema,
&instance,
"test_fragment_validation_failed",

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/sensitivity.rs
// version: 1
// version: 3
//! Application environment-variable sensitivity classification.
@@ -20,7 +20,7 @@ pub enum EnvironmentValueSensitivity {
/// one `KS_SECRET_*` placeholder is classified as secret even when the surrounding URL is public.
pub fn classify_environment_template(
raw: &str,
) -> std::option::Option<EnvironmentValueSensitivity> {
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
let bytes = raw.as_bytes();
let mut sensitivity = std::option::Option::None;
let mut index = 0_usize;
@@ -36,7 +36,8 @@ pub fn classify_environment_template(
std::option::Option::Some((name, _)) => name,
std::option::Option::None => expression,
};
sensitivity = merge_sensitivity(sensitivity, classify_environment_variable(name));
sensitivity =
merge_sensitivity(sensitivity, crate::classify_environment_variable(name));
index = end + 1;
continue;
}
@@ -49,15 +50,15 @@ pub fn classify_environment_template(
/// Classifies a Khadhroony-owned environment variable by its namespace.
pub fn classify_environment_variable(
name: &str,
) -> std::option::Option<EnvironmentValueSensitivity> {
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
if name.starts_with("KS_SECRET_") || name.starts_with("KB_SECRET_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Secret);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret);
}
if name.starts_with("KS_PUBLIC_") || name.starts_with("KB_PUBLIC_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Public);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Public);
}
if name.starts_with("KS_") || name.starts_with("KB_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Internal);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal);
}
return std::option::Option::None;
}
@@ -67,14 +68,14 @@ pub fn diagnostic_environment_value(
name: &str,
value: &str,
) -> std::option::Option<std::string::String> {
return match classify_environment_variable(name) {
std::option::Option::Some(EnvironmentValueSensitivity::Secret) => {
return match crate::classify_environment_variable(name) {
std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret) => {
std::option::Option::Some("<redacted>".to_string())
},
std::option::Option::Some(EnvironmentValueSensitivity::Internal) => {
std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal) => {
std::option::Option::Some("<configured>".to_string())
},
std::option::Option::Some(EnvironmentValueSensitivity::Public) => {
std::option::Option::Some(crate::EnvironmentValueSensitivity::Public) => {
std::option::Option::Some(value.to_string())
},
std::option::Option::None => std::option::Option::None,
@@ -82,23 +83,23 @@ pub fn diagnostic_environment_value(
}
fn merge_sensitivity(
left: std::option::Option<EnvironmentValueSensitivity>,
right: std::option::Option<EnvironmentValueSensitivity>,
) -> std::option::Option<EnvironmentValueSensitivity> {
if left == std::option::Option::Some(EnvironmentValueSensitivity::Secret)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Secret)
left: std::option::Option<crate::EnvironmentValueSensitivity>,
right: std::option::Option<crate::EnvironmentValueSensitivity>,
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret)
|| right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret)
{
return std::option::Option::Some(EnvironmentValueSensitivity::Secret);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret);
}
if left == std::option::Option::Some(EnvironmentValueSensitivity::Internal)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Internal)
if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal)
|| right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal)
{
return std::option::Option::Some(EnvironmentValueSensitivity::Internal);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal);
}
if left == std::option::Option::Some(EnvironmentValueSensitivity::Public)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Public)
if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Public)
|| right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Public)
{
return std::option::Option::Some(EnvironmentValueSensitivity::Public);
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Public);
}
return std::option::Option::None;
}
@@ -108,19 +109,19 @@ mod tests {
#[test]
fn composed_values_inherit_the_strongest_placeholder_sensitivity() {
assert_eq!(
super::classify_environment_template(
crate::classify_environment_template(
"https://rpc.invalid/?api-key=${KS_SECRET_HELIUS_API_KEY}&mint=${KS_PUBLIC_DEVNET_TOKEN_2022_MINT}"
),
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
);
assert_eq!(
super::classify_environment_template(
crate::classify_environment_template(
"${KS_LOGS_DIRECTORY:-logs}/${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"
),
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
);
assert_eq!(
super::classify_environment_template("${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"),
crate::classify_environment_template("${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Public)
);
}
@@ -128,15 +129,15 @@ mod tests {
#[test]
fn generic_diagnostic_never_reveals_secret_or_internal_values() {
assert_eq!(
super::diagnostic_environment_value("KS_SECRET_HELIUS_API_KEY", "SECRET-CANARY"),
crate::diagnostic_environment_value("KS_SECRET_HELIUS_API_KEY", "SECRET-CANARY"),
std::option::Option::Some("<redacted>".to_string())
);
assert_eq!(
super::diagnostic_environment_value("KS_LOGS_DIRECTORY", "/private/logs"),
crate::diagnostic_environment_value("KS_LOGS_DIRECTORY", "/private/logs"),
std::option::Option::Some("<configured>".to_string())
);
assert_eq!(
super::diagnostic_environment_value("KS_PUBLIC_SPL_TOKEN_PROGRAM_ID", "Token111"),
crate::diagnostic_environment_value("KS_PUBLIC_SPL_TOKEN_PROGRAM_ID", "Token111"),
std::option::Option::Some("Token111".to_string())
);
}
@@ -144,17 +145,17 @@ mod tests {
#[test]
fn namespaces_map_to_secret_internal_and_public() {
assert_eq!(
super::classify_environment_variable("KS_SECRET_HELIUS_API_KEY"),
crate::classify_environment_variable("KS_SECRET_HELIUS_API_KEY"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
);
assert_eq!(
super::classify_environment_variable("KS_LOGS_DIRECTORY"),
crate::classify_environment_variable("KS_LOGS_DIRECTORY"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
);
assert_eq!(
super::classify_environment_variable("KB_PUBLIC_DEMO_VALUE"),
crate::classify_environment_variable("KB_PUBLIC_DEMO_VALUE"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Public)
);
assert_eq!(super::classify_environment_variable("HOME"), std::option::Option::None);
assert_eq!(crate::classify_environment_variable("HOME"), std::option::Option::None);
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/settings.rs
// version: 25
// version: 29
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
@@ -12,7 +12,7 @@ pub struct AppConfig {
/// Active profile name.
pub active_profile: std::string::String,
/// Named profiles reconstructed from the active binary composition and shared documents.
pub profiles: std::vec::Vec<ProfileConfig>,
pub profiles: std::vec::Vec<crate::ProfileConfig>,
}
/// Resolved runtime profile selected by the root active profile name.
@@ -21,67 +21,35 @@ pub struct ProfileConfig {
/// Profile code.
pub name: std::string::String,
/// Database configuration.
pub database: DatabaseConfig,
pub database: crate::DatabaseConfig,
/// Solana endpoint and listener configuration.
pub solana: SolanaConfig,
pub solana: crate::SolanaConfig,
/// Wallet configuration.
pub wallet: WalletConfig,
pub wallet: crate::WalletConfig,
/// Execution safety configuration.
pub execution: ExecutionConfig,
pub execution: crate::ExecutionConfig,
}
/// Database backend configuration.
/// Backend-agnostic store configuration resolved by `ks-config`.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct DatabaseConfig {
/// Enables database storage.
/// Enables persistent storage for this profile.
pub enabled: bool,
/// Selected backend code.
/// Selected backend code interpreted by `ks-store`.
pub backend: std::string::String,
/// PostgreSQL configuration.
pub postgres: PostgresConfig,
/// SQLite configuration for tests or imports.
pub sqlite: SqliteConfig,
}
/// PostgreSQL backend configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct PostgresConfig {
/// PostgreSQL URL or local placeholder.
pub url: std::string::String,
/// Maximum connection count.
pub max_connections: u32,
/// Connection timeout in milliseconds.
pub connect_timeout_ms: u64,
/// Enables schema initialization at startup.
pub auto_initialize_schema: bool,
}
/// SQLite backend configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct SqliteConfig {
/// SQLite database path.
pub path: std::string::String,
/// Creates the file when missing.
pub create_if_missing: bool,
/// Busy timeout in milliseconds.
pub busy_timeout_ms: u64,
/// Maximum connection count.
pub max_connections: u32,
/// Enables schema initialization at startup.
pub auto_initialize_schema: bool,
/// Enables WAL mode.
pub use_wal: bool,
/// Backend-specific options retained as an opaque JSON object until `ks-store` opens the store.
pub backend_options: serde_json::Value,
}
/// Solana endpoints and listener configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct SolanaConfig {
/// HTTP JSON-RPC endpoints.
pub http_endpoints: std::vec::Vec<HttpEndpointConfig>,
pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
/// Standard Solana WebSocket endpoints.
pub ws_endpoints: std::vec::Vec<WsEndpointConfig>,
pub ws_endpoints: std::vec::Vec<crate::WsEndpointConfig>,
/// Listener declarations.
pub listeners: ListenerConfig,
pub listeners: crate::ListenerConfig,
}
/// HTTP JSON-RPC endpoint configuration.
@@ -104,7 +72,7 @@ pub struct HttpEndpointConfig {
/// Maximum idle connections per host.
pub max_idle_connections_per_host: u32,
/// Role-specific limits for this URL.
pub roles: std::vec::Vec<EndpointRoleConfig>,
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
}
/// Standard Solana WebSocket endpoint configuration.
@@ -133,7 +101,7 @@ pub struct WsEndpointConfig {
/// Enables automatic reconnect for this endpoint.
pub auto_reconnect: bool,
/// Role-specific limits for this URL.
pub roles: std::vec::Vec<EndpointRoleConfig>,
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
}
/// Role-specific endpoint limits.
@@ -167,11 +135,11 @@ pub struct ListenerConfig {
/// Default commitment used by listener subscriptions.
pub default_commitment: std::string::String,
/// Log listeners filtered by program mentions.
pub log_listeners: std::vec::Vec<LogListenerConfig>,
pub log_listeners: std::vec::Vec<crate::LogListenerConfig>,
/// Program account listeners.
pub program_listeners: std::vec::Vec<ProgramListenerConfig>,
pub program_listeners: std::vec::Vec<crate::ProgramListenerConfig>,
/// Account listeners.
pub account_listeners: std::vec::Vec<AccountListenerConfig>,
pub account_listeners: std::vec::Vec<crate::AccountListenerConfig>,
}
/// Log listener filtered by a program identifier mention.
@@ -298,7 +266,7 @@ pub fn config_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates a raw resolved runtime JSON string against the embedded compatibility schema.
pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match config_json_schema_value() {
let schema = match crate::config_json_schema_value() {
std::result::Result::Ok(schema) => schema,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -331,12 +299,12 @@ pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> {
}
/// Parses the resolved runtime application contract from a JSON string and validates it.
pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> {
match validate_config_json_schema(raw_json) {
pub fn parse_config_json(raw_json: &str) -> ks_core::Result<crate::AppConfig> {
match crate::validate_config_json_schema(raw_json) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let config = match serde_json::from_str::<AppConfig>(raw_json) {
let config = match serde_json::from_str::<crate::AppConfig>(raw_json) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -345,14 +313,14 @@ pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> {
));
},
};
return match validate_config(&config) {
return match crate::validate_config(&config) {
std::result::Result::Ok(()) => std::result::Result::Ok(config),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Reads and parses a resolved runtime application snapshot from a filesystem path.
pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<AppConfig> {
pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<crate::AppConfig> {
let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(content) => content,
std::result::Result::Err(error) => {
@@ -362,14 +330,14 @@ pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<AppConfi
));
},
};
return parse_config_json(&raw_json);
return crate::parse_config_json(&raw_json);
}
/// Loads the workspace environment, resolves placeholders and parses one resolved runtime snapshot.
pub fn read_config_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<AppConfig> {
) -> ks_core::Result<crate::AppConfig> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -384,11 +352,11 @@ pub fn read_config_json_file_with_environment(
},
};
let resolved = crate::resolve_environment_placeholders(&raw_json);
return parse_config_json(&resolved);
return crate::parse_config_json(&resolved);
}
/// Returns the active profile declared by the root configuration.
pub fn active_profile(config: &AppConfig) -> ks_core::Result<&ProfileConfig> {
pub fn active_profile(config: &crate::AppConfig) -> ks_core::Result<&crate::ProfileConfig> {
for profile in &config.profiles {
if profile.name == config.active_profile {
return std::result::Result::Ok(profile);
@@ -401,7 +369,7 @@ pub fn active_profile(config: &AppConfig) -> ks_core::Result<&ProfileConfig> {
}
/// Validates a typed configuration after JSON Schema validation and deserialization.
pub fn validate_config(config: &AppConfig) -> ks_core::Result<()> {
pub fn validate_config(config: &crate::AppConfig) -> ks_core::Result<()> {
match validate_root_config(config) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -412,14 +380,14 @@ pub fn validate_config(config: &AppConfig) -> ks_core::Result<()> {
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match active_profile(config) {
return match crate::active_profile(config) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
match require_non_empty(&config.active_profile, "active_profile") {
fn validate_root_config(config: &crate::AppConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.active_profile, "active_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -432,7 +400,7 @@ fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
let mut names = std::collections::BTreeSet::<std::string::String>::new();
let mut active_count = 0_u32;
for profile in &config.profiles {
match require_non_empty(&profile.name, "profile.name") {
match crate::require_non_empty(&profile.name, "profile.name") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -455,8 +423,8 @@ fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(());
}
fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
match validate_database(&profile.database) {
fn validate_profile(profile: &crate::ProfileConfig) -> ks_core::Result<()> {
match crate::validate_database(&profile.database) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -464,44 +432,32 @@ 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_wallet(&profile.wallet) {
match crate::validate_wallet(&profile.wallet) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match validate_execution(&profile.execution) {
match crate::validate_execution(&profile.execution) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
return validate_wallet_execution_pair(&profile.wallet, &profile.execution);
}
pub(crate) fn validate_database(config: &DatabaseConfig) -> ks_core::Result<()> {
if config.backend != "postgres" && config.backend != "sqlite" {
return std::result::Result::Err(ks_core::Error::new(
"database_backend_invalid",
config.backend.clone(),
));
}
match require_non_empty(&config.postgres.url, "database.postgres.url") {
pub(crate) fn validate_database(config: &crate::DatabaseConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.backend, "database.backend") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if config.postgres.max_connections == 0 || config.sqlite.max_connections == 0 {
if !config.backend_options.is_object() {
return std::result::Result::Err(ks_core::Error::new(
"database_max_connections_zero",
"database connection limits must be greater than zero",
"database_backend_options_invalid",
"database.backend_options must be a JSON object",
));
}
if config.postgres.connect_timeout_ms == 0 || config.sqlite.busy_timeout_ms == 0 {
return std::result::Result::Err(ks_core::Error::new(
"database_timeout_zero",
"database timeouts must be greater than zero",
));
}
return require_non_empty(&config.sqlite.path, "database.sqlite.path");
return std::result::Result::Ok(());
}
fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
fn validate_solana(config: &crate::SolanaConfig) -> ks_core::Result<()> {
if config.http_endpoints.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"http_endpoints_empty",
@@ -522,7 +478,7 @@ fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
endpoint.name.clone(),
));
}
match validate_http_endpoint(endpoint) {
match crate::validate_http_endpoint(endpoint) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -535,15 +491,15 @@ fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
endpoint.name.clone(),
));
}
match validate_ws_endpoint(endpoint) {
match crate::validate_ws_endpoint(endpoint) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return validate_listeners(&config.listeners);
return crate::validate_listeners(&config.listeners);
}
pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Result<()> {
pub(crate) fn validate_http_endpoint(config: &crate::HttpEndpointConfig) -> ks_core::Result<()> {
match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -574,7 +530,7 @@ pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Re
return std::result::Result::Ok(());
}
pub(crate) fn validate_ws_endpoint(config: &WsEndpointConfig) -> ks_core::Result<()> {
pub(crate) fn validate_ws_endpoint(config: &crate::WsEndpointConfig) -> ks_core::Result<()> {
match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -603,17 +559,17 @@ fn validate_endpoint_common(
name: &str,
provider: &str,
cluster: &str,
roles: &[EndpointRoleConfig],
roles: &[crate::EndpointRoleConfig],
) -> ks_core::Result<()> {
match require_non_empty(name, "endpoint.name") {
match crate::require_non_empty(name, "endpoint.name") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(provider, "endpoint.provider") {
match crate::require_non_empty(provider, "endpoint.provider") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(cluster, "endpoint.cluster") {
match crate::require_non_empty(cluster, "endpoint.cluster") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -639,8 +595,8 @@ fn validate_endpoint_common(
return std::result::Result::Ok(());
}
fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
match require_non_empty(&config.role, "endpoint.roles.role") {
fn validate_endpoint_role(config: &crate::EndpointRoleConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.role, "endpoint.roles.role") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -651,7 +607,7 @@ fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
));
}
for request_kind in &config.request_kinds {
match require_non_empty(request_kind, "endpoint.roles.request_kinds") {
match crate::require_non_empty(request_kind, "endpoint.roles.request_kinds") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -669,8 +625,11 @@ fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(());
}
pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.default_commitment, "solana.listeners.default_commitment") {
pub(crate) fn validate_listeners(config: &crate::ListenerConfig) -> ks_core::Result<()> {
match crate::require_non_empty(
&config.default_commitment,
"solana.listeners.default_commitment",
) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -695,62 +654,65 @@ pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()>
return std::result::Result::Ok(());
}
fn validate_log_listener(config: &LogListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "log_listener.name") {
fn validate_log_listener(config: &crate::LogListenerConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.name, "log_listener.name") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.endpoint_role, "log_listener.endpoint_role") {
match crate::require_non_empty(&config.endpoint_role, "log_listener.endpoint_role") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
return require_non_empty(&config.program_id, "log_listener.program_id");
return crate::require_non_empty(&config.program_id, "log_listener.program_id");
}
fn validate_program_listener(config: &ProgramListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "program_listener.name") {
fn validate_program_listener(config: &crate::ProgramListenerConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.name, "program_listener.name") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.endpoint_role, "program_listener.endpoint_role") {
match crate::require_non_empty(&config.endpoint_role, "program_listener.endpoint_role") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
return require_non_empty(&config.program_id, "program_listener.program_id");
return crate::require_non_empty(&config.program_id, "program_listener.program_id");
}
fn validate_account_listener(config: &AccountListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "account_listener.name") {
fn validate_account_listener(config: &crate::AccountListenerConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.name, "account_listener.name") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.endpoint_role, "account_listener.endpoint_role") {
match crate::require_non_empty(&config.endpoint_role, "account_listener.endpoint_role") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
return require_non_empty(&config.account_pubkey, "account_listener.account_pubkey");
return crate::require_non_empty(&config.account_pubkey, "account_listener.account_pubkey");
}
pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
match require_non_empty(&config.wallet_dir, "wallet.wallet_dir") {
pub(crate) fn validate_wallet(config: &crate::WalletConfig) -> ks_core::Result<()> {
match crate::require_non_empty(&config.wallet_dir, "wallet.wallet_dir") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.temporary_wallet_dir, "wallet.temporary_wallet_dir") {
match crate::require_non_empty(&config.temporary_wallet_dir, "wallet.temporary_wallet_dir") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.cluster, "wallet.cluster") {
match crate::require_non_empty(&config.cluster, "wallet.cluster") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if let std::option::Option::Some(alias) = config.wallet_alias.as_deref() {
match validate_wallet_alias(alias, "wallet.wallet_alias") {
match crate::validate_wallet_alias(alias, "wallet.wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
match validate_wallet_alias(&config.temporary_wallet_alias, "wallet.temporary_wallet_alias") {
match crate::validate_wallet_alias(
&config.temporary_wallet_alias,
"wallet.temporary_wallet_alias",
) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -787,7 +749,7 @@ pub(crate) fn validate_wallet_alias(value: &str, field: &str) -> ks_core::Result
return std::result::Result::Ok(());
}
pub(crate) fn validate_execution(config: &ExecutionConfig) -> ks_core::Result<()> {
pub(crate) fn validate_execution(config: &crate::ExecutionConfig) -> ks_core::Result<()> {
let configured_spend = config
.localnet_max_spend_lamports
.saturating_add(config.devnet_max_spend_lamports)
@@ -833,8 +795,8 @@ pub(crate) fn validate_execution(config: &ExecutionConfig) -> ks_core::Result<()
}
fn validate_wallet_execution_pair(
wallet: &WalletConfig,
execution: &ExecutionConfig,
wallet: &crate::WalletConfig,
execution: &crate::ExecutionConfig,
) -> ks_core::Result<()> {
for (enabled, limit, cluster) in [
(
@@ -909,25 +871,25 @@ mod tests {
#[test]
fn schema_text_is_valid_json() {
let result = super::config_json_schema_value();
let result = crate::config_json_schema_value();
assert!(result.is_ok());
}
#[test]
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());
assert!(crate::validate_config_json_schema(DEFAULT_CONFIG).is_ok());
assert!(crate::validate_config_json_schema(EXAMPLE_CONFIG).is_ok());
}
#[test]
fn default_app_config_parses_and_resolves_active_profile() {
let config_result = super::parse_config_json(DEFAULT_CONFIG);
let config_result = crate::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!("default app config must parse: {error}"),
};
let active_result = super::active_profile(&config);
let active_result = crate::active_profile(&config);
assert!(active_result.is_ok());
//let active =
match active_result {
@@ -942,33 +904,62 @@ mod tests {
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);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
#[test]
fn default_app_config_separates_runtime_postgres_profiles() {
fn default_app_config_separates_selected_store_backend_options() {
let value = parse_default_value();
assert_eq!(
value["profiles"][0]["database"]["postgres"]["url"],
value["profiles"][0]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_DEVNET_URL}".to_string())
);
assert_eq!(
value["profiles"][1]["database"]["postgres"]["url"],
value["profiles"][1]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string())
);
assert_eq!(
value["profiles"][2]["database"]["postgres"]["url"],
value["profiles"][2]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string())
);
}
#[test]
fn parser_keeps_unknown_store_backend_options_opaque() {
let mut value = parse_default_value();
value["profiles"][0]["database"]["backend"] =
serde_json::Value::String("future-backend".to_string());
value["profiles"][0]["database"]["backend_options"] =
serde_json::json!({"secret": "STORE-SECRET-CANARY"});
let raw_json = value_to_json(&value);
let config = match crate::parse_config_json(&raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("opaque backend must parse: {error}"),
};
assert_eq!(config.profiles[0].database.backend, "future-backend");
assert_eq!(
config.profiles[0].database.backend_options["secret"],
serde_json::Value::String("STORE-SECRET-CANARY".to_string())
);
}
#[test]
fn schema_rejects_non_object_store_backend_options() {
let mut value = parse_default_value();
value["profiles"][0]["database"]["backend_options"] =
serde_json::Value::String("invalid".to_string());
let raw_json = value_to_json(&value);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
#[test]
fn schema_rejects_profile_enabled_field() {
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);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
@@ -977,7 +968,7 @@ mod tests {
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);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -987,7 +978,7 @@ mod tests {
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);
let result = super::validate_config_json_schema(&raw_json);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
@@ -997,7 +988,7 @@ mod tests {
value["profiles"][0]["data"]["idls_directory"] =
serde_json::Value::String("idls".to_string());
let raw_json = value_to_json(&value);
let result = super::validate_config_json_schema(&raw_json);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
@@ -1006,7 +997,7 @@ mod tests {
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);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
@@ -1016,7 +1007,7 @@ mod tests {
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);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1026,7 +1017,7 @@ mod tests {
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);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1036,7 +1027,7 @@ mod tests {
value["profiles"][0]["wallet"]["wallet_alias"] =
serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1046,7 +1037,7 @@ mod tests {
value["profiles"][0]["wallet"]["temporary_wallet_alias"] =
serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1055,7 +1046,7 @@ mod tests {
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);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1065,7 +1056,7 @@ mod tests {
value["profiles"][0]["execution"]["devnet_max_spend_lamports"] =
serde_json::Value::Number(serde_json::Number::from(0));
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
@@ -1081,7 +1072,7 @@ mod tests {
value["profiles"][0]["execution"][field] =
serde_json::Value::Number(serde_json::Number::from(0));
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
}
@@ -1092,7 +1083,7 @@ mod tests {
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);
let result = super::parse_config_json(&raw_json);
let result = crate::parse_config_json(&raw_json);
assert!(result.is_err());
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/store.rs
// version: 2
// version: 4
//! Independent store configuration document loading and validation.
@@ -11,7 +11,7 @@ pub struct StoreConfigDocument {
/// Default store profile used when a composition does not override it.
pub default_profile: std::string::String,
/// Named store profiles.
pub profiles: std::vec::Vec<StoreProfileConfig>,
pub profiles: std::vec::Vec<crate::StoreProfileConfig>,
}
/// Named store profile.
@@ -42,7 +42,7 @@ pub fn store_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw store JSON against the embedded schema.
pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match store_json_schema_value() {
let schema = match crate::store_json_schema_value() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -74,12 +74,12 @@ pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> {
}
/// Parses and validates one store configuration document.
pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument> {
match validate_store_json_schema(raw_json) {
pub fn parse_store_json(raw_json: &str) -> ks_core::Result<crate::StoreConfigDocument> {
match crate::validate_store_json_schema(raw_json) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let document = match serde_json::from_str::<StoreConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::StoreConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -88,7 +88,7 @@ pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument>
));
},
};
return match validate_store_document(&document) {
return match crate::validate_store_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -98,7 +98,7 @@ pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument>
pub fn read_store_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<StoreConfigDocument> {
) -> ks_core::Result<crate::StoreConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -112,14 +112,14 @@ pub fn read_store_json_file_with_environment(
));
},
};
return parse_store_json(&crate::resolve_environment_placeholders(&raw_json));
return crate::parse_store_json(&crate::resolve_environment_placeholders(&raw_json));
}
/// Returns one named store profile.
pub fn store_profile<'a>(
document: &'a StoreConfigDocument,
document: &'a crate::StoreConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a StoreProfileConfig> {
) -> ks_core::Result<&'a crate::StoreProfileConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -133,13 +133,13 @@ pub fn store_profile<'a>(
/// Returns the default store profile.
pub fn default_store_profile(
document: &StoreConfigDocument,
) -> ks_core::Result<&StoreProfileConfig> {
return store_profile(document, document.default_profile.as_str());
document: &crate::StoreConfigDocument,
) -> ks_core::Result<&crate::StoreProfileConfig> {
return crate::store_profile(document, document.default_profile.as_str());
}
/// Validates one typed store configuration document.
pub fn validate_store_document(document: &StoreConfigDocument) -> ks_core::Result<()> {
pub fn validate_store_document(document: &crate::StoreConfigDocument) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "store.default_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -167,7 +167,7 @@ pub fn validate_store_document(document: &StoreConfigDocument) -> ks_core::Resul
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match default_store_profile(document) {
return match crate::default_store_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -180,7 +180,7 @@ mod tests {
#[test]
fn default_and_example_store_configs_validate() {
assert!(super::parse_store_json(DEFAULT_STORE).is_ok());
assert!(super::parse_store_json(EXAMPLE_STORE).is_ok());
assert!(crate::parse_store_json(DEFAULT_STORE).is_ok());
assert!(crate::parse_store_json(EXAMPLE_STORE).is_ok());
}
}

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/transport.rs
// version: 3
// version: 5
//! Independent Solana transport configuration document loading and validation.
@@ -12,9 +12,9 @@ pub struct TransportConfigDocument {
/// Default transport profile used when a composition does not override it.
pub default_profile: std::string::String,
/// Named WebSocket endpoint defaults resolved before endpoint-specific overrides.
pub ws_endpoint_defaults: std::vec::Vec<WsEndpointDefaultsConfig>,
pub ws_endpoint_defaults: std::vec::Vec<crate::WsEndpointDefaultsConfig>,
/// Named transport profiles available in this document.
pub profiles: std::vec::Vec<TransportProfileSourceConfig>,
pub profiles: std::vec::Vec<crate::TransportProfileSourceConfig>,
}
/// Default values shared by one class of WebSocket endpoints.
@@ -70,7 +70,7 @@ pub struct WsEndpointSourceConfig {
pub url: std::string::String,
/// Optional endpoint-specific overrides.
#[serde(default)]
pub overrides: WsEndpointOverridesConfig,
pub overrides: crate::WsEndpointOverridesConfig,
/// Role-specific limits for this URL.
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
}
@@ -83,7 +83,7 @@ pub struct TransportProfileSourceConfig {
/// HTTP JSON-RPC endpoints.
pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
/// Standard Solana WebSocket endpoints before shared defaults are resolved.
pub ws_endpoints: std::vec::Vec<WsEndpointSourceConfig>,
pub ws_endpoints: std::vec::Vec<crate::WsEndpointSourceConfig>,
}
/// Fully resolved transport profile consumed by transport clients.
@@ -116,7 +116,7 @@ pub fn transport_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw transport JSON against the embedded transport schema.
pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match transport_json_schema_value() {
let schema = match crate::transport_json_schema_value() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -148,12 +148,12 @@ pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> {
}
/// Parses and validates one independent transport configuration document.
pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDocument> {
match validate_transport_json_schema(raw_json) {
pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<crate::TransportConfigDocument> {
match crate::validate_transport_json_schema(raw_json) {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let document = match serde_json::from_str::<TransportConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::TransportConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -162,7 +162,7 @@ pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDo
));
},
};
return match validate_transport_document(&document) {
return match crate::validate_transport_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -172,7 +172,7 @@ pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDo
pub fn read_transport_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<TransportConfigDocument> {
) -> ks_core::Result<crate::TransportConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -186,14 +186,14 @@ pub fn read_transport_json_file_with_environment(
));
},
};
return parse_transport_json(&crate::resolve_environment_placeholders(&raw_json));
return crate::parse_transport_json(&crate::resolve_environment_placeholders(&raw_json));
}
/// Resolves one named transport profile including WebSocket defaults and overrides.
pub fn transport_profile(
document: &TransportConfigDocument,
document: &crate::TransportConfigDocument,
profile_name: &str,
) -> ks_core::Result<TransportProfileConfig> {
) -> ks_core::Result<crate::TransportProfileConfig> {
let source = match source_transport_profile(document, profile_name) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -259,7 +259,7 @@ pub fn transport_profile(
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(TransportProfileConfig {
return std::result::Result::Ok(crate::TransportProfileConfig {
name: source.name.clone(),
http_endpoints: source.http_endpoints.clone(),
ws_endpoints,
@@ -268,15 +268,15 @@ pub fn transport_profile(
/// Returns the default resolved transport profile.
pub fn default_transport_profile(
document: &TransportConfigDocument,
) -> ks_core::Result<TransportProfileConfig> {
return transport_profile(document, document.default_profile.as_str());
document: &crate::TransportConfigDocument,
) -> ks_core::Result<crate::TransportProfileConfig> {
return crate::transport_profile(document, document.default_profile.as_str());
}
fn source_transport_profile<'a>(
document: &'a TransportConfigDocument,
document: &'a crate::TransportConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a TransportProfileSourceConfig> {
) -> ks_core::Result<&'a crate::TransportProfileSourceConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -289,9 +289,9 @@ fn source_transport_profile<'a>(
}
fn ws_endpoint_defaults<'a>(
document: &'a TransportConfigDocument,
document: &'a crate::TransportConfigDocument,
defaults_name: &str,
) -> ks_core::Result<&'a WsEndpointDefaultsConfig> {
) -> ks_core::Result<&'a crate::WsEndpointDefaultsConfig> {
for defaults in &document.ws_endpoint_defaults {
if defaults.name == defaults_name {
return std::result::Result::Ok(defaults);
@@ -304,7 +304,9 @@ fn ws_endpoint_defaults<'a>(
}
/// Validates a typed transport configuration document.
pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_core::Result<()> {
pub fn validate_transport_document(
document: &crate::TransportConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "transport.default_profile") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -379,12 +381,12 @@ pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_cor
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
match transport_profile(document, profile.name.as_str()) {
match crate::transport_profile(document, profile.name.as_str()) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match default_transport_profile(document) {
return match crate::default_transport_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -398,17 +400,17 @@ mod tests {
#[test]
fn default_and_example_transport_configs_validate() {
assert!(super::parse_transport_json(DEFAULT_TRANSPORT).is_ok());
assert!(super::parse_transport_json(EXAMPLE_TRANSPORT).is_ok());
assert!(crate::parse_transport_json(DEFAULT_TRANSPORT).is_ok());
assert!(crate::parse_transport_json(EXAMPLE_TRANSPORT).is_ok());
}
#[test]
fn websocket_defaults_are_resolved_before_endpoint_overrides() {
let document = match super::parse_transport_json(DEFAULT_TRANSPORT) {
let document = match crate::parse_transport_json(DEFAULT_TRANSPORT) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default transport must parse: {error}"),
};
let profile = match super::transport_profile(&document, "mainnet_research") {
let profile = match crate::transport_profile(&document, "mainnet_research") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("mainnet research transport must resolve: {error}")

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/wallet.rs
// version: 6
// version: 8
//! Independent wallet configuration document loading and validation.
@@ -13,7 +13,7 @@ pub struct WalletConfigDocument {
/// Default wallet profile used when a composition does not override it.
pub default_profile: std::string::String,
/// Named wallet profiles.
pub profiles: std::vec::Vec<WalletProfileConfig>,
pub profiles: std::vec::Vec<crate::WalletProfileConfig>,
}
/// Named wallet profile whose directory is relative to the global wallet root unless absolute.
@@ -41,7 +41,7 @@ pub fn wallet_json_schema_text() -> &'static str {
}
/// Parses and validates one wallet configuration document.
pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument> {
pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<crate::WalletConfigDocument> {
let schema = match serde_json::from_str::<serde_json::Value>(WALLET_JSON_SCHEMA) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -75,7 +75,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
"wallet configuration does not satisfy its schema",
));
}
let document = match serde_json::from_str::<WalletConfigDocument>(raw_json) {
let document = match serde_json::from_str::<crate::WalletConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new(
@@ -84,7 +84,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
));
},
};
return match validate_wallet_document(&document) {
return match crate::validate_wallet_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -94,7 +94,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
pub fn read_wallet_json_file_with_environment(
path: &std::path::Path,
workspace_root: &std::path::Path,
) -> ks_core::Result<WalletConfigDocument> {
) -> ks_core::Result<crate::WalletConfigDocument> {
match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -108,14 +108,14 @@ pub fn read_wallet_json_file_with_environment(
));
},
};
return parse_wallet_json(&crate::resolve_environment_placeholders(&raw_json));
return crate::parse_wallet_json(&crate::resolve_environment_placeholders(&raw_json));
}
/// Returns one named wallet source profile.
pub fn wallet_profile<'a>(
document: &'a WalletConfigDocument,
document: &'a crate::WalletConfigDocument,
profile_name: &str,
) -> ks_core::Result<&'a WalletProfileConfig> {
) -> ks_core::Result<&'a crate::WalletProfileConfig> {
for profile in &document.profiles {
if profile.name == profile_name {
return std::result::Result::Ok(profile);
@@ -129,17 +129,17 @@ pub fn wallet_profile<'a>(
/// Returns the default wallet source profile.
pub fn default_wallet_profile(
document: &WalletConfigDocument,
) -> ks_core::Result<&WalletProfileConfig> {
return wallet_profile(document, document.default_profile.as_str());
document: &crate::WalletConfigDocument,
) -> ks_core::Result<&crate::WalletProfileConfig> {
return crate::wallet_profile(document, document.default_profile.as_str());
}
/// Resolves one source wallet profile into the runtime wallet contract.
pub fn resolved_wallet_profile(
document: &WalletConfigDocument,
document: &crate::WalletConfigDocument,
profile_name: &str,
) -> ks_core::Result<crate::WalletConfig> {
let profile = match wallet_profile(document, profile_name) {
let profile = match crate::wallet_profile(document, profile_name) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -166,7 +166,7 @@ pub fn resolved_wallet_profile(
}
/// Validates one typed wallet configuration document.
pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Result<()> {
pub fn validate_wallet_document(document: &crate::WalletConfigDocument) -> ks_core::Result<()> {
match crate::require_non_empty(&document.wallets_directory, "wallet.wallets_directory") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -203,12 +203,12 @@ pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Res
profile.name.clone(),
));
}
match resolved_wallet_profile(document, profile.name.as_str()) {
match crate::resolved_wallet_profile(document, profile.name.as_str()) {
std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return match default_wallet_profile(document) {
return match crate::default_wallet_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
@@ -221,9 +221,9 @@ mod tests {
#[test]
fn default_and_example_wallet_configs_validate() {
let default = super::parse_wallet_json(DEFAULT_WALLET);
let default = crate::parse_wallet_json(DEFAULT_WALLET);
assert!(default.is_ok());
let example = super::parse_wallet_json(EXAMPLE_WALLET);
let example = crate::parse_wallet_json(EXAMPLE_WALLET);
assert!(example.is_ok());
let example = match example {
std::result::Result::Ok(value) => value,
@@ -238,11 +238,11 @@ mod tests {
#[test]
fn resolved_profile_separates_persistent_and_temporary_wallet_directories() {
let resolved_raw = crate::resolve_environment_placeholders(DEFAULT_WALLET);
let document = match super::parse_wallet_json(resolved_raw.as_str()) {
let document = match crate::parse_wallet_json(resolved_raw.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("resolved wallet config failed: {error}"),
};
let resolved = match super::resolved_wallet_profile(&document, "local_devnet") {
let resolved = match crate::resolved_wallet_profile(&document, "local_devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet profile resolution failed: {error}"),
};
@@ -269,7 +269,7 @@ mod tests {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
let parsed = super::parse_wallet_json(raw.as_str());
let parsed = crate::parse_wallet_json(raw.as_str());
assert!(parsed.is_ok());
let parsed = match parsed {
std::result::Result::Ok(value) => value,
@@ -289,6 +289,6 @@ mod tests {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
assert!(super::parse_wallet_json(raw.as_str()).is_err());
assert!(crate::parse_wallet_json(raw.as_str()).is_err());
}
}