Browse Source

Added app_cli/app_api crates

master
chodak166 4 months ago
parent
commit
dbf77e9876
  1. 3
      Cargo.toml
  2. 24
      apps/app_api/Cargo.toml
  3. 0
      apps/app_api/src/app.rs
  4. 74
      apps/app_api/src/commands.rs
  5. 73
      apps/app_api/src/commands/listen.rs
  6. 43
      apps/app_api/src/config.rs
  7. 31
      apps/app_api/src/container.rs
  8. 0
      apps/app_api/src/main.rs
  9. 4
      apps/app_cli/Cargo.toml
  10. 42
      apps/app_cli/src/app.rs
  11. 0
      apps/app_cli/src/commands.rs
  12. 0
      apps/app_cli/src/commands/decode.rs
  13. 0
      apps/app_cli/src/commands/encode.rs
  14. 0
      apps/app_cli/src/commands/import_dict.rs
  15. 0
      apps/app_cli/src/config.rs
  16. 0
      apps/app_cli/src/container.rs
  17. 14
      apps/app_cli/src/main.rs
  18. 4
      lib/src/sys_major/rules_pl.rs

3
Cargo.toml

@ -1,6 +1,7 @@
[workspace]
members = [
"app",
"apps/app_api",
"apps/app_cli",
"lib",
]
resolver = "3"

24
apps/app_api/Cargo.toml

@ -0,0 +1,24 @@
[package]
name = "phomnemic-server"
version = "0.1.0"
edition = "2024"
[dependencies]
# Internal
applib = { path = "../../lib" }
# Runtime & Async
tokio = { version = "1.48", features = ["full"] }
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
async-trait = "0.1"
# Configuration & Inputs
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.9.8"
clap = { version = "4.5", features = ["derive", "env"] }
config = "0.15.19"
const_format = "0.2.35"

0
app/src/app.rs → apps/app_api/src/app.rs

74
apps/app_api/src/commands.rs

@ -0,0 +1,74 @@
pub mod listen;
use crate::config::AppConfig;
use crate::container::Container;
use anyhow::Result;
use async_trait::async_trait;
use clap::Subcommand;
use clap::{Args as ClapArgs, Parser};
use config::ConfigBuilder;
use config::builder::DefaultState;
use std::path::PathBuf;
#[derive(Subcommand, Debug, Clone)]
pub enum Command {
/// Decode a word using given system
Listen(listen::ListenCmd),
}
impl Command {
pub fn into_app_command(self) -> Box<dyn AppCommand> {
match self {
Command::Listen(cmd) => Box::new(cmd),
}
}
}
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct CliArgs {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(ClapArgs, Debug)]
pub struct GlobalArgs {
/// Path to config file
#[arg(short, long, default_value = "config.toml")]
pub config: PathBuf,
#[arg(long, help = defaults::HELP_LOG)]
pub log_level: Option<String>,
}
pub trait Configurable {
fn apply_defaults(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>>;
fn apply_overrides(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>>;
}
#[async_trait]
pub trait Executable {
async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()>;
}
// AppCommand must be dyn-compatible. Configurable is already dyn-compatible.
// Executable is dyn-compatible because of #[async_trait].
pub trait AppCommand: Configurable + Executable {}
impl<T: Configurable + Executable> AppCommand for T {}
mod defaults {
use const_format::formatcp;
pub const LOG_LEVEL: &str = "info";
pub const HELP_LOG: &str = formatcp!("Override Log Level [default: {}]", LOG_LEVEL);
}

73
apps/app_api/src/commands/listen.rs

@ -0,0 +1,73 @@
use crate::commands::{ClapArgs, Configurable, Executable};
use crate::config::AppConfig;
use crate::container::Container;
use anyhow::Result;
use async_trait::async_trait;
use config::ConfigBuilder;
use config::builder::DefaultState;
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub host: String,
pub port: u16,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct ListenCmd {
#[arg(short, long, help = defaults::HELP_LISTEN_HOST)]
pub host: Option<String>,
#[arg(short, long, help = defaults::HELP_LISTEN_PORT)]
pub port: Option<u16>,
}
impl Configurable for ListenCmd {
fn apply_defaults(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>> {
builder
.set_default("listen.host", defaults::LISTEN_HOST)?
.set_default("listen.port", defaults::LISTEN_PORT)
.map_err(Into::into)
}
fn apply_overrides(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>> {
let mut builder = builder;
if let Some(host) = &self.host {
builder = builder.set_override("listen.host", host.clone())?;
}
if let Some(port) = &self.port {
builder = builder.set_override("listen.port", port.clone())?;
}
Ok(builder)
}
}
#[async_trait]
impl Executable for ListenCmd {
async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> {
let config = config
.listen
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Decoder config missing"))?;
// TODO: start axum server
Ok(())
}
}
mod defaults {
use const_format::formatcp;
pub const LISTEN_HOST: &str = "0.0.0.0";
pub const LISTEN_PORT: u16 = 3000;
pub const HELP_LISTEN_HOST: &str = formatcp!("Host address [default: {}]", LISTEN_HOST);
pub const HELP_LISTEN_PORT: &str = formatcp!("Port to listen on [default: {}]", LISTEN_PORT);
}

43
apps/app_api/src/config.rs

@ -0,0 +1,43 @@
use crate::commands::*;
use anyhow::{Context, Result};
use config::{Config, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
#[serde(default)]
pub listen: Option<listen::Config>,
pub log_level: String,
}
impl AppConfig {
pub fn build(args: &GlobalArgs, handler: &dyn Configurable) -> Result<Self> {
let mut builder = Config::builder();
// Command-specific defaults via Trait
builder = handler.apply_defaults(builder)?;
// File Layer
let config_path = &args.config;
let is_default_path = config_path.to_str() == Some("config.toml");
builder = builder.add_source(File::from(config_path.as_path()).required(!is_default_path));
// Environment Layer (e.g. APP_LISTEN_PORT)
builder = builder.add_source(Environment::with_prefix("APP").separator("_"));
// Global log level override
if let Some(ref level) = args.log_level {
builder = builder.set_override("log_level", level.clone())?;
}
// Command-specific overrides via Trait
builder = handler.apply_overrides(builder)?;
builder
.build()
.context("Failed to build configuration layers")?
.try_deserialize()
.context("Failed to deserialize Config")
}
}

31
apps/app_api/src/container.rs

@ -0,0 +1,31 @@
use std::sync::Arc;
use applib::DictImporter;
use applib::DictRepository;
use applib::SqliteDictRepository;
// use applib::SystemDecoder;
// use applib::SystemEncoder;
// use applib::sys_major as major;
#[derive(Clone)]
pub struct Container;
impl Container {
pub async fn new() -> anyhow::Result<Self> {
Ok(Self)
}
pub async fn create_dict_importer(&self, dict_name: &str) -> anyhow::Result<DictImporter> {
let repo = self.create_dict_repo(dict_name).await?;
Ok(DictImporter::new(repo))
}
pub async fn create_dict_repo(
&self,
dict_name: &str,
) -> anyhow::Result<Arc<dyn DictRepository>> {
let mut dict_repo = SqliteDictRepository::new("sqlite:app.db").await?;
dict_repo.use_dict(dict_name);
Ok(Arc::new(dict_repo))
}
}

0
app/src/main.rs → apps/app_api/src/main.rs

4
app/Cargo.toml → apps/app_cli/Cargo.toml

@ -1,11 +1,11 @@
[package]
name = "phomnemic"
name = "phomnemic-cli"
version = "0.1.0"
edition = "2024"
[dependencies]
# Internal
applib = { path = "../lib" }
applib = { path = "../../lib" }
# Runtime & Async

42
apps/app_cli/src/app.rs

@ -0,0 +1,42 @@
use crate::commands::{AppCommand, CliArgs};
use crate::config::AppConfig;
use crate::container::Container;
use anyhow::Result;
use clap::Parser;
use tracing::debug;
pub struct Application {
config: AppConfig,
container: Container,
command: Box<dyn AppCommand>,
}
impl Application {
pub async fn build() -> Result<Self> {
let args = CliArgs::parse();
let app_cmd = args.command.into_app_command();
let config = AppConfig::build(&args.global, app_cmd.as_ref())?;
tracing_subscriber::fmt()
.compact()
.with_env_filter(&config.log_level)
.with_target(false)
.init();
debug!("Bootstrapping application...");
let container = Container::new().await?;
Ok(Self {
config,
container,
command: app_cmd,
})
}
pub async fn run(self) -> Result<()> {
self.command.execute(&self.config, &self.container).await
}
}

0
app/src/commands.rs → apps/app_cli/src/commands.rs

0
app/src/commands/decode.rs → apps/app_cli/src/commands/decode.rs

0
app/src/commands/encode.rs → apps/app_cli/src/commands/encode.rs

0
app/src/commands/import_dict.rs → apps/app_cli/src/commands/import_dict.rs

0
app/src/config.rs → apps/app_cli/src/config.rs

0
app/src/container.rs → apps/app_cli/src/container.rs

14
apps/app_cli/src/main.rs

@ -0,0 +1,14 @@
mod app;
mod commands;
mod config;
mod container;
use anyhow::Result;
use app::Application;
#[tokio::main]
async fn main() -> Result<()> {
let app = Application::build().await?;
app.run().await?;
Ok(())
}

4
lib/src/sys_major/rules_pl.rs

@ -133,8 +133,8 @@ pub fn get_rules() -> Rules {
#[cfg(test)]
mod tests {
use super::*;
use crate::core::sys_major::Decoder;
use crate::traits::SystemDecoder;
use crate::SystemDecoder;
use crate::sys_major::Decoder;
#[test]
fn test_major_dict_pl_decode_0_1() {

Loading…
Cancel
Save