Browse Source

Added initial axum handlers

master
chodak166 4 months ago
parent
commit
623ea2edf2
  1. 9
      apps/app_api/Cargo.toml
  2. 5
      apps/app_api/config.toml.example
  3. 48
      apps/app_api/src/commands/listen.rs
  4. 32
      apps/app_api/src/container.rs
  5. 1
      apps/app_api/src/main.rs
  6. 23
      apps/app_api/src/router.rs
  7. 25
      apps/app_api/src/router/handlers.rs
  8. 34
      apps/app_api/src/router/responses.rs
  9. 23
      apps/app_api/src/router/state.rs

9
apps/app_api/Cargo.toml

@ -7,7 +7,6 @@ edition = "2024"
# Internal # Internal
applib = { path = "../../lib" } applib = { path = "../../lib" }
# Runtime & Async # Runtime & Async
tokio = { version = "1.48", features = ["full"] } tokio = { version = "1.48", features = ["full"] }
anyhow = "1.0" anyhow = "1.0"
@ -21,4 +20,10 @@ serde_json = "1.0"
toml = "0.9.8" toml = "0.9.8"
clap = { version = "4.5", features = ["derive", "env"] } clap = { version = "4.5", features = ["derive", "env"] }
config = "0.15.19" config = "0.15.19"
const_format = "0.2.35" const_format = "0.2.35"
chrono = { version = "0.4", features = ["serde"] }
# Web Framework
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["trace", "cors"] }

5
apps/app_api/config.toml.example

@ -0,0 +1,5 @@
[listen]
host = "0.0.0.0"
port = 3000
log_level = "info"

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

@ -1,12 +1,15 @@
use crate::commands::{ClapArgs, Configurable, Executable}; use crate::commands::{ClapArgs, Configurable, Executable};
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::container::Container; use crate::container::Container;
use crate::router;
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use config::ConfigBuilder; use config::ConfigBuilder;
use config::builder::DefaultState; use config::builder::DefaultState;
use serde::Deserialize; use serde::Deserialize;
use tokio::net::TcpListener;
use tracing::info;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct Config { pub struct Config {
@ -16,7 +19,7 @@ pub struct Config {
#[derive(ClapArgs, Debug, Clone)] #[derive(ClapArgs, Debug, Clone)]
pub struct ListenCmd { pub struct ListenCmd {
#[arg(short, long, help = defaults::HELP_LISTEN_HOST)] #[arg(short = 'H', long, help = defaults::HELP_LISTEN_HOST)]
pub host: Option<String>, pub host: Option<String>,
#[arg(short, long, help = defaults::HELP_LISTEN_PORT)] #[arg(short, long, help = defaults::HELP_LISTEN_PORT)]
@ -52,18 +55,53 @@ impl Configurable for ListenCmd {
#[async_trait] #[async_trait]
impl Executable for ListenCmd { impl Executable for ListenCmd {
async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> { async fn execute(&self, config: &AppConfig, _container: &Container) -> Result<()> {
let config = config let listen_config = config
.listen .listen
.as_ref() .as_ref()
.ok_or_else(|| anyhow::anyhow!("Decoder config missing"))?; .ok_or_else(|| anyhow::anyhow!("Listen config missing"))?;
// TODO: start axum server let app = router::create_router();
let addr = format!("{}:{}", listen_config.host, listen_config.port);
let listener = TcpListener::bind(&addr).await?;
info!("Starting server on {}", addr);
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
info!("Server shut down gracefully");
Ok(()) Ok(())
} }
} }
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("Failed to install signal handler")
.recv()
.await;
info!("Received SIGTERM signal");
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
mod defaults { mod defaults {
use const_format::formatcp; use const_format::formatcp;
pub const LISTEN_HOST: &str = "0.0.0.0"; pub const LISTEN_HOST: &str = "0.0.0.0";

32
apps/app_api/src/container.rs

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

1
apps/app_api/src/main.rs

@ -2,6 +2,7 @@ mod app;
mod commands; mod commands;
mod config; mod config;
mod container; mod container;
mod router;
use anyhow::Result; use anyhow::Result;
use app::Application; use app::Application;

23
apps/app_api/src/router.rs

@ -0,0 +1,23 @@
use axum::{
Router,
routing::{get, post},
};
use std::sync::Arc;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
mod handlers;
mod responses;
mod state;
pub use state::AppState;
pub fn create_router() -> Router {
let state = Arc::new(AppState::new());
Router::new()
.route("/api/echo", post(handlers::echo_handler))
.route("/api/version", get(handlers::version_handler))
.with_state(state)
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive())
}

25
apps/app_api/src/router/handlers.rs

@ -0,0 +1,25 @@
use axum::{Json, extract::State};
use chrono::Utc;
use serde_json::Value;
use std::sync::Arc;
use super::responses::{EchoResponse, ErrorResponse, VersionResponse};
use super::state::AppState;
pub async fn echo_handler(
State(_state): State<Arc<AppState>>,
Json(payload): Json<Value>,
) -> Result<Json<EchoResponse>, ErrorResponse> {
let response = EchoResponse {
data: payload,
timestamp: Utc::now().to_rfc3339(),
};
Ok(Json(response))
}
pub async fn version_handler(State(state): State<Arc<AppState>>) -> Json<VersionResponse> {
Json(VersionResponse {
name: state.name.clone(),
version: state.version.clone(),
})
}

34
apps/app_api/src/router/responses.rs

@ -0,0 +1,34 @@
use axum::{Json, http::StatusCode, response::IntoResponse};
use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Serialize)]
pub struct EchoResponse {
pub data: Value,
pub timestamp: String,
}
#[derive(Debug, Serialize)]
pub struct VersionResponse {
pub name: String,
pub version: String,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
}
}
impl<E: std::error::Error> From<E> for ErrorResponse {
fn from(err: E) -> Self {
Self {
error: err.to_string(),
}
}
}

23
apps/app_api/src/router/state.rs

@ -0,0 +1,23 @@
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Clone)]
pub struct AppState {
pub name: String,
pub version: String,
}
impl AppState {
pub fn new() -> Self {
Self {
name: APP_NAME.to_string(),
version: APP_VERSION.to_string(),
}
}
}
impl Default for AppState {
fn default() -> Self {
Self::new()
}
}
Loading…
Cancel
Save