v0.5.1-pre.008
This commit is contained in:
@@ -1,8 +1,19 @@
|
||||
<!-- file: ks-config/CHANGELOG.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 19 -->
|
||||
|
||||
# CHANGELOG — ks-config
|
||||
|
||||
## `0.5.1-pre.008`
|
||||
|
||||
- Fix: use `Result::is_err()` for execution and wallet schema validation checks, removing the Clippy redundant-pattern warnings without changing fail-closed behavior.
|
||||
|
||||
- retire `ts-rs` de `ks-config`, supprime ses bindings TypeScript générés et réserve les projections frontend aux DTO des applications propriétaires ;
|
||||
- rend les contrats source/runtime susceptibles de contenir des valeurs résolues non sérialisables et non `Debug` par défaut, et retire les sérialiseurs publics de `AppConfig` ;
|
||||
- ajoute la classification `Secret > Internal > Public`, y compris pour les chaînes composées contenant plusieurs placeholders d'environnement, ainsi qu'une représentation diagnostic générique qui ne révèle jamais les valeurs secret/internal ;
|
||||
- rend la section `application` des compositions opaque pour `ks-config` et fournit un helper générique de validation JSON Schema pour les fragments possédés par les binaires ;
|
||||
- durcit les erreurs de validation/décodage afin de ne plus recopier les valeurs rejetées, notamment pour les URLs et fragments contenant potentiellement des secrets ;
|
||||
- ajoute un test d'API externe et des canaris de non-divulgation pour les helpers de sensibilité.
|
||||
|
||||
## `0.5.1-pre.007`
|
||||
|
||||
- extrait store, wallet et execution vers `config/store.config.json`, `config/wallet.config.json` et `config/execution.config.json`, chacun avec schéma, exemple et `default_profile` ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: ks-config/Cargo.toml
|
||||
# version: 4
|
||||
# version: 5
|
||||
|
||||
[package]
|
||||
name = "ks-config"
|
||||
@@ -14,7 +14,6 @@ ks-core = { path = "../ks-core" }
|
||||
jsonschema.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
ts-rs.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-config/README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# ks-config
|
||||
|
||||
@@ -46,7 +46,9 @@ Les valeurs globales telles que `logs_directory` et `wallets_directory` ne sont
|
||||
- `read_wallet_json_file_with_environment`, `wallet_profile`, `default_wallet_profile` ;
|
||||
- `read_execution_json_file_with_environment`, `execution_profile`, `default_execution_profile` ;
|
||||
- `load_workspace_environment`, `resolve_environment_placeholders` ;
|
||||
- `AppConfig`, `ProfileConfig` pendant la phase de compatibilité.
|
||||
- `classify_environment_variable`, `classify_environment_template`, `diagnostic_environment_value` ;
|
||||
- `validate_json_value_against_schema` pour les fragments possédés par les binaires ;
|
||||
- `AppConfig`, `ProfileConfig` pendant la phase de compatibilité backend-only.
|
||||
|
||||
## Frontières
|
||||
|
||||
@@ -54,7 +56,9 @@ Les valeurs globales telles que `logs_directory` et `wallets_directory` ne sont
|
||||
|
||||
`ks-onchain-transport` peut consommer directement `TransportProfileConfig`.
|
||||
|
||||
Les dérivations TS-RS des types de `ks-config` sont encore présentes pendant `pre.007`, mais elles ne sont pas considérées comme une frontière durable. L'audit montre que le frontend desktop n'importe pas directement ces bindings ; leur suppression ou réduction est prévue avec les DTO publics sûrs de la prochaine prerelease.
|
||||
Depuis `0.5.1-pre.008`, `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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
<!-- file: ks-config/TODO.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# TODO — ks-config
|
||||
|
||||
## Série `0.5.x`
|
||||
|
||||
- [ ] `0.5.1` - appliquer à l’exécution les classes `KS_SECRET_*` / `KB_SECRET_*`, `KS_PUBLIC_*` / `KB_PUBLIC_*` et internes, avec propagation de sensibilité aux valeurs composées.
|
||||
- [ ] `0.5.1` - séparer les représentations source, runtime résolue, publique et diagnostic.
|
||||
- [ ] `0.5.1` - interdire qu’un secret résolu soit sérialisé, loggé, inclus dans une erreur ou transmis via Tauri.
|
||||
- [ ] `0.5.1` - supprimer l’exposition frontend de `AppConfig` et `ProfileConfig` résolus complets.
|
||||
- [ ] `0.5.1` - retirer de `ks-config` et réduire dans `ks-lib` les bindings TS-RS qui ne constituent pas un contrat générique indépendant de Tauri ; définir les wrappers publics nécessaires dans `kb-app-demo-desktop`.
|
||||
- [ ] `0.5.1-pre.009` - réconcilier une dernière fois schémas, exemples, guide de configuration et contrat d'API externe avant archivage du plan.
|
||||
- [ ] Contrat - maintenir l’identité entre chaque schéma embarqué et son fichier sous `config/schemas/`.
|
||||
- [ ] Tests - ajouter les tests d’API externe et les canaris de non-divulgation.
|
||||
- [ ] Documentation - maintenir guides, exemples et `.env.example` avec l’implémentation correspondante.
|
||||
- [ ] Intégration - coordonner les surfaces publiques sûres avec logging, transports, pipeline, scénarios et applications.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-config/USAGE.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# Utilisation de ks-config
|
||||
|
||||
@@ -108,6 +108,14 @@ KS_WALLETS_DIRECTORY
|
||||
|
||||
Le schéma de ce contrat est `config/schemas/resolved.app.config.schema.json`. Les fixtures correspondantes sont sous `test-fixtures/config/`.
|
||||
|
||||
## Frontière source/runtime/public/diagnostic
|
||||
|
||||
`AppConfig`, `ProfileConfig` et les documents spécialisés chargés avec résolution d'environnement sont des contrats backend-only. Ils peuvent contenir des URLs, DSN, chemins ou autres valeurs sensibles et ne dérivent ni `serde::Serialize` ni `Debug`. `ks-config` n'offre plus de sérialiseur du runtime résolu.
|
||||
|
||||
La sensibilité des variables suit `Secret > Internal > Public`. `classify_environment_template` applique cette règle aux chaînes composées : une URL contenant `${KS_SECRET_HELIUS_API_KEY}` est secrète même si le champ s'appelle simplement `url`. `diagnostic_environment_value` ne retourne jamais une valeur secrète ou interne en clair.
|
||||
|
||||
Les fragments propres à un binaire restent opaques dans `CompositionProfileConfig.application`. Le propriétaire les valide avec `validate_json_value_against_schema` et son schéma dédié.
|
||||
|
||||
## Frontière TypeScript
|
||||
|
||||
Les bindings TS-RS générés depuis `ks-config` ne sont pas importés directement par le frontend desktop actuel. Ils sont donc considérés comme transitoires jusqu'à la prochaine prerelease, qui introduira les DTO publics/diagnostiques et supprimera les exports TypeScript non justifiés des crates généralistes.
|
||||
`ks-config` ne dépend plus de TS-RS et ne génère aucun binding TypeScript. Les types traversant Tauri sont définis comme DTO/wrappers dans l'application propriétaire ; `kb-app-demo-desktop` construit notamment ses projections de configuration publique et diagnostic sans sérialiser `AppConfig/ProfileConfig`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/composition.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Binary composition configuration and resolution into the shared runtime profile contract.
|
||||
|
||||
@@ -13,7 +13,7 @@ const DEFAULT_WALLET_CONFIG_PATH: &str = "config/wallet.config.json";
|
||||
const DEFAULT_EXECUTION_CONFIG_PATH: &str = "config/execution.config.json";
|
||||
|
||||
/// Root composition document used by a binary to select shared configuration profiles.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct CompositionConfigDocument {
|
||||
/// Active binary composition profile name.
|
||||
pub active_profile: std::string::String,
|
||||
@@ -24,7 +24,7 @@ pub struct CompositionConfigDocument {
|
||||
}
|
||||
|
||||
/// Paths of shared configuration documents consumed by one binary composition.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct CompositionConfigSources {
|
||||
/// Logging configuration document path relative to the workspace root unless absolute.
|
||||
pub logging: std::string::String,
|
||||
@@ -41,12 +41,12 @@ pub struct CompositionConfigSources {
|
||||
}
|
||||
|
||||
/// Named composition profile with optional overrides of each shared document default.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct CompositionProfileConfig {
|
||||
/// Composition profile code.
|
||||
pub name: std::string::String,
|
||||
/// Application metadata retained until the application-specific split is completed.
|
||||
pub app: crate::AppSectionConfig,
|
||||
/// Optional binary-owned application settings opaque to the shared composition layer.
|
||||
pub application: std::option::Option<serde_json::Value>,
|
||||
/// Optional logging profile override.
|
||||
pub logging_profile: std::option::Option<std::string::String>,
|
||||
/// Optional transport profile override.
|
||||
@@ -59,12 +59,10 @@ pub struct CompositionProfileConfig {
|
||||
pub wallet_profile: std::option::Option<std::string::String>,
|
||||
/// Optional execution profile override.
|
||||
pub execution_profile: std::option::Option<std::string::String>,
|
||||
/// Desktop demo flags retained until the application-specific split is completed.
|
||||
pub demo: crate::DemoConfig,
|
||||
}
|
||||
|
||||
/// Resolved application configuration and the source documents needed by a binary.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct ComposedAppConfig {
|
||||
/// Parsed binary composition document.
|
||||
pub composition: CompositionConfigDocument,
|
||||
@@ -127,9 +125,9 @@ pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> {
|
||||
};
|
||||
return match validator.validate(&instance) {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"composition_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"composition configuration does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
@@ -142,10 +140,10 @@ pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConf
|
||||
}
|
||||
let document = match serde_json::from_str::<CompositionConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"composition_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"composition configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -291,7 +289,6 @@ pub fn compose_app_config(
|
||||
};
|
||||
profiles.push(crate::ProfileConfig {
|
||||
name: profile.name.clone(),
|
||||
app: profile.app.clone(),
|
||||
database: store_profile.database.clone(),
|
||||
solana: crate::SolanaConfig {
|
||||
http_endpoints: transport_profile.http_endpoints,
|
||||
@@ -300,7 +297,6 @@ pub fn compose_app_config(
|
||||
},
|
||||
wallet: wallet_profile,
|
||||
execution: execution_profile.config.clone(),
|
||||
demo: profile.demo.clone(),
|
||||
});
|
||||
}
|
||||
let config = crate::AppConfig {
|
||||
@@ -437,20 +433,13 @@ pub fn read_default_shared_app_config_with_environment(
|
||||
},
|
||||
profiles: vec![CompositionProfileConfig {
|
||||
name: profile_name.clone(),
|
||||
app: crate::AppSectionConfig {
|
||||
name: "khadhroony-solana".to_string(),
|
||||
environment: profile_name,
|
||||
},
|
||||
application: std::option::Option::None,
|
||||
logging_profile: std::option::Option::None,
|
||||
transport_profile: std::option::Option::None,
|
||||
listeners_profile: std::option::Option::None,
|
||||
store_profile: std::option::Option::None,
|
||||
wallet_profile: std::option::Option::None,
|
||||
execution_profile: std::option::Option::None,
|
||||
demo: crate::DemoConfig {
|
||||
live_demo_enabled: false,
|
||||
trading_demo_enabled: false,
|
||||
},
|
||||
}],
|
||||
};
|
||||
return compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution);
|
||||
@@ -593,7 +582,7 @@ mod tests {
|
||||
const DEFAULT_COMPOSITION: &str =
|
||||
include_str!("../../config/kb-app-demo-desktop.default.config.json");
|
||||
const EXAMPLE_COMPOSITION: &str =
|
||||
include_str!("../../config/example.kb-app-demo-desktop.default.config.json");
|
||||
include_str!("../../config/exemples/example.kb-app-demo-desktop.default.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_compositions_validate() {
|
||||
@@ -613,6 +602,7 @@ mod tests {
|
||||
panic!("local Devnet composition must resolve: {error}")
|
||||
},
|
||||
};
|
||||
assert!(profile.application.is_some());
|
||||
assert!(profile.logging_profile.is_none());
|
||||
assert!(profile.transport_profile.is_none());
|
||||
assert!(profile.store_profile.is_none());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/environment.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Environment-file loading and configuration placeholder resolution.
|
||||
|
||||
@@ -32,9 +32,9 @@ pub fn load_workspace_environment(
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(EnvironmentLoadReport {
|
||||
loaded_path: std::option::Option::Some(selected_path),
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"config_env_file_load_failed",
|
||||
format!("{}: {error}", selected_path.display()),
|
||||
format!("failed to load environment file {}", selected_path.display()),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/execution.rs
|
||||
// version: 1
|
||||
// version: 3
|
||||
|
||||
//! Independent execution policy configuration document loading and validation.
|
||||
|
||||
@@ -7,7 +7,7 @@ const EXECUTION_JSON_SCHEMA: &str =
|
||||
include_str!("../../config/schemas/execution.config.schema.json");
|
||||
|
||||
/// Root execution policy document with one default profile and named alternatives.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ExecutionConfigDocument {
|
||||
/// Default execution profile used when a composition does not override it.
|
||||
pub default_profile: std::string::String,
|
||||
@@ -16,7 +16,7 @@ pub struct ExecutionConfigDocument {
|
||||
}
|
||||
|
||||
/// Named execution policy profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ExecutionProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -59,18 +59,18 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::result::Result::Err(error) = validator.validate(&instance) {
|
||||
if validator.validate(&instance).is_err() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"execution_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"execution configuration does not satisfy its schema",
|
||||
));
|
||||
}
|
||||
let document = match serde_json::from_str::<ExecutionConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"execution_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"execution configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -162,7 +162,8 @@ pub fn validate_execution_document(document: &ExecutionConfigDocument) -> ks_cor
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const DEFAULT_EXECUTION: &str = include_str!("../../config/execution.config.json");
|
||||
const EXAMPLE_EXECUTION: &str = include_str!("../../config/example.execution.config.json");
|
||||
const EXAMPLE_EXECUTION: &str =
|
||||
include_str!("../../config/exemples/example.execution.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_execution_configs_validate() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/lib.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Khadhroony Solana shared configuration contracts and composition helpers.
|
||||
#![warn(missing_docs)]
|
||||
@@ -10,6 +10,8 @@ mod composition;
|
||||
mod environment;
|
||||
mod execution;
|
||||
mod listeners;
|
||||
mod schema;
|
||||
mod sensitivity;
|
||||
mod settings;
|
||||
mod store;
|
||||
mod transport;
|
||||
@@ -97,16 +99,22 @@ pub use self::listeners::resolved_listener_config;
|
||||
pub use self::listeners::validate_listeners_document;
|
||||
/// Exposes listener JSON Schema validation.
|
||||
pub use self::listeners::validate_listeners_json_schema;
|
||||
/// Exposes generic caller-owned JSON Schema validation.
|
||||
pub use self::schema::validate_json_value_against_schema;
|
||||
/// Exposes Khadhroony environment-value sensitivity classification.
|
||||
pub use self::sensitivity::EnvironmentValueSensitivity;
|
||||
/// Exposes composed-value environment-placeholder sensitivity classification.
|
||||
pub use self::sensitivity::classify_environment_template;
|
||||
/// Exposes Khadhroony environment-variable namespace classification.
|
||||
pub use self::sensitivity::classify_environment_variable;
|
||||
/// Exposes the bounded generic diagnostic representation for an environment value.
|
||||
pub use self::sensitivity::diagnostic_environment_value;
|
||||
/// Exposes the account listener configuration type.
|
||||
pub use self::settings::AccountListenerConfig;
|
||||
/// Exposes the root resolved runtime configuration type.
|
||||
pub use self::settings::AppConfig;
|
||||
/// Exposes the application metadata configuration type.
|
||||
pub use self::settings::AppSectionConfig;
|
||||
/// Exposes the database configuration type.
|
||||
pub use self::settings::DatabaseConfig;
|
||||
/// Exposes the temporary demo application configuration type.
|
||||
pub use self::settings::DemoConfig;
|
||||
/// Exposes the endpoint role configuration type.
|
||||
pub use self::settings::EndpointRoleConfig;
|
||||
/// Exposes the execution configuration type.
|
||||
@@ -143,10 +151,6 @@ pub use self::settings::parse_config_json;
|
||||
pub use self::settings::read_config_json_file;
|
||||
/// Exposes resolved runtime snapshot loading with workspace environment resolution.
|
||||
pub use self::settings::read_config_json_file_with_environment;
|
||||
/// Exposes the compact JSON serializer for resolved runtime configuration values.
|
||||
pub use self::settings::serialize_config_json;
|
||||
/// Exposes the pretty JSON serializer for resolved runtime configuration values.
|
||||
pub use self::settings::serialize_config_json_pretty;
|
||||
/// Exposes the typed resolved runtime configuration validator.
|
||||
pub use self::settings::validate_config;
|
||||
/// Exposes the JSON Schema validator for raw resolved runtime configuration JSON.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/listeners.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Independent Solana listener configuration document loading and validation.
|
||||
|
||||
@@ -7,7 +7,7 @@ const LISTENERS_JSON_SCHEMA: &str =
|
||||
include_str!("../../config/schemas/listeners.config.schema.json");
|
||||
|
||||
/// Root listener document containing independently selectable named profiles.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ListenersConfigDocument {
|
||||
/// Default listener profile used by consumers that do not provide an explicit selection.
|
||||
pub default_profile: std::string::String,
|
||||
@@ -16,7 +16,7 @@ pub struct ListenersConfigDocument {
|
||||
}
|
||||
|
||||
/// Named listener profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ListenersProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -75,9 +75,9 @@ pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> {
|
||||
};
|
||||
return match validator.validate(&instance) {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"listeners_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"listeners configuration does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
@@ -90,10 +90,10 @@ pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDo
|
||||
}
|
||||
let document = match serde_json::from_str::<ListenersConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(document) => document,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"listeners_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"listeners configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -214,7 +214,8 @@ pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_cor
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const DEFAULT_LISTENERS: &str = include_str!("../../config/listeners.config.json");
|
||||
const EXAMPLE_LISTENERS: &str = include_str!("../../config/example.listeners.config.json");
|
||||
const EXAMPLE_LISTENERS: &str =
|
||||
include_str!("../../config/exemples/example.listeners.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_listener_configs_validate() {
|
||||
|
||||
56
ks-config/src/schema.rs
Normal file
56
ks-config/src/schema.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// file: ks-config/src/schema.rs
|
||||
// version: 1
|
||||
|
||||
//! Generic JSON Schema validation helpers for binary-owned configuration fragments.
|
||||
|
||||
/// Validates one JSON value against caller-owned JSON Schema text without echoing instance values.
|
||||
pub fn validate_json_value_against_schema(
|
||||
schema_json: &str,
|
||||
instance: &serde_json::Value,
|
||||
validation_error_code: &str,
|
||||
) -> ks_core::Result<()> {
|
||||
let schema = match serde_json::from_str::<serde_json::Value>(schema_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"config_fragment_schema_parse_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let validator = match jsonschema::validator_for(&schema) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"config_fragment_schema_compile_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
return match validator.validate(instance) {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
validation_error_code,
|
||||
"configuration fragment does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
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(
|
||||
schema,
|
||||
&instance,
|
||||
"test_fragment_validation_failed",
|
||||
);
|
||||
let error = match result {
|
||||
std::result::Result::Ok(()) => panic!("invalid fragment must fail"),
|
||||
std::result::Result::Err(error) => error,
|
||||
};
|
||||
assert!(!error.to_string().contains("SECRET-CANARY"));
|
||||
}
|
||||
}
|
||||
160
ks-config/src/sensitivity.rs
Normal file
160
ks-config/src/sensitivity.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
// file: ks-config/src/sensitivity.rs
|
||||
// version: 1
|
||||
|
||||
//! Application environment-variable sensitivity classification.
|
||||
|
||||
/// Sensitivity assigned to one Khadhroony-owned environment value.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum EnvironmentValueSensitivity {
|
||||
/// Secret value that must never cross logs, diagnostics, serialization or UI boundaries.
|
||||
Secret,
|
||||
/// Internal value that is hidden by default and may appear only through an explicit bounded diagnostic.
|
||||
Internal,
|
||||
/// Value explicitly classified as eligible for a public DTO.
|
||||
Public,
|
||||
}
|
||||
|
||||
/// Classifies every Khadhroony environment placeholder contained in arbitrary text.
|
||||
///
|
||||
/// The returned sensitivity follows `Secret > Internal > Public`, so an endpoint URL that embeds
|
||||
/// 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> {
|
||||
let bytes = raw.as_bytes();
|
||||
let mut sensitivity = std::option::Option::None;
|
||||
let mut index = 0_usize;
|
||||
while index < bytes.len() {
|
||||
if bytes[index] == b'$' && index + 1 < bytes.len() && bytes[index + 1] == b'{' {
|
||||
let mut end = index + 2;
|
||||
while end < bytes.len() && bytes[end] != b'}' {
|
||||
end += 1;
|
||||
}
|
||||
if end < bytes.len() {
|
||||
let expression = &raw[index + 2..end];
|
||||
let name = match expression.split_once(":-") {
|
||||
std::option::Option::Some((name, _)) => name,
|
||||
std::option::Option::None => expression,
|
||||
};
|
||||
sensitivity = merge_sensitivity(sensitivity, classify_environment_variable(name));
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return sensitivity;
|
||||
}
|
||||
|
||||
/// Classifies a Khadhroony-owned environment variable by its namespace.
|
||||
pub fn classify_environment_variable(
|
||||
name: &str,
|
||||
) -> std::option::Option<EnvironmentValueSensitivity> {
|
||||
if name.starts_with("KS_SECRET_") || name.starts_with("KB_SECRET_") {
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Secret);
|
||||
}
|
||||
if name.starts_with("KS_PUBLIC_") || name.starts_with("KB_PUBLIC_") {
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Public);
|
||||
}
|
||||
if name.starts_with("KS_") || name.starts_with("KB_") {
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Internal);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
/// Produces the generic diagnostic representation allowed for one environment value.
|
||||
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) => {
|
||||
std::option::Option::Some("<redacted>".to_string())
|
||||
},
|
||||
std::option::Option::Some(EnvironmentValueSensitivity::Internal) => {
|
||||
std::option::Option::Some("<configured>".to_string())
|
||||
},
|
||||
std::option::Option::Some(EnvironmentValueSensitivity::Public) => {
|
||||
std::option::Option::Some(value.to_string())
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Secret);
|
||||
}
|
||||
if left == std::option::Option::Some(EnvironmentValueSensitivity::Internal)
|
||||
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Internal)
|
||||
{
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Internal);
|
||||
}
|
||||
if left == std::option::Option::Some(EnvironmentValueSensitivity::Public)
|
||||
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Public)
|
||||
{
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Public);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn composed_values_inherit_the_strongest_placeholder_sensitivity() {
|
||||
assert_eq!(
|
||||
super::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(
|
||||
"${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}"),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Public)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_diagnostic_never_reveals_secret_or_internal_values() {
|
||||
assert_eq!(
|
||||
super::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"),
|
||||
std::option::Option::Some("<configured>".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::diagnostic_environment_value("KS_PUBLIC_SPL_TOKEN_PROGRAM_ID", "Token111"),
|
||||
std::option::Option::Some("Token111".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespaces_map_to_secret_internal_and_public() {
|
||||
assert_eq!(
|
||||
super::classify_environment_variable("KS_SECRET_HELIUS_API_KEY"),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
|
||||
);
|
||||
assert_eq!(
|
||||
super::classify_environment_variable("KS_LOGS_DIRECTORY"),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
|
||||
);
|
||||
assert_eq!(
|
||||
super::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);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
// file: ks-config/src/settings.rs
|
||||
// version: 22
|
||||
// version: 23
|
||||
|
||||
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
const CONFIG_JSON_SCHEMA: &str =
|
||||
include_str!("../../config/schemas/resolved.app.config.schema.json");
|
||||
|
||||
/// Resolved runtime configuration containing every composed named profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/AppConfig.ts")]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct AppConfig {
|
||||
/// Active profile name.
|
||||
pub active_profile: std::string::String,
|
||||
@@ -19,16 +16,10 @@ pub struct AppConfig {
|
||||
}
|
||||
|
||||
/// Resolved runtime profile selected by the root active profile name.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/ProfileConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
/// Application metadata.
|
||||
pub app: AppSectionConfig,
|
||||
/// Database configuration.
|
||||
pub database: DatabaseConfig,
|
||||
/// Solana endpoint and listener configuration.
|
||||
@@ -37,29 +28,10 @@ pub struct ProfileConfig {
|
||||
pub wallet: WalletConfig,
|
||||
/// Execution safety configuration.
|
||||
pub execution: ExecutionConfig,
|
||||
/// Demo application configuration.
|
||||
pub demo: DemoConfig,
|
||||
}
|
||||
|
||||
/// Application metadata for a profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/AppSectionConfig.ts"
|
||||
)]
|
||||
pub struct AppSectionConfig {
|
||||
/// Application name.
|
||||
pub name: std::string::String,
|
||||
/// Environment name.
|
||||
pub environment: std::string::String,
|
||||
}
|
||||
|
||||
/// Database backend configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/DatabaseConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Enables database storage.
|
||||
pub enabled: bool,
|
||||
@@ -72,33 +44,26 @@ pub struct DatabaseConfig {
|
||||
}
|
||||
|
||||
/// PostgreSQL backend configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/PostgresConfig.ts"
|
||||
)]
|
||||
#[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.
|
||||
#[ts(type = "number")]
|
||||
pub connect_timeout_ms: u64,
|
||||
/// Enables schema initialization at startup.
|
||||
pub auto_initialize_schema: bool,
|
||||
}
|
||||
|
||||
/// SQLite backend configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/SqliteConfig.ts")]
|
||||
#[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.
|
||||
#[ts(type = "number")]
|
||||
pub busy_timeout_ms: u64,
|
||||
/// Maximum connection count.
|
||||
pub max_connections: u32,
|
||||
@@ -109,8 +74,7 @@ pub struct SqliteConfig {
|
||||
}
|
||||
|
||||
/// Solana endpoints and listener configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/SolanaConfig.ts")]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct SolanaConfig {
|
||||
/// HTTP JSON-RPC endpoints.
|
||||
pub http_endpoints: std::vec::Vec<HttpEndpointConfig>,
|
||||
@@ -121,11 +85,7 @@ pub struct SolanaConfig {
|
||||
}
|
||||
|
||||
/// HTTP JSON-RPC endpoint configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/HttpEndpointConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct HttpEndpointConfig {
|
||||
/// Endpoint name.
|
||||
pub name: std::string::String,
|
||||
@@ -138,10 +98,8 @@ pub struct HttpEndpointConfig {
|
||||
/// Full endpoint URL.
|
||||
pub url: std::string::String,
|
||||
/// Connection timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub connect_timeout_ms: u64,
|
||||
/// Request timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub request_timeout_ms: u64,
|
||||
/// Maximum idle connections per host.
|
||||
pub max_idle_connections_per_host: u32,
|
||||
@@ -150,11 +108,7 @@ pub struct HttpEndpointConfig {
|
||||
}
|
||||
|
||||
/// Standard Solana WebSocket endpoint configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/WsEndpointConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WsEndpointConfig {
|
||||
/// Endpoint name.
|
||||
pub name: std::string::String,
|
||||
@@ -167,13 +121,10 @@ pub struct WsEndpointConfig {
|
||||
/// Full endpoint URL.
|
||||
pub url: std::string::String,
|
||||
/// Connection timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub connect_timeout_ms: u64,
|
||||
/// Request timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub request_timeout_ms: u64,
|
||||
/// Unsubscribe timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub unsubscribe_timeout_ms: u64,
|
||||
/// Writer channel capacity.
|
||||
pub write_channel_capacity: u32,
|
||||
@@ -186,11 +137,7 @@ pub struct WsEndpointConfig {
|
||||
}
|
||||
|
||||
/// Role-specific endpoint limits.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/EndpointRoleConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct EndpointRoleConfig {
|
||||
/// Role code used by endpoint pools.
|
||||
pub role: std::string::String,
|
||||
@@ -209,16 +156,11 @@ pub struct EndpointRoleConfig {
|
||||
/// Maximum subscriptions allowed for this role on this URL.
|
||||
pub max_subscriptions: u32,
|
||||
/// Pause after a rate limit response in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub pause_after_rate_limit_ms: u64,
|
||||
}
|
||||
|
||||
/// Listener configuration used by standard WebSocket subscriptions.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/ListenerConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ListenerConfig {
|
||||
/// Enables listener creation.
|
||||
pub enabled: bool,
|
||||
@@ -233,11 +175,7 @@ pub struct ListenerConfig {
|
||||
}
|
||||
|
||||
/// Log listener filtered by a program identifier mention.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/LogListenerConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct LogListenerConfig {
|
||||
/// Listener name.
|
||||
pub name: std::string::String,
|
||||
@@ -252,11 +190,7 @@ pub struct LogListenerConfig {
|
||||
}
|
||||
|
||||
/// Program account listener.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/ProgramListenerConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ProgramListenerConfig {
|
||||
/// Listener name.
|
||||
pub name: std::string::String,
|
||||
@@ -271,11 +205,7 @@ pub struct ProgramListenerConfig {
|
||||
}
|
||||
|
||||
/// Account listener.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/AccountListenerConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct AccountListenerConfig {
|
||||
/// Listener name.
|
||||
pub name: std::string::String,
|
||||
@@ -290,8 +220,7 @@ pub struct AccountListenerConfig {
|
||||
}
|
||||
|
||||
/// Wallet configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/WalletConfig.ts")]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WalletConfig {
|
||||
/// Wallet directory path.
|
||||
pub wallet_dir: std::string::String,
|
||||
@@ -306,11 +235,7 @@ pub struct WalletConfig {
|
||||
}
|
||||
|
||||
/// Execution safety configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/ks_config/settings/ExecutionConfig.ts"
|
||||
)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct ExecutionConfig {
|
||||
/// Enables local validator transaction sending.
|
||||
pub localnet_send_enabled: bool,
|
||||
@@ -327,48 +252,29 @@ pub struct ExecutionConfig {
|
||||
/// Requires explicit operator confirmation before send.
|
||||
pub require_operator_confirmation: bool,
|
||||
/// Maximum spend in lamports for local validator tests.
|
||||
#[ts(type = "number")]
|
||||
pub localnet_max_spend_lamports: u64,
|
||||
/// Maximum spend in lamports for devnet tests.
|
||||
#[ts(type = "number")]
|
||||
pub devnet_max_spend_lamports: u64,
|
||||
/// Maximum spend in lamports for testnet tests.
|
||||
#[ts(type = "number")]
|
||||
pub testnet_max_spend_lamports: u64,
|
||||
/// Maximum spend in lamports for mainnet operations.
|
||||
#[ts(type = "number")]
|
||||
pub mainnet_max_spend_lamports: u64,
|
||||
/// Maximum estimated transaction fee in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub max_fee_lamports: u64,
|
||||
/// Maximum compute-unit price in micro-lamports.
|
||||
#[ts(type = "number")]
|
||||
pub max_compute_unit_price_micro_lamports: u64,
|
||||
/// Maximum accepted age for a recent blockhash in slots.
|
||||
#[ts(type = "number")]
|
||||
pub recent_blockhash_max_age_slots: u64,
|
||||
/// Maximum node retransmission retries requested by `sendTransaction`.
|
||||
pub send_max_retries: u32,
|
||||
/// Delay between transaction confirmation polls.
|
||||
#[ts(type = "number")]
|
||||
pub confirmation_poll_interval_ms: u64,
|
||||
/// Maximum number of transaction confirmation polls.
|
||||
pub confirmation_max_attempts: u32,
|
||||
/// Maximum devnet faucet airdrop allowed for a temporary wallet.
|
||||
#[ts(type = "number")]
|
||||
pub devnet_airdrop_max_lamports: u64,
|
||||
}
|
||||
|
||||
/// Demo application configuration.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/DemoConfig.ts")]
|
||||
pub struct DemoConfig {
|
||||
/// Enables live demo pages.
|
||||
pub live_demo_enabled: bool,
|
||||
/// Enables trading demo pages.
|
||||
pub trading_demo_enabled: bool,
|
||||
}
|
||||
|
||||
/// Returns the embedded JSON Schema text used for resolved runtime contract validation.
|
||||
pub fn config_json_schema_text() -> &'static str {
|
||||
return CONFIG_JSON_SCHEMA;
|
||||
@@ -413,9 +319,9 @@ pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> {
|
||||
let validation_result = validator.validate(&instance);
|
||||
return match validation_result {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"config_json_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"resolved configuration does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
@@ -428,10 +334,10 @@ pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> {
|
||||
}
|
||||
let config = match serde_json::from_str::<AppConfig>(raw_json) {
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"resolved configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -477,36 +383,6 @@ pub fn read_config_json_file_with_environment(
|
||||
return parse_config_json(&resolved);
|
||||
}
|
||||
|
||||
/// Serializes a configuration value to compact JSON.
|
||||
pub fn serialize_config_json(config: &AppConfig) -> ks_core::Result<std::string::String> {
|
||||
match validate_config(config) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return match serde_json::to_string(config) {
|
||||
std::result::Result::Ok(serialized) => std::result::Result::Ok(serialized),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
"config_json_serialize_failed",
|
||||
error.to_string(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes a configuration value to pretty JSON.
|
||||
pub fn serialize_config_json_pretty(config: &AppConfig) -> ks_core::Result<std::string::String> {
|
||||
match validate_config(config) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return match serde_json::to_string_pretty(config) {
|
||||
std::result::Result::Ok(serialized) => std::result::Result::Ok(serialized),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
"config_json_pretty_serialize_failed",
|
||||
error.to_string(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the active profile declared by the root configuration.
|
||||
pub fn active_profile(config: &AppConfig) -> ks_core::Result<&ProfileConfig> {
|
||||
for profile in &config.profiles {
|
||||
@@ -576,10 +452,6 @@ fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
|
||||
}
|
||||
|
||||
fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
|
||||
match validate_app_section(&profile.app) {
|
||||
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),
|
||||
@@ -599,14 +471,6 @@ fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
|
||||
return validate_wallet_execution_pair(&profile.wallet, &profile.execution);
|
||||
}
|
||||
|
||||
fn validate_app_section(config: &AppSectionConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.name, "app.name") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return require_non_empty(&config.environment, "app.environment");
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -683,7 +547,7 @@ pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Re
|
||||
if !config.url.starts_with("http://") && !config.url.starts_with("https://") {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"http_endpoint_url_invalid",
|
||||
config.url.clone(),
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
if config.connect_timeout_ms == 0
|
||||
@@ -714,7 +578,7 @@ pub(crate) fn validate_ws_endpoint(config: &WsEndpointConfig) -> ks_core::Result
|
||||
if !config.url.starts_with("ws://") && !config.url.starts_with("wss://") {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"ws_endpoint_url_invalid",
|
||||
config.url.clone(),
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
if config.connect_timeout_ms == 0
|
||||
@@ -1006,24 +870,6 @@ pub(crate) fn require_non_empty(value: &str, field_name: &str) -> ks_core::Resul
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
#[test]
|
||||
fn exported_json_configuration_numbers_do_not_use_bigint() {
|
||||
let config = ts_rs::Config::default();
|
||||
let declarations = [
|
||||
<crate::PostgresConfig as TS>::decl(&config),
|
||||
<crate::SqliteConfig as TS>::decl(&config),
|
||||
<crate::HttpEndpointConfig as TS>::decl(&config),
|
||||
<crate::WsEndpointConfig as TS>::decl(&config),
|
||||
<crate::EndpointRoleConfig as TS>::decl(&config),
|
||||
<crate::ExecutionConfig as TS>::decl(&config),
|
||||
];
|
||||
for declaration in declarations {
|
||||
assert!(!declaration.contains("bigint"));
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: &str =
|
||||
include_str!("../../test-fixtures/config/resolved.app.config.json");
|
||||
const EXAMPLE_CONFIG: &str =
|
||||
@@ -1077,29 +923,6 @@ mod tests {
|
||||
//assert_eq!(active.name, "local_devnet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
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!("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!("default app config must serialize: {error}"),
|
||||
};
|
||||
let reparsed_result = super::parse_config_json(&serialized);
|
||||
assert!(reparsed_result.is_ok());
|
||||
let reparsed = match reparsed_result {
|
||||
std::result::Result::Ok(reparsed) => reparsed,
|
||||
std::result::Result::Err(error) => panic!("serialized config must parse: {error}"),
|
||||
};
|
||||
assert_eq!(config, reparsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_rejects_missing_active_profile() {
|
||||
let mut value = parse_default_value();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: ks-config/src/store.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Independent store configuration document loading and validation.
|
||||
|
||||
const STORE_JSON_SCHEMA: &str = include_str!("../../config/schemas/store.config.schema.json");
|
||||
|
||||
/// Root store document with one default profile and named alternatives.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct StoreConfigDocument {
|
||||
/// Default store profile used when a composition does not override it.
|
||||
pub default_profile: std::string::String,
|
||||
@@ -15,7 +15,7 @@ pub struct StoreConfigDocument {
|
||||
}
|
||||
|
||||
/// Named store profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct StoreProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -66,9 +66,9 @@ pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> {
|
||||
};
|
||||
return match validator.validate(&instance) {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"store_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"store configuration does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
@@ -81,10 +81,10 @@ pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument>
|
||||
}
|
||||
let document = match serde_json::from_str::<StoreConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"store_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"store configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -176,7 +176,7 @@ pub fn validate_store_document(document: &StoreConfigDocument) -> ks_core::Resul
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const DEFAULT_STORE: &str = include_str!("../../config/store.config.json");
|
||||
const EXAMPLE_STORE: &str = include_str!("../../config/example.store.config.json");
|
||||
const EXAMPLE_STORE: &str = include_str!("../../config/exemples/example.store.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_store_configs_validate() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/transport.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Independent Solana transport configuration document loading and validation.
|
||||
|
||||
@@ -7,7 +7,7 @@ const TRANSPORT_JSON_SCHEMA: &str =
|
||||
include_str!("../../config/schemas/transport.config.schema.json");
|
||||
|
||||
/// Root transport document containing shared endpoint defaults and named profiles.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct TransportConfigDocument {
|
||||
/// Default transport profile used when a composition does not override it.
|
||||
pub default_profile: std::string::String,
|
||||
@@ -18,7 +18,7 @@ pub struct TransportConfigDocument {
|
||||
}
|
||||
|
||||
/// Default values shared by one class of WebSocket endpoints.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WsEndpointDefaultsConfig {
|
||||
/// Defaults code referenced by endpoints.
|
||||
pub name: std::string::String,
|
||||
@@ -37,7 +37,7 @@ pub struct WsEndpointDefaultsConfig {
|
||||
}
|
||||
|
||||
/// Endpoint-specific overrides applied on top of named WebSocket defaults.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, Default, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WsEndpointOverridesConfig {
|
||||
/// Optional connection timeout override.
|
||||
pub connect_timeout_ms: std::option::Option<u64>,
|
||||
@@ -54,7 +54,7 @@ pub struct WsEndpointOverridesConfig {
|
||||
}
|
||||
|
||||
/// Source shape of one WebSocket endpoint before defaults are resolved.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WsEndpointSourceConfig {
|
||||
/// Endpoint name.
|
||||
pub name: std::string::String,
|
||||
@@ -76,7 +76,7 @@ pub struct WsEndpointSourceConfig {
|
||||
}
|
||||
|
||||
/// Named Solana HTTP and WebSocket endpoint source profile.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct TransportProfileSourceConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -87,7 +87,7 @@ pub struct TransportProfileSourceConfig {
|
||||
}
|
||||
|
||||
/// Fully resolved transport profile consumed by transport clients.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct TransportProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -140,9 +140,9 @@ pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> {
|
||||
};
|
||||
return match validator.validate(&instance) {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"transport_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"transport configuration does not satisfy its schema",
|
||||
)),
|
||||
};
|
||||
}
|
||||
@@ -155,10 +155,10 @@ pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDo
|
||||
}
|
||||
let document = match serde_json::from_str::<TransportConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"transport_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"transport configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -393,7 +393,8 @@ pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_cor
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const DEFAULT_TRANSPORT: &str = include_str!("../../config/transport.config.json");
|
||||
const EXAMPLE_TRANSPORT: &str = include_str!("../../config/example.transport.config.json");
|
||||
const EXAMPLE_TRANSPORT: &str =
|
||||
include_str!("../../config/exemples/example.transport.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_transport_configs_validate() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: ks-config/src/wallet.rs
|
||||
// version: 1
|
||||
// version: 3
|
||||
|
||||
//! Independent wallet configuration document loading and validation.
|
||||
|
||||
const WALLET_JSON_SCHEMA: &str = include_str!("../../config/schemas/wallet.config.schema.json");
|
||||
|
||||
/// Root wallet document with global storage and named runtime profiles.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WalletConfigDocument {
|
||||
/// Root directory for wallet storage, independent from profile selection.
|
||||
pub wallets_directory: std::string::String,
|
||||
@@ -17,7 +17,7 @@ pub struct WalletConfigDocument {
|
||||
}
|
||||
|
||||
/// Named wallet profile whose directory is relative to the global wallet root unless absolute.
|
||||
#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WalletProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
@@ -67,18 +67,18 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::result::Result::Err(error) = validator.validate(&instance) {
|
||||
if validator.validate(&instance).is_err() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"wallet_config_schema_validation_failed",
|
||||
error.to_string(),
|
||||
"wallet configuration does not satisfy its schema",
|
||||
));
|
||||
}
|
||||
let document = match serde_json::from_str::<WalletConfigDocument>(raw_json) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"wallet_config_json_decode_failed",
|
||||
error.to_string(),
|
||||
"wallet configuration could not be decoded",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -206,7 +206,7 @@ pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Res
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const DEFAULT_WALLET: &str = include_str!("../../config/wallet.config.json");
|
||||
const EXAMPLE_WALLET: &str = include_str!("../../config/example.wallet.config.json");
|
||||
const EXAMPLE_WALLET: &str = include_str!("../../config/exemples/example.wallet.config.json");
|
||||
|
||||
#[test]
|
||||
fn default_and_example_wallet_configs_validate() {
|
||||
|
||||
@@ -6,36 +6,37 @@
|
||||
#[test]
|
||||
fn external_consumer_resolves_composition_through_crate_root_exports() {
|
||||
let composition = match ks_config::parse_composition_json(include_str!(
|
||||
"../../config/example.kb-app-demo-desktop.default.config.json"
|
||||
"../../config/exemples/example.kb-app-demo-desktop.default.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("composition must parse: {error}"),
|
||||
};
|
||||
let transport = match ks_config::parse_transport_json(include_str!(
|
||||
"../../config/example.transport.config.json"
|
||||
"../../config/exemples/example.transport.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("transport must parse: {error}"),
|
||||
};
|
||||
let listeners = match ks_config::parse_listeners_json(include_str!(
|
||||
"../../config/example.listeners.config.json"
|
||||
"../../config/exemples/example.listeners.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("listeners must parse: {error}"),
|
||||
};
|
||||
let store =
|
||||
match ks_config::parse_store_json(include_str!("../../config/example.store.config.json")) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("store must parse: {error}"),
|
||||
};
|
||||
let wallet =
|
||||
match ks_config::parse_wallet_json(include_str!("../../config/example.wallet.config.json"))
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("wallet must parse: {error}"),
|
||||
};
|
||||
let store = match ks_config::parse_store_json(include_str!(
|
||||
"../../config/exemples/example.store.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("store must parse: {error}"),
|
||||
};
|
||||
let wallet = match ks_config::parse_wallet_json(include_str!(
|
||||
"../../config/exemples/example.wallet.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("wallet must parse: {error}"),
|
||||
};
|
||||
let execution = match ks_config::parse_execution_json(include_str!(
|
||||
"../../config/example.execution.config.json"
|
||||
"../../config/exemples/example.execution.config.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("execution must parse: {error}"),
|
||||
|
||||
18
ks-config/tests/external_sensitivity_api.rs
Normal file
18
ks-config/tests/external_sensitivity_api.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
// file: ks-config/tests/external_sensitivity_api.rs
|
||||
// version: 1
|
||||
|
||||
//! External-contract coverage for configuration sensitivity helpers.
|
||||
|
||||
#[test]
|
||||
fn external_consumer_classifies_composed_values_and_redacts_secrets() {
|
||||
let sensitivity = ks_config::classify_environment_template(
|
||||
"https://provider.invalid/?api-key=${KS_SECRET_HELIUS_API_KEY}",
|
||||
);
|
||||
assert_eq!(
|
||||
sensitivity,
|
||||
std::option::Option::Some(ks_config::EnvironmentValueSensitivity::Secret)
|
||||
);
|
||||
let diagnostic =
|
||||
ks_config::diagnostic_environment_value("KS_SECRET_HELIUS_API_KEY", "SECRET-CANARY");
|
||||
assert_eq!(diagnostic, std::option::Option::Some("<redacted>".to_string()));
|
||||
}
|
||||
Reference in New Issue
Block a user