From 9920fc62873aa6669d3efc6cbb002c2f59762aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Tue, 29 Aug 2023 21:34:00 +0200 Subject: [PATCH] refactor --- rust/Cargo.toml | 6 -- rust/Dockerfile | 8 +- rust/src/bin/adm.rs | 107 ------------------------ rust/src/error.rs | 123 +++++++++++++++++++-------- rust/src/lib.rs | 2 +- rust/src/main.rs | 199 +++++++++++++++++++++++++++++++++++--------- 6 files changed, 252 insertions(+), 193 deletions(-) delete mode 100644 rust/src/bin/adm.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3c8343e..3220a96 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,16 +3,10 @@ name = "packager" version = "0.1.0" edition = "2021" -default-run = "packager" - [[bin]] name = "packager" path = "src/main.rs" -[[bin]] -name = "packager-adm" -path = "src/bin/adm.rs" - [profile.dev] opt-level = 0 lto = "off" diff --git a/rust/Dockerfile b/rust/Dockerfile index 6f8f065..fb1ee92 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -6,4 +6,10 @@ COPY target/x86_64-unknown-linux-musl/release/packager /usr/local/bin/packager ENTRYPOINT ["tini", "--"] -CMD ["/usr/local/bin/packager", "--bind", "0.0.0.0", "--port", "3000", "--database-url", "/var/lib/packager/db/db.sqlite"] +CMD [ \ + "/usr/local/bin/packager", \ + "--database-url", "/var/lib/packager/db/db.sqlite", \ + "serve", \ + "--bind", "0.0.0.0", \ + "--port", "3000" \ +] diff --git a/rust/src/bin/adm.rs b/rust/src/bin/adm.rs deleted file mode 100644 index b0e0e7d..0000000 --- a/rust/src/bin/adm.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::fmt; -use std::process::exit; - -use clap::{Parser, Subcommand}; - -use packager::{models, sqlite, StartError}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(long)] - database_url: String, - - #[command(subcommand)] - command: Command, -} - -#[derive(Subcommand, Debug)] -enum Command { - #[command(subcommand)] - User(UserCommand), -} - -#[derive(Subcommand, Debug)] -enum UserCommand { - Create(UserCreate), -} - -#[derive(Parser, Debug)] -struct UserCreate { - #[arg(long)] - username: String, - #[arg(long)] - fullname: String, -} - -#[derive(Debug)] -enum Error { - Generic { message: String }, - UserExists { username: String }, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Generic { message } => write!(f, "{}", message), - Self::UserExists { username } => write!(f, "user \"{username}\" already exists"), - } - } -} - -impl From for Error { - fn from(starterror: StartError) -> Self { - Self::Generic { - message: starterror.to_string(), - } - } -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let args = Args::parse(); - - let database_pool = sqlite::init_database_pool(&args.database_url).await?; - - match args.command { - Command::User(cmd) => match cmd { - UserCommand::Create(user) => { - let id = match models::user::create( - &database_pool, - models::user::NewUser { - username: &user.username, - fullname: &user.fullname, - }, - ) - .await - { - Ok(id) => id, - Err(error) => { - if let models::Error::Query(models::QueryError::Duplicate { - description: _, - }) = error - { - println!( - "Error: {}", - Error::UserExists { - username: user.username, - } - .to_string() - ); - exit(1); - } - return Err(error.into()); - } - }; - println!( - "User \"{}\" created successfully (id {})", - user.username, id - ) - } - }, - } - - Ok(()) -} diff --git a/rust/src/error.rs b/rust/src/error.rs index dd5fd00..7067feb 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -17,6 +17,7 @@ pub enum RequestError { AuthenticationUserNotFound { username: String }, AuthenticationHeaderMissing, AuthenticationHeaderInvalid { message: String }, + Transport { inner: hyper::Error }, } impl std::error::Error for RequestError {} @@ -35,6 +36,9 @@ impl fmt::Display for RequestError { Self::AuthenticationHeaderInvalid { message } => { write!(f, "Authentication header invalid: {message}") } + Self::Transport { inner } => { + write!(f, "HTTP error: {inner}") + } } } } @@ -43,52 +47,19 @@ impl fmt::Display for RequestError { pub enum Error { Model(models::Error), Request(RequestError), + Start(StartError), + Command(CommandError), } impl std::error::Error for Error {} -#[derive(Debug)] -pub enum StartError { - DatabaseInitError { message: String }, - DatabaseMigrationError { message: String }, -} - -impl std::error::Error for StartError {} - -impl fmt::Display for StartError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::DatabaseInitError { message } => { - write!(f, "database initialization error: {message}") - } - Self::DatabaseMigrationError { message } => { - write!(f, "database migration error: {message}") - } - } - } -} - -impl From for StartError { - fn from(value: sqlx::Error) -> Self { - Self::DatabaseInitError { - message: value.to_string(), - } - } -} - -impl From for StartError { - fn from(value: sqlx::migrate::MigrateError) -> Self { - Self::DatabaseMigrationError { - message: value.to_string(), - } - } -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Model(model_error) => write!(f, "Model error: {model_error}"), Self::Request(request_error) => write!(f, "Request error: {request_error}"), + Self::Start(start_error) => write!(f, "{start_error}"), + Self::Command(command_error) => write!(f, "{command_error}"), } } } @@ -99,6 +70,18 @@ impl From for Error { } } +impl From for Error { + fn from(value: StartError) -> Self { + Self::Start(value) + } +} + +impl From for Error { + fn from(value: hyper::Error) -> Self { + Self::Request(RequestError::Transport { inner: value }) + } +} + impl IntoResponse for Error { fn into_response(self) -> Response { match self { @@ -146,8 +129,74 @@ impl IntoResponse for Error { StatusCode::UNAUTHORIZED, view::ErrorPage::build(&request_error.to_string()), ), + RequestError::Transport { inner } => ( + StatusCode::INTERNAL_SERVER_ERROR, + view::ErrorPage::build(&inner.to_string()), + ), }, + Self::Start(start_error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + view::ErrorPage::build(&start_error.to_string()), + ), + Self::Command(command_error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + view::ErrorPage::build(&command_error.to_string()), + ), } .into_response() } } + +#[derive(Debug)] +pub enum StartError { + DatabaseInitError { message: String }, + DatabaseMigrationError { message: String }, +} + +impl std::error::Error for StartError {} + +impl fmt::Display for StartError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::DatabaseInitError { message } => { + write!(f, "database initialization error: {message}") + } + Self::DatabaseMigrationError { message } => { + write!(f, "database migration error: {message}") + } + } + } +} + +impl From for StartError { + fn from(value: sqlx::Error) -> Self { + Self::DatabaseInitError { + message: value.to_string(), + } + } +} + +impl From for StartError { + fn from(value: sqlx::migrate::MigrateError) -> Self { + Self::DatabaseMigrationError { + message: value.to_string(), + } + } +} + +#[derive(Debug)] +pub enum CommandError { + UserExists { username: String }, +} + +impl std::error::Error for CommandError {} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::UserExists { username } => { + write!(f, "user \"{username}\" already exists") + } + } + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e44e69e..ee3dec8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -11,7 +11,7 @@ pub mod sqlite; mod view; -pub use error::{Error, RequestError, StartError}; +pub use error::{CommandError, Error, RequestError, StartError}; #[derive(Clone)] pub struct AppState { diff --git a/rust/src/main.rs b/rust/src/main.rs index 044655d..5b4bb33 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,15 +1,31 @@ -use packager::{auth, routing, sqlite, AppState, ClientState, StartError}; - use std::net::{IpAddr, SocketAddr}; +use std::process::ExitCode; use std::str::FromStr; -use clap::Parser; +use clap::{Parser, Subcommand}; + +use packager::{auth, models, routing, sqlite, AppState, ClientState, Error}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { #[arg(long)] database_url: String, + + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + Serve(Serve), + #[command(subcommand)] + Admin(Admin), + Migrate, +} + +#[derive(Parser, Debug)] +struct Serve { #[arg(long, default_value_t = 3000)] port: u16, #[arg(long)] @@ -18,42 +34,143 @@ struct Args { disable_auth_and_assume_user: Option, } -#[tokio::main] -async fn main() -> Result<(), StartError> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) - .init(); - - let args = Args::parse(); - - let database_pool = sqlite::init_database_pool(&args.database_url).await?; - sqlite::migrate(&database_pool).await?; - - let state = AppState { - database_pool, - client_state: ClientState::new(), - auth_config: if let Some(assume_user) = args.disable_auth_and_assume_user { - auth::AuthConfig::Disabled { assume_user } - } else { - auth::AuthConfig::Enabled - }, - }; - - // build our application with a route - let app = routing::router(state); - let addr = SocketAddr::from(( - IpAddr::from_str(&args.bind) - .map_err(|error| format!("error parsing bind address {}: {}", &args.bind, error)) - .unwrap(), - args.port, - )); - tracing::debug!("listening on {}", addr); - axum::Server::try_bind(&addr) - .map_err(|error| format!("error binding to {}: {}", addr, error)) - .unwrap() - .serve(app.into_make_service()) - .await - .unwrap(); - - Ok(()) +#[derive(Subcommand, Debug)] +enum Admin { + #[command(subcommand)] + User(UserCommand), +} + +#[derive(Subcommand, Debug)] +enum UserCommand { + Create(UserCreate), +} + +#[derive(Parser, Debug)] +struct UserCreate { + #[arg(long)] + username: String, + #[arg(long)] + fullname: String, +} + +struct MainResult(Result<(), Error>); + +impl std::process::Termination for MainResult { + fn report(self) -> std::process::ExitCode { + match self.0 { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("Error: {e}"); + ExitCode::FAILURE + } + } + } +} + +impl From for MainResult { + fn from(error: Error) -> Self { + Self(Err(error)) + } +} + +#[tokio::main] +async fn main() -> MainResult { + let args = Args::parse(); + match args.command { + Command::Serve(serve_args) => { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); + + let database_pool = match sqlite::init_database_pool(&args.database_url).await { + Ok(pool) => pool, + Err(e) => return <_ as Into>::into(e).into(), + }; + + if let Err(e) = sqlite::migrate(&database_pool).await { + return <_ as Into>::into(e).into(); + } + + let state = AppState { + database_pool, + client_state: ClientState::new(), + auth_config: if let Some(assume_user) = serve_args.disable_auth_and_assume_user { + auth::AuthConfig::Disabled { assume_user } + } else { + auth::AuthConfig::Enabled + }, + }; + + // build our application with a route + let app = routing::router(state); + let addr = SocketAddr::from(( + IpAddr::from_str(&serve_args.bind) + .map_err(|error| { + format!("error parsing bind address {}: {}", &serve_args.bind, error) + }) + .unwrap(), + serve_args.port, + )); + tracing::debug!("listening on {}", addr); + if let Err(e) = axum::Server::try_bind(&addr) + .map_err(|error| format!("error binding to {}: {}", addr, error)) + .unwrap() + .serve(app.into_make_service()) + .await + { + return >::into(e).into(); + } + } + Command::Admin(admin_command) => match admin_command { + Admin::User(cmd) => match cmd { + UserCommand::Create(user) => { + let database_pool = match sqlite::init_database_pool(&args.database_url).await { + Ok(pool) => pool, + Err(e) => return <_ as Into>::into(e).into(), + }; + + let id = match models::user::create( + &database_pool, + models::user::NewUser { + username: &user.username, + fullname: &user.fullname, + }, + ) + .await + .map_err(|error| match error { + models::Error::Query(models::QueryError::Duplicate { description: _ }) => { + Error::Command(packager::CommandError::UserExists { + username: user.username.clone(), + }) + } + _ => Error::Model(error), + }) { + Ok(id) => id, + Err(e) => { + return e.into(); + } + }; + + println!( + "User \"{}\" created successfully (id {})", + &user.username, id + ) + } + }, + }, + Command::Migrate => { + let database_pool = match sqlite::init_database_pool(&args.database_url).await { + Ok(pool) => pool, + Err(e) => return <_ as Into>::into(e).into(), + }; + + if let Err(e) = sqlite::migrate(&database_pool).await { + return <_ as Into>::into(e).into(); + } + + println!("Migrations successfully applied"); + } + } + + MainResult(Ok(())) }