🚀 meta: initial commit

This commit is contained in:
2023-11-29 12:34:54 -05:00
commit 5e5be16ee0
8 changed files with 1442 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1190
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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())
}

View 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))
}