use crate::http::problem::{InvalidParam, Problem};
use crate::search::MAX_AUTOCOMPLETE_LIMIT;
pub(crate) const MAX_QUERY_CHARS: usize = 256;
pub(crate) const MAX_SEARCH_LIMIT: usize = 50;
pub(crate) fn validate_query_length(raw: &str) -> Result<String, Problem> {
let query = raw.trim();
if query.chars().count() > MAX_QUERY_CHARS {
return Err(Problem::invalid_query(format!(
"q must be at most {MAX_QUERY_CHARS} characters"
)));
}
Ok(query.to_string())
}
pub(crate) fn validate_query(raw: &str) -> Result<String, Problem> {
let query = validate_query_length(raw)?;
if query.is_empty() {
return Err(Problem::invalid_query("q must not be empty"));
}
Ok(query)
}
pub(crate) fn validate_search_limit(limit: usize) -> Result<(), Problem> {
if limit > MAX_SEARCH_LIMIT {
return Err(Problem::limit_too_large(vec![InvalidParam::new(
"limit",
format!("must be at most {MAX_SEARCH_LIMIT}"),
)]));
}
Ok(())
}
pub(crate) fn validate_autocomplete_limit(limit: usize) -> Result<(), Problem> {
if limit > MAX_AUTOCOMPLETE_LIMIT {
return Err(Problem::limit_too_large(vec![InvalidParam::new(
"limit",
format!("must be at most {MAX_AUTOCOMPLETE_LIMIT}"),
)]));
}
Ok(())
}
pub(crate) fn validate_coords(lon: f64, lat: f64) -> Result<(), Problem> {
let mut invalid = Vec::new();
if !(-180.0..=180.0).contains(&lon) {
invalid.push(InvalidParam::new("lon", "must be within [-180, 180]"));
}
if !(-90.0..=90.0).contains(&lat) {
invalid.push(InvalidParam::new("lat", "must be within [-90, 90]"));
}
if invalid.is_empty() {
Ok(())
} else {
Err(Problem::invalid_coordinate(invalid))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty_and_over_length_queries() {
assert!(validate_query(" ").is_err());
let long = "a".repeat(MAX_QUERY_CHARS + 1);
assert!(validate_query(&long).is_err());
assert_eq!(validate_query(" king street ").unwrap(), "king street");
}
#[test]
fn query_length_counts_chars_not_bytes() {
let at_limit = "é".repeat(MAX_QUERY_CHARS);
assert!(validate_query(&at_limit).is_ok());
let over = "é".repeat(MAX_QUERY_CHARS + 1);
assert!(validate_query(&over).is_err());
}
#[test]
fn search_limit_boundary() {
assert!(validate_search_limit(MAX_SEARCH_LIMIT).is_ok());
let problem = validate_search_limit(MAX_SEARCH_LIMIT + 1).unwrap_err();
assert_eq!(problem.status(), 400);
assert_eq!(problem.invalid_params()[0].name, "limit");
}
#[test]
fn coordinate_ranges() {
assert!(validate_coords(-180.0, -90.0).is_ok());
assert!(validate_coords(180.0, 90.0).is_ok());
let lon_bad = validate_coords(999.0, 0.0).unwrap_err();
assert_eq!(lon_bad.invalid_params()[0].name, "lon");
let lat_bad = validate_coords(0.0, 999.0).unwrap_err();
assert_eq!(lat_bad.invalid_params()[0].name, "lat");
assert!(validate_coords(f64::NAN, 0.0).is_err());
assert!(validate_coords(0.0, f64::INFINITY).is_err());
}
}