🚀 meta: initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
1190
Cargo.lock
generated
Normal file
1190
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "custom-backend-http"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
atom_syndication = "0.12.2"
|
||||||
|
axum = { version = "0.7.1", default-features = false, features = ["json", "tokio", "http2"] }
|
||||||
|
error-stack = "0.4.1"
|
||||||
|
redb = "1.4.0"
|
||||||
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
|
thiserror = "1.0.50"
|
||||||
|
tokio = { version = "1.34.0", features = ["macros", "rt-multi-thread", "rt", "net"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-appender = "0.2.3"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
4
src/env_vars.rs
Normal file
4
src/env_vars.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub const REDB_DB_PATH: &str = "REDB_DB_PATH";
|
||||||
|
pub const LOGS_DIR: &str = "LOGS_DIR";
|
||||||
|
pub const MAX_LOGS: &str = "MAX_LOGS";
|
||||||
|
pub const RUST_LOG: &str = "RUST_LOG";
|
173
src/lib.rs
Normal file
173
src/lib.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
use error_stack::{Report, ResultExt};
|
||||||
|
use redb::{CompactionError, Database};
|
||||||
|
use std::env;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
|
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||||
|
use tracing_subscriber::filter::EnvFilter;
|
||||||
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
|
use tracing_subscriber::fmt::writer::MakeWriterExt;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use tracing_subscriber::{Layer, Registry};
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
|
||||||
|
mod env_vars;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InnerAppState {
|
||||||
|
db: Database,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct AppState(Arc<InnerAppState>);
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
fn new(db: Database) -> Self {
|
||||||
|
AppState(Arc::new(InnerAppState { db }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("environment variable error")]
|
||||||
|
struct EnvironmentVariableError;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("error opening redb database")]
|
||||||
|
struct RedbOpeningError;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("app creation error")]
|
||||||
|
pub struct AppCreationError;
|
||||||
|
|
||||||
|
pub async fn create_app() -> Result<Router, Report<AppCreationError>> {
|
||||||
|
let db_path = env::var(env_vars::REDB_DB_PATH)
|
||||||
|
.attach_printable(env_vars::REDB_DB_PATH)
|
||||||
|
.change_context(EnvironmentVariableError)
|
||||||
|
.change_context(AppCreationError)?;
|
||||||
|
|
||||||
|
let mut db = Database::create(db_path)
|
||||||
|
.change_context(RedbOpeningError)
|
||||||
|
.change_context(AppCreationError)?;
|
||||||
|
|
||||||
|
if let Err(err) = db.compact() {
|
||||||
|
if matches!(err, CompactionError::Storage(_)) {
|
||||||
|
return Err(err)
|
||||||
|
.change_context(RedbOpeningError)
|
||||||
|
.change_context(AppCreationError)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let router = routes::create_router();
|
||||||
|
let app = router.with_state(AppState::new(db));
|
||||||
|
|
||||||
|
Ok(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BoxedErr(Box<dyn std::error::Error + Send + Sync>);
|
||||||
|
impl From<Box<dyn std::error::Error + Send + Sync>> for BoxedErr {
|
||||||
|
fn from(value: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||||
|
BoxedErr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for BoxedErr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for BoxedErr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for BoxedErr {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
self.0.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("tracing initialization error")]
|
||||||
|
pub struct TracingInitializationError;
|
||||||
|
|
||||||
|
pub struct Guards(WorkerGuard, WorkerGuard, WorkerGuard);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Suggestion(&'static str);
|
||||||
|
|
||||||
|
impl Display for Suggestion {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "suggestion: ")?;
|
||||||
|
Display::fmt(&self.0, f)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_tracing() -> Result<Guards, Report<TracingInitializationError>> {
|
||||||
|
let (stderr_writer, stderr_guard) = tracing_appender::non_blocking(std::io::stderr());
|
||||||
|
let (stdout_writer, stdout_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||||
|
|
||||||
|
let logs_dir = env::var(env_vars::LOGS_DIR)
|
||||||
|
.attach_printable(env_vars::LOGS_DIR)
|
||||||
|
.change_context(EnvironmentVariableError)
|
||||||
|
.change_context(TracingInitializationError)?;
|
||||||
|
|
||||||
|
let max_logs = env::var(env_vars::MAX_LOGS)
|
||||||
|
.attach_printable(env_vars::MAX_LOGS)
|
||||||
|
.change_context(EnvironmentVariableError)
|
||||||
|
.change_context(TracingInitializationError)?;
|
||||||
|
|
||||||
|
let max_logs = max_logs
|
||||||
|
.parse::<usize>()
|
||||||
|
.attach_printable(max_logs)
|
||||||
|
.attach_printable(Suggestion(
|
||||||
|
"a valid string is only numbers, for example 732",
|
||||||
|
))
|
||||||
|
.change_context(EnvironmentVariableError)
|
||||||
|
.attach_printable(env_vars::MAX_LOGS)
|
||||||
|
.change_context(TracingInitializationError)?;
|
||||||
|
|
||||||
|
let logs_dir_writer = RollingFileAppender::builder()
|
||||||
|
.rotation(Rotation::HOURLY)
|
||||||
|
.filename_prefix("trace")
|
||||||
|
.filename_suffix("log")
|
||||||
|
.max_log_files(max_logs)
|
||||||
|
.build(logs_dir)
|
||||||
|
.unwrap();
|
||||||
|
let (logs_dir_writer, logs_dir_guard) = tracing_appender::non_blocking(logs_dir_writer);
|
||||||
|
let logs_dir_writer = logs_dir_writer.with_max_level(Level::TRACE);
|
||||||
|
|
||||||
|
let stdio_writer = stderr_writer
|
||||||
|
.with_max_level(Level::WARN)
|
||||||
|
.or_else(stdout_writer);
|
||||||
|
|
||||||
|
let stdio_layer = tracing_subscriber::fmt::layer()
|
||||||
|
.with_writer(stdio_writer)
|
||||||
|
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||||
|
.pretty()
|
||||||
|
.with_filter(
|
||||||
|
EnvFilter::builder()
|
||||||
|
.with_default_directive(Level::INFO.into())
|
||||||
|
.from_env()
|
||||||
|
.change_context(EnvironmentVariableError)
|
||||||
|
.attach_printable(env_vars::RUST_LOG)
|
||||||
|
.change_context(TracingInitializationError)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let logs_dir_layer = tracing_subscriber::fmt::layer()
|
||||||
|
.with_writer(logs_dir_writer)
|
||||||
|
.with_span_events(FmtSpan::FULL)
|
||||||
|
.with_ansi(false);
|
||||||
|
|
||||||
|
let registry = Registry::default().with(stdio_layer).with(logs_dir_layer);
|
||||||
|
|
||||||
|
registry
|
||||||
|
.try_init()
|
||||||
|
.change_context(TracingInitializationError)?;
|
||||||
|
|
||||||
|
Ok(Guards(stderr_guard, stdout_guard, logs_dir_guard))
|
||||||
|
}
|
30
src/main.rs
Normal file
30
src/main.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use std::net::SocketAddrV4;
|
||||||
|
|
||||||
|
use custom_backend_http::{create_app, init_tracing};
|
||||||
|
use error_stack::{Report, ResultExt};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("application error")]
|
||||||
|
struct AppError;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Report<AppError>> {
|
||||||
|
let guards = init_tracing().change_context(AppError)?;
|
||||||
|
|
||||||
|
let app = create_app().await.change_context(AppError)?;
|
||||||
|
|
||||||
|
let ip = [0, 0, 0, 0].into();
|
||||||
|
let port = 3000;
|
||||||
|
|
||||||
|
let addr = SocketAddrV4::new(ip, port);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr).await.unwrap();
|
||||||
|
|
||||||
|
tracing::info!(?addr, "app listening");
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
|
drop(guards);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
11
src/routes/mod.rs
Normal file
11
src/routes/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use axum::Router;
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
mod notifications;
|
||||||
|
|
||||||
|
const NOTIFICATIONS: &str = "/notifications";
|
||||||
|
|
||||||
|
pub fn create_router() -> Router<AppState> {
|
||||||
|
Router::new().nest(NOTIFICATIONS, notifications::create_router())
|
||||||
|
}
|
17
src/routes/notifications/mod.rs
Normal file
17
src/routes/notifications/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use axum::{extract::State, response::IntoResponse, Router};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace")]
|
||||||
|
async fn get(state: State<AppState>) -> impl IntoResponse {
|
||||||
|
"Hi, getter!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace")]
|
||||||
|
async fn post(state: State<AppState>) -> impl IntoResponse {
|
||||||
|
"Hi, poster!"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_router() -> Router<AppState> {
|
||||||
|
Router::new().route("/", axum::routing::get(get).post(post))
|
||||||
|
}
|
Reference in New Issue
Block a user