diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 56 | ||||
| -rw-r--r-- | src/clock.rs | 12 | ||||
| -rw-r--r-- | src/error.rs | 2 | ||||
| -rw-r--r-- | src/events/extract.rs | 4 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/login/extract.rs | 20 | ||||
| -rw-r--r-- | src/login/repo/auth.rs | 6 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/repo/channel.rs | 3 | ||||
| -rw-r--r-- | src/repo/login/store.rs | 2 | ||||
| -rw-r--r-- | src/repo/message.rs | 2 | ||||
| -rw-r--r-- | src/repo/token.rs | 16 |
12 files changed, 89 insertions, 38 deletions
@@ -1,3 +1,8 @@ +//! The `hi` command-line interface. +//! +//! This module supports running `hi` as a freestanding program, via the +//! [`Args`] struct. + use std::io; use axum::{middleware, Router}; @@ -7,22 +12,62 @@ use tokio::net; use crate::{app::App, channel, clock, events, login, repo::pool}; -pub type Result<T> = std::result::Result<T, Error>; - +/// Command-line entry point for running the `hi` server. +/// +/// This is intended to be used as a Clap [Parser], to capture command-line +/// arguments for the `hi` server: +/// +/// ```no_run +/// # use hi::cli::Error; +/// # +/// # #[tokio::main] +/// # async fn main() -> Result<(), Error> { +/// use clap::Parser; +/// use hi::cli::Args; +/// +/// let args = Args::parse(); +/// args.run().await?; +/// # Ok(()) +/// # } +/// ``` #[derive(Parser)] +#[command( + about = "Run the `hi` server.", + long_about = r#"Run the `hi` server. + +The database at `--database-url` will be created, or upgraded, automatically."# +)] pub struct Args { + /// The network address `hi` should listen on #[arg(short, long, env, default_value = "localhost")] address: String, + /// The network port `hi` should listen on #[arg(short, long, env, default_value_t = 64209)] port: u16, + /// Sqlite URL or path for the `hi` database #[arg(short, long, env, default_value = "sqlite://.hi")] database_url: String, } impl Args { - pub async fn run(self) -> Result<()> { + /// Runs the `hi` server, using the parsed configuation in `self`. + /// + /// This will perform the following tasks: + /// + /// * Migrate the `hi` database (at `--database-url`). + /// * Start an HTTP server (on the interface and port controlled by + /// `--address` and `--port`). + /// * Print a status message. + /// * Wait for that server to shut down. + /// + /// # Errors + /// + /// Will return `Err` if the server is unable to start, or terminates + /// prematurely. The specific [`Error`] variant will expose the cause + /// of the failure. + pub async fn run(self) -> Result<(), Error> { let pool = self.pool().await?; let app = App::from(pool).await?; @@ -66,10 +111,15 @@ fn started_msg(listener: &net::TcpListener) -> io::Result<String> { Ok(format!("listening on http://{local_addr}/")) } +/// Errors that can be raised by [`Args::run`]. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub enum Error { + /// Failure due to `io::Error`. See [`io::Error`]. IoError(#[from] io::Error), + /// Failure due to a database error. See [`sqlx::Error`]. DatabaseError(#[from] sqlx::Error), + /// Failure due to a database migration error. See + /// [`sqlx::migrate::MigrateError`]. MigrateError(#[from] sqlx::migrate::MigrateError), } diff --git a/src/clock.rs b/src/clock.rs index d162fc0..81ecede 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -8,10 +8,10 @@ use chrono::Utc; pub type DateTime = chrono::DateTime<chrono::Utc>; -/// Extractor that provides the "current time" for a request. This time is calculated -/// once per request, even if the extractor is used in multiple places. This requires -/// the [middleware] function to be installed with [axum::middleware::from_fn] around -/// the current route. +// Extractor that provides the "current time" for a request. This time is calculated +// once per request, even if the extractor is used in multiple places. This requires +// the [middleware] function to be installed with [axum::middleware::from_fn] around +// the current route. #[derive(Clone)] pub struct RequestedAt(pub DateTime); @@ -53,8 +53,8 @@ impl std::ops::Deref for RequestedAt { } } -/// Computes a canonical "requested at" time for each request it wraps. This -/// time can be recovered using the [RequestedAt] extractor. +// Computes a canonical "requested at" time for each request it wraps. This +// time can be recovered using the [RequestedAt] extractor. pub async fn middleware(mut req: Request, next: Next) -> Result<Response, StatusCode> { let now = RequestedAt::now(); req.extensions_mut().insert(now); diff --git a/src/error.rs b/src/error.rs index e2128d3..ffc90e9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,7 +40,7 @@ impl IntoResponse for InternalError { } } -/// Transient identifier for an InternalError. Prefixed with `E`. +// Transient identifier for an InternalError. Prefixed with `E`. #[derive(Debug)] pub struct Id(BaseId); diff --git a/src/events/extract.rs b/src/events/extract.rs index 683c1f9..44bcc71 100644 --- a/src/events/extract.rs +++ b/src/events/extract.rs @@ -7,8 +7,8 @@ use axum::{ use axum_extra::typed_header::TypedHeader; use serde::{de::DeserializeOwned, Serialize}; -/// A typed header. When used as a bare extractor, reads from the -/// `Last-Event-Id` HTTP header. +// A typed header. When used as a bare extractor, reads from the +// `Last-Event-Id` HTTP header. pub struct LastEventId<T>(pub T); static LAST_EVENT_ID: HeaderName = HeaderName::from_static("last-event-id"); @@ -1,3 +1,5 @@ +//! `hi` - a web-based, self-hosted chat system. + mod app; mod channel; pub mod cli; diff --git a/src/login/extract.rs b/src/login/extract.rs index bda55cd..542f879 100644 --- a/src/login/extract.rs +++ b/src/login/extract.rs @@ -13,7 +13,7 @@ pub struct IdentityToken { } impl IdentityToken { - /// Creates a new, unpopulated identity token store. + // Creates a new, unpopulated identity token store. #[cfg(test)] pub fn new() -> Self { Self { @@ -21,17 +21,17 @@ impl IdentityToken { } } - /// Get the identity secret sent in the request, if any. If the identity - /// was not sent, or if it has previously been [clear]ed, then this will - /// return [None]. If the identity has previously been [set], then this - /// will return that secret, regardless of what the request originally - /// included. + // Get the identity secret sent in the request, if any. If the identity + // was not sent, or if it has previously been [clear]ed, then this will + // return [None]. If the identity has previously been [set], then this + // will return that secret, regardless of what the request originally + // included. pub fn secret(&self) -> Option<&str> { self.cookies.get(IDENTITY_COOKIE).map(Cookie::value) } - /// Positively set the identity secret, and ensure that it will be sent - /// back to the client when this extractor is included in a response. + // Positively set the identity secret, and ensure that it will be sent + // back to the client when this extractor is included in a response. pub fn set(self, secret: &str) -> Self { let identity_cookie = Cookie::build((IDENTITY_COOKIE, String::from(secret))) .http_only(true) @@ -43,8 +43,8 @@ impl IdentityToken { } } - /// Remove the identity secret and ensure that it will be cleared when this - /// extractor is included in a response. + // Remove the identity secret and ensure that it will be cleared when this + // extractor is included in a response. pub fn clear(self) -> Self { Self { cookies: self.cookies.remove(IDENTITY_COOKIE), diff --git a/src/login/repo/auth.rs b/src/login/repo/auth.rs index 78b44f0..3033c8f 100644 --- a/src/login/repo/auth.rs +++ b/src/login/repo/auth.rs @@ -18,9 +18,9 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Auth<'t>(&'t mut SqliteConnection); impl<'t> Auth<'t> { - /// Retrieves a login by name, plus its stored password hash for - /// verification. If there's no login with the requested name, this will - /// return [None]. + // Retrieves a login by name, plus its stored password hash for + // verification. If there's no login with the requested name, this will + // return [None]. pub async fn for_name( &mut self, name: &str, diff --git a/src/main.rs b/src/main.rs index d199a74..d0830ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use clap::Parser; use hi::cli; #[tokio::main] -async fn main() -> cli::Result<()> { +async fn main() -> Result<(), cli::Error> { let args = cli::Args::parse(); args.run().await } diff --git a/src/repo/channel.rs b/src/repo/channel.rs index da63b45..0186413 100644 --- a/src/repo/channel.rs +++ b/src/repo/channel.rs @@ -23,7 +23,6 @@ pub struct Channel { } impl<'c> Channels<'c> { - /// Create a new channel. pub async fn create(&mut self, name: &str) -> Result<Channel, sqlx::Error> { let id = Id::generate(); @@ -78,7 +77,7 @@ impl<'c> Channels<'c> { } } -/// Stable identifier for a [Channel]. Prefixed with `C`. +// Stable identifier for a [Channel]. Prefixed with `C`. #[derive( Clone, Debug, diff --git a/src/repo/login/store.rs b/src/repo/login/store.rs index 2f922d7..b485941 100644 --- a/src/repo/login/store.rs +++ b/src/repo/login/store.rs @@ -62,7 +62,7 @@ impl<'t> From<&'t mut SqliteConnection> for Logins<'t> { } } -/// Stable identifier for a [Login]. Prefixed with `L`. +// Stable identifier for a [Login]. Prefixed with `L`. #[derive(Clone, Debug, Eq, PartialEq, sqlx::Type, serde::Serialize)] #[sqlx(transparent)] pub struct Id(BaseId); diff --git a/src/repo/message.rs b/src/repo/message.rs index e331a4e..385b103 100644 --- a/src/repo/message.rs +++ b/src/repo/message.rs @@ -2,7 +2,7 @@ use std::fmt; use crate::id::Id as BaseId; -/// Stable identifier for a [Message]. Prefixed with `M`. +// Stable identifier for a [Message]. Prefixed with `M`. #[derive(Clone, Debug, Eq, Hash, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] #[sqlx(transparent)] #[serde(transparent)] diff --git a/src/repo/token.rs b/src/repo/token.rs index 4eaa6ea..8276bea 100644 --- a/src/repo/token.rs +++ b/src/repo/token.rs @@ -17,8 +17,8 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Tokens<'t>(&'t mut SqliteConnection); impl<'c> Tokens<'c> { - /// Issue a new token for an existing login. The issued_at timestamp will - /// be used to control expiry, until the token is actually used. + // Issue a new token for an existing login. The issued_at timestamp will + // be used to control expiry, until the token is actually used. pub async fn issue( &mut self, login: &Login, @@ -43,7 +43,7 @@ impl<'c> Tokens<'c> { Ok(secret) } - /// Revoke a token by its secret. + // Revoke a token by its secret. pub async fn revoke(&mut self, secret: &str) -> Result<(), sqlx::Error> { sqlx::query!( r#" @@ -60,8 +60,8 @@ impl<'c> Tokens<'c> { Ok(()) } - /// Expire and delete all tokens that haven't been used more recently than - /// ``expire_at``. + // Expire and delete all tokens that haven't been used more recently than + // `expire_at`. pub async fn expire(&mut self, expire_at: &DateTime) -> Result<(), sqlx::Error> { sqlx::query!( r#" @@ -77,9 +77,9 @@ impl<'c> Tokens<'c> { Ok(()) } - /// Validate a token by its secret, retrieving the associated Login record. - /// Will return [None] if the token is not valid. The token's last-used - /// timestamp will be set to `used_at`. + // Validate a token by its secret, retrieving the associated Login record. + // Will return [None] if the token is not valid. The token's last-used + // timestamp will be set to `used_at`. pub async fn validate( &mut self, secret: &str, |
