0.1.0
This commit is contained in:
40
khbb_lib/Cargo.toml
Normal file
40
khbb_lib/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
# file: khbb_lib/Cargo.toml
|
||||
|
||||
[package]
|
||||
name = "khbb_lib"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
futures-util.workspace = true
|
||||
reqwest.workspace = true
|
||||
rustls.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
solana-account-decoder-client-types.workspace = true
|
||||
solana-address-lookup-table-interface.workspace = true
|
||||
solana-client.workspace = true
|
||||
solana-compute-budget-interface.workspace = true
|
||||
solana-rpc-client-api.workspace = true
|
||||
solana-sdk.workspace = true
|
||||
solana-sdk-ids.workspace = true
|
||||
solana-system-interface.workspace = true
|
||||
solana-transaction-status-client-types.workspace = true
|
||||
spl-associated-token-account-interface.workspace = true
|
||||
spl-memo-interface.workspace = true
|
||||
spl-token-2022-interface.workspace = true
|
||||
spl-token-interface.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
tokio-tungstenite.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
yellowstone-grpc-client.workspace = true
|
||||
yellowstone-grpc-proto.workspace = true
|
||||
33
khbb_lib/README.md
Normal file
33
khbb_lib/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- file: khbb_lib/README.md -->
|
||||
|
||||
# khbb_lib
|
||||
|
||||
Core library for the `khadhroony-bobot` workspace.
|
||||
|
||||
## Goals
|
||||
|
||||
- centralize reusable logic
|
||||
- expose explicit APIs to binaries
|
||||
- provide Solana RPC HTTP / WS / gRPC integrations
|
||||
- provide storage and domain layers
|
||||
- avoid hidden logic in binaries
|
||||
|
||||
## Rules
|
||||
|
||||
- no `anyhow`
|
||||
- no `thiserror`
|
||||
- no `?`
|
||||
- no `unwrap` / `expect`
|
||||
- explicit error handling
|
||||
- async first
|
||||
- `tracing`
|
||||
- no `mod.rs`
|
||||
- no `pub mod`
|
||||
- `pub use` only from `lib.rs`
|
||||
|
||||
## Initial scope
|
||||
|
||||
- config loading
|
||||
- tracing initialization
|
||||
- SQLite connectivity
|
||||
- listener runtime bootstrap
|
||||
22
khbb_lib/TODO.md
Normal file
22
khbb_lib/TODO.md
Normal file
@@ -0,0 +1,22 @@
|
||||
<!-- file: khbb_lib/TODO.md -->
|
||||
|
||||
# khbb_lib TODO
|
||||
|
||||
## Foundation
|
||||
|
||||
- [x] create public library entrypoint
|
||||
- [x] create explicit error type
|
||||
- [x] create config loader
|
||||
- [x] create tracing bootstrap
|
||||
- [x] create listener app runner
|
||||
|
||||
## Next
|
||||
|
||||
- [ ] add storage layer
|
||||
- [ ] add SQLite schema bootstrap
|
||||
- [ ] add HTTP RPC client layer based on `reqwest`
|
||||
- [ ] add WebSocket RPC client layer based on `tokio-tungstenite`
|
||||
- [ ] add Yellowstone gRPC client layer
|
||||
- [ ] add event normalization types
|
||||
- [ ] add listener orchestration
|
||||
- [ ] add tests
|
||||
59
khbb_lib/src/app.rs
Normal file
59
khbb_lib/src/app.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// file: khbb_lib/src/app.rs
|
||||
|
||||
/// Runs the initial listener application workflow.
|
||||
///
|
||||
/// This first version only:
|
||||
/// - loads configuration
|
||||
/// - opens the SQLite connection pool
|
||||
/// - verifies connectivity
|
||||
/// - keeps the runtime alive as the future integration point for listener tasks
|
||||
pub async fn run_listener_app(config_path: &str) -> core::result::Result<(), crate::KhbbError> {
|
||||
let config_result = crate::KhbbAppConfig::load_from_json_file(config_path).await;
|
||||
let config = match config_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Err(error);
|
||||
},
|
||||
};
|
||||
let tracing_result = crate::init_tracing(&config.log_filter);
|
||||
match tracing_result {
|
||||
Ok(()) => {},
|
||||
Err(error) => {
|
||||
return Err(error);
|
||||
},
|
||||
}
|
||||
tracing::info!(
|
||||
database_url = %config.database_url,
|
||||
solana_http_rpc_url = %config.solana_http_rpc_url,
|
||||
solana_ws_rpc_url = %config.solana_ws_rpc_url,
|
||||
yellowstone_grpc_url = ?config.yellowstone_grpc_url,
|
||||
"khbb listener app starting"
|
||||
);
|
||||
let connect_result = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect(&config.database_url)
|
||||
.await;
|
||||
let pool = match connect_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Err(crate::KhbbError::Database {
|
||||
context: "connect sqlite pool",
|
||||
message: error.to_string(),
|
||||
});
|
||||
},
|
||||
};
|
||||
let ping_result = sqlx::query("SELECT 1;").execute(&pool).await;
|
||||
match ping_result {
|
||||
Ok(_) => {
|
||||
tracing::info!("sqlite connectivity check succeeded");
|
||||
},
|
||||
Err(error) => {
|
||||
return Err(crate::KhbbError::Database {
|
||||
context: "ping sqlite database",
|
||||
message: error.to_string(),
|
||||
});
|
||||
},
|
||||
}
|
||||
tracing::info!("listener tasks are not wired yet");
|
||||
Ok(())
|
||||
}
|
||||
71
khbb_lib/src/config.rs
Normal file
71
khbb_lib/src/config.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
// file: khbb_lib/src/config.rs
|
||||
|
||||
/// Root application configuration used by the initial listener stack.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KhbbAppConfig {
|
||||
/// Path or URL to the SQLite database.
|
||||
pub database_url: std::string::String,
|
||||
/// Solana HTTP RPC endpoint.
|
||||
pub solana_http_rpc_url: std::string::String,
|
||||
/// Solana WebSocket RPC endpoint.
|
||||
pub solana_ws_rpc_url: std::string::String,
|
||||
/// Optional Yellowstone gRPC endpoint.
|
||||
pub yellowstone_grpc_url: std::option::Option<std::string::String>,
|
||||
/// Tracing filter string.
|
||||
pub log_filter: std::string::String,
|
||||
}
|
||||
|
||||
impl KhbbAppConfig {
|
||||
/// Loads the application configuration from a JSON file.
|
||||
pub async fn load_from_json_file(path: &str) -> core::result::Result<Self, crate::KhbbError> {
|
||||
let file_content_result = tokio::fs::read_to_string(path).await;
|
||||
let file_content = match file_content_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Err(crate::KhbbError::Io {
|
||||
context: "read config file",
|
||||
message: error.to_string(),
|
||||
});
|
||||
},
|
||||
};
|
||||
let parse_result = serde_json::from_str::<Self>(&file_content);
|
||||
let config = match parse_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Err(crate::KhbbError::Json {
|
||||
context: "parse config json",
|
||||
message: error.to_string(),
|
||||
});
|
||||
},
|
||||
};
|
||||
let validate_result = config.validate();
|
||||
match validate_result {
|
||||
Ok(()) => Ok(config),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
/// Validates the application configuration.
|
||||
pub fn validate(&self) -> core::result::Result<(), crate::KhbbError> {
|
||||
if self.database_url.trim().is_empty() {
|
||||
return Err(crate::KhbbError::Config {
|
||||
message: std::string::String::from("database_url must not be empty"),
|
||||
});
|
||||
}
|
||||
if self.solana_http_rpc_url.trim().is_empty() {
|
||||
return Err(crate::KhbbError::Config {
|
||||
message: std::string::String::from("solana_http_rpc_url must not be empty"),
|
||||
});
|
||||
}
|
||||
if self.solana_ws_rpc_url.trim().is_empty() {
|
||||
return Err(crate::KhbbError::Config {
|
||||
message: std::string::String::from("solana_ws_rpc_url must not be empty"),
|
||||
});
|
||||
}
|
||||
if self.log_filter.trim().is_empty() {
|
||||
return Err(crate::KhbbError::Config {
|
||||
message: std::string::String::from("log_filter must not be empty"),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
76
khbb_lib/src/error.rs
Normal file
76
khbb_lib/src/error.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// file: khbb_lib/src/error.rs
|
||||
|
||||
/// Main error type used across the khbb workspace.
|
||||
///
|
||||
/// This project intentionally uses a single explicit error enum instead of
|
||||
/// `anyhow` or `thiserror`.
|
||||
#[derive(Debug)]
|
||||
pub enum KhbbError {
|
||||
/// Returned when a filesystem operation fails.
|
||||
Io {
|
||||
/// Human-readable operation label.
|
||||
context: &'static str,
|
||||
/// Source message.
|
||||
message: std::string::String,
|
||||
},
|
||||
/// Returned when JSON decoding or encoding fails.
|
||||
Json {
|
||||
/// Human-readable operation label.
|
||||
context: &'static str,
|
||||
/// Source message.
|
||||
message: std::string::String,
|
||||
},
|
||||
/// Returned when configuration validation fails.
|
||||
Config {
|
||||
/// Validation message.
|
||||
message: std::string::String,
|
||||
},
|
||||
/// Returned when SQLx operations fail.
|
||||
Database {
|
||||
/// Human-readable operation label.
|
||||
context: &'static str,
|
||||
/// Source message.
|
||||
message: std::string::String,
|
||||
},
|
||||
/// Returned when tracing initialization fails.
|
||||
Tracing {
|
||||
/// Human-readable operation label.
|
||||
context: &'static str,
|
||||
/// Source message.
|
||||
message: std::string::String,
|
||||
},
|
||||
/// Returned when a runtime task fails.
|
||||
Runtime {
|
||||
/// Human-readable operation label.
|
||||
context: &'static str,
|
||||
/// Source message.
|
||||
message: std::string::String,
|
||||
},
|
||||
}
|
||||
|
||||
impl core::fmt::Display for KhbbError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Io { context, message } => {
|
||||
write!(f, "io error during {context}: {message}")
|
||||
},
|
||||
Self::Json { context, message } => {
|
||||
write!(f, "json error during {context}: {message}")
|
||||
},
|
||||
Self::Config { message } => {
|
||||
write!(f, "configuration error: {message}")
|
||||
},
|
||||
Self::Database { context, message } => {
|
||||
write!(f, "database error during {context}: {message}")
|
||||
},
|
||||
Self::Tracing { context, message } => {
|
||||
write!(f, "tracing error during {context}: {message}")
|
||||
},
|
||||
Self::Runtime { context, message } => {
|
||||
write!(f, "runtime error during {context}: {message}")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for KhbbError {}
|
||||
23
khbb_lib/src/lib.rs
Normal file
23
khbb_lib/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// file: khbb_lib/src/lib.rs
|
||||
|
||||
//! Core public library for the `khadhroony-bobot` workspace.
|
||||
//!
|
||||
//! This crate exposes the reusable building blocks shared by the khbb
|
||||
//! applications, starting with the listener runtime bootstrap.
|
||||
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
mod error;
|
||||
mod tracing_setup;
|
||||
|
||||
/// Public re-exports for the khbb core library.
|
||||
pub use crate::app::run_listener_app;
|
||||
/// Public re-exports for configuration loading.
|
||||
pub use crate::config::KhbbAppConfig;
|
||||
/// Public re-exports for configuration loading errors and runtime errors.
|
||||
pub use crate::error::KhbbError;
|
||||
/// Public re-exports for tracing initialization.
|
||||
pub use crate::tracing_setup::init_tracing;
|
||||
31
khbb_lib/src/tracing_setup.rs
Normal file
31
khbb_lib/src/tracing_setup.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
// file: khbb_lib/src/tracing_setup.rs
|
||||
|
||||
/// Initializes tracing subscribers for the application.
|
||||
pub fn init_tracing(log_filter: &str) -> core::result::Result<(), crate::KhbbError> {
|
||||
let env_filter_result =
|
||||
tracing_subscriber::EnvFilter::try_new(std::string::String::from(log_filter));
|
||||
let env_filter = match env_filter_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Err(crate::KhbbError::Tracing {
|
||||
context: "build env filter",
|
||||
message: error.to_string(),
|
||||
});
|
||||
},
|
||||
};
|
||||
let subscriber = tracing_subscriber::fmt()
|
||||
.with_env_filter(env_filter)
|
||||
.with_target(true)
|
||||
.with_thread_ids(true)
|
||||
.with_thread_names(true)
|
||||
.with_ansi(true)
|
||||
.finish();
|
||||
let set_result = tracing::subscriber::set_global_default(subscriber);
|
||||
match set_result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(error) => Err(crate::KhbbError::Tracing {
|
||||
context: "set global subscriber",
|
||||
message: error.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user