Skip to content

Commit

Permalink
Make use of NonZero
Browse files Browse the repository at this point in the history
Make clear to readers that the value 0 is not supported and has no
special meaning.

Also allows the compiler to improve enum layouts.
  • Loading branch information
cgzones authored and matze committed Sep 30, 2024
1 parent 38f551d commit ba69063
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 18 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Changed

- Use NonZero internally for data where 0 is not a valid value and
any special meaning in expressed via an outer Option.


## 2.5.0

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ run-time behavior:
* `WASTEBIN_MAX_BODY_SIZE` number of bytes to accept for POST requests. Defaults
to 1 MB.
* `WASTEBIN_MAX_PASTE_EXPIRATION` maximum allowed lifetime of a paste in
seconds. Defaults to unlimited.
seconds. Defaults to 0 meaning unlimited.
* `WASTEBIN_PASSWORD_SALT` salt used to hash user passwords used for encrypting
pastes.
* `WASTEBIN_SIGNING_KEY` sets the key to sign cookies. If not set, a random key
Expand Down
7 changes: 5 additions & 2 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub mod write {
use async_compression::tokio::bufread::ZstdEncoder;
use serde::{Deserialize, Serialize};
use std::io::Cursor;
use std::num::NonZeroU32;
use tokio::io::{AsyncReadExt, BufReader};

/// An uncompressed entry to be inserted into the database.
Expand All @@ -78,7 +79,7 @@ pub mod write {
/// File extension
pub extension: Option<String>,
/// Expiration in seconds from now
pub expires: Option<u32>,
pub expires: Option<NonZeroU32>,
/// Delete if read
pub burn_after_reading: Option<bool>,
/// User identifier that inserted the entry
Expand Down Expand Up @@ -364,6 +365,8 @@ impl Database {

#[cfg(test)]
mod tests {
use std::num::NonZero;

use super::*;

fn new_db() -> Result<Database, Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -399,7 +402,7 @@ mod tests {
let db = new_db()?;

let entry = write::Entry {
expires: Some(1),
expires: Some(NonZero::new(1).unwrap()),
..Default::default()
};

Expand Down
5 changes: 3 additions & 2 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{db, highlight};
use axum_extra::extract::cookie::Key;
use std::env::VarError;
use std::net::SocketAddr;
use std::num::{NonZeroUsize, ParseIntError};
use std::num::{NonZero, NonZeroU32, NonZeroUsize, ParseIntError};
use std::path::PathBuf;
use std::sync::LazyLock;
use std::time::Duration;
Expand Down Expand Up @@ -178,9 +178,10 @@ pub fn http_timeout() -> Result<Duration, Error> {
.map_err(Error::HttpTimeout)
}

pub fn max_paste_expiration() -> Result<Option<u32>, Error> {
pub fn max_paste_expiration() -> Result<Option<NonZeroU32>, Error> {
std::env::var(VAR_MAX_PASTE_EXPIRATION)
.ok()
.map(|value| value.parse::<u32>().map_err(Error::MaxPasteExpiration))
.transpose()
.map(|op| op.and_then(NonZero::new))
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::errors::Error;
use axum::extract::{DefaultBodyLimit, FromRef};
use axum::Router;
use axum_extra::extract::cookie::Key;
use std::num::NonZeroU32;
use std::process::ExitCode;
use std::time::Duration;
use tokio::net::TcpListener;
Expand Down Expand Up @@ -32,7 +33,7 @@ pub struct AppState {
cache: Cache,
key: Key,
base_url: Option<Url>,
max_expiration: Option<u32>,
max_expiration: Option<NonZeroU32>,
}

impl FromRef<AppState> for Key {
Expand Down
31 changes: 22 additions & 9 deletions src/pages.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::num::NonZero;
use std::num::NonZeroU32;

use crate::cache::Key as CacheKey;
use crate::env;
use crate::highlight::Html;
Expand Down Expand Up @@ -35,11 +38,11 @@ impl From<crate::Error> for ErrorResponse<'_> {
pub struct Index<'a> {
meta: &'a env::Metadata<'a>,
base_path: &'static env::BasePath,
max_expiration: Option<u32>,
max_expiration: Option<NonZeroU32>,
}

impl<'a> Index<'a> {
pub fn new(max_expiration: Option<u32>) -> Self {
pub fn new(max_expiration: Option<NonZeroU32>) -> Self {
Self {
meta: &env::METADATA,
base_path: &env::BASE_PATH,
Expand All @@ -52,7 +55,7 @@ impl<'a> Index<'a> {
enum Expiration {
None,
Burn,
Time(u32),
Time(NonZeroU32),
}

impl std::fmt::Display for Expiration {
Expand All @@ -65,14 +68,24 @@ impl std::fmt::Display for Expiration {
}
}

// TODO: replace once Option::expect is const (/~https://github.com/rust-lang/rust/issues/67441) to construct EXPIRATION_OPTIONS
macro_rules! nonzero {
($value:literal) => {
match NonZero::new($value) {
Some(v) => v,
None => unreachable!(),
}
};
}

const EXPIRATION_OPTIONS: [(&str, Expiration); 8] = [
("never", Expiration::None),
("10 minutes", Expiration::Time(600)),
("1 hour", Expiration::Time(3600)),
("1 day", Expiration::Time(86400)),
("1 week", Expiration::Time(604_800)),
("1 month", Expiration::Time(2_592_000)),
("1 year", Expiration::Time(31_536_000)),
("10 minutes", Expiration::Time(nonzero!(600))),
("1 hour", Expiration::Time(nonzero!(3600))),
("1 day", Expiration::Time(nonzero!(86400))),
("1 week", Expiration::Time(nonzero!(604_800))),
("1 month", Expiration::Time(nonzero!(2_592_000))),
("1 year", Expiration::Time(nonzero!(31_536_000))),
("🔥 after reading", Expiration::Burn),
];

Expand Down
6 changes: 4 additions & 2 deletions src/routes/form.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::num::NonZeroU32;

use crate::db::write;
use crate::env::BASE_PATH;
use crate::id::Id;
Expand All @@ -21,8 +23,8 @@ impl From<Entry> for write::Entry {
let burn_after_reading = Some(entry.expires == "burn");
let password = (!entry.password.is_empty()).then_some(entry.password);

let expires = match entry.expires.parse::<u32>() {
Ok(0) | Err(_) => None,
let expires = match entry.expires.parse::<NonZeroU32>() {
Err(_) => None,
Ok(secs) => Some(secs),
};

Expand Down
4 changes: 3 additions & 1 deletion src/routes/json.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::num::NonZeroU32;

use crate::db::write;
use crate::env::BASE_PATH;
use crate::errors::{Error, JsonErrorResponse};
Expand All @@ -12,7 +14,7 @@ use serde::{Deserialize, Serialize};
pub struct Entry {
pub text: String,
pub extension: Option<String>,
pub expires: Option<u32>,
pub expires: Option<NonZeroU32>,
pub burn_after_reading: Option<bool>,
pub password: Option<String>,
}
Expand Down

0 comments on commit ba69063

Please sign in to comment.