use axum::http::StatusCode;
use serde::Deserialize;
use crate::webadmin::error::AdminError;
use acme_proxy_store::status::UnknownStatus;
pub(crate) fn bad_status(error: UnknownStatus) -> AdminError {
AdminError::with_code(StatusCode::BAD_REQUEST, "invalid_status", error.to_string())
}
pub(crate) fn non_empty(raw: &str) -> Option<String> {
let trimmed = raw.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}
pub(crate) fn empty_is_absent<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = Option::<String>::deserialize(deserializer)?;
Ok(raw.as_deref().and_then(non_empty))
}
pub(crate) fn empty_is_absent_serial<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(empty_is_absent(deserializer)?.map(|value| acme_proxy_core::cert::normalize_serial(&value)))
}
#[cfg(test)]
mod tests {
use super::*;
use axum::extract::Query;
use axum::http::Uri;
#[derive(Debug, Deserialize)]
struct Filters {
#[serde(default, deserialize_with = "empty_is_absent")]
profile: Option<String>,
}
#[derive(Debug, Deserialize)]
struct SerialFilter {
#[serde(
rename = "certSerial",
default,
deserialize_with = "empty_is_absent_serial"
)]
cert_serial: Option<String>,
}
#[test]
fn a_pasted_serial_is_folded_to_the_stored_form() {
let parse = |query: &str| {
let uri: Uri = format!("/api/orders{query}").parse().expect("a valid URI");
Query::<SerialFilter>::try_from_uri(&uri)
.expect("the query string parses")
.0
.cert_serial
};
assert_eq!(parse("?certSerial=0A1B2C"), Some("0a1b2c".to_string()));
assert_eq!(parse("?certSerial=0a:1b:2c"), Some("0a1b2c".to_string()));
assert_eq!(parse("?certSerial=0a1b2c"), Some("0a1b2c".to_string()));
assert_eq!(parse("?certSerial="), None);
assert_eq!(parse(""), None);
assert_eq!(parse("?certSerial=zzz"), Some("zzz".to_string()));
}
fn parse(query: &str) -> Option<String> {
let uri: Uri = format!("/ui/expiring{query}").parse().expect("a valid URI");
Query::<Filters>::try_from_uri(&uri)
.expect("the query string parses")
.0
.profile
}
#[test]
fn a_blank_filter_is_absent_and_a_named_one_survives_trimmed() {
assert_eq!(parse(""), None, "an omitted key is the unfiltered load");
assert_eq!(parse("?profile="), None, "what the form actually submits");
assert_eq!(parse("?profile=%20%20"), None, "spaces are still blank");
assert_eq!(parse("?profile=le"), Some("le".to_string()));
assert_eq!(parse("?profile=%20le%20"), Some("le".to_string()));
}
#[test]
fn non_empty_answers_for_the_same_four_shapes() {
assert_eq!(non_empty(""), None);
assert_eq!(non_empty(" "), None);
assert_eq!(non_empty("le"), Some("le".to_string()));
assert_eq!(non_empty(" le "), Some("le".to_string()));
}
}