brainos_httpadapter/
validate.rs1use axum::http::StatusCode;
11
12pub const MAX_CONTENT_BYTES: usize = 256 * 1024;
15pub const MAX_QUERY_BYTES: usize = 16 * 1024;
18pub const MAX_IDENT_BYTES: usize = 256;
21pub const MAX_TOP_K: usize = 1000;
25
26fn bad(reason: impl Into<String>) -> (StatusCode, String) {
27 (StatusCode::BAD_REQUEST, reason.into())
28}
29
30pub fn check_content(content: &str) -> Result<(), (StatusCode, String)> {
32 if content.is_empty() {
33 return Err(bad("content must not be empty"));
34 }
35 if content.len() > MAX_CONTENT_BYTES {
36 return Err(bad(format!(
37 "content exceeds {MAX_CONTENT_BYTES} bytes ({} given)",
38 content.len()
39 )));
40 }
41 Ok(())
42}
43
44pub fn check_query(q: &str) -> Result<(), (StatusCode, String)> {
46 if q.is_empty() {
47 return Err(bad("query must not be empty"));
48 }
49 if q.len() > MAX_QUERY_BYTES {
50 return Err(bad(format!(
51 "query exceeds {MAX_QUERY_BYTES} bytes ({} given)",
52 q.len()
53 )));
54 }
55 Ok(())
56}
57
58pub fn check_short_ident(name: &str, value: &str) -> Result<(), (StatusCode, String)> {
62 if value.is_empty() {
63 return Err(bad(format!("{name} must not be empty")));
64 }
65 if value.len() > MAX_IDENT_BYTES {
66 return Err(bad(format!(
67 "{name} exceeds {MAX_IDENT_BYTES} bytes ({} given)",
68 value.len()
69 )));
70 }
71 if value.bytes().any(|b| b.is_ascii_control()) {
72 return Err(bad(format!("{name} contains control characters")));
73 }
74 Ok(())
75}
76
77pub fn check_top_k(top_k: Option<usize>) -> Result<(), (StatusCode, String)> {
80 match top_k {
81 None => Ok(()),
82 Some(0) => Err(bad("top_k must be ≥ 1")),
83 Some(k) if k > MAX_TOP_K => Err(bad(format!("top_k exceeds {MAX_TOP_K} ({k} given)"))),
84 Some(_) => Ok(()),
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn content_bounds() {
94 assert!(check_content("").is_err());
95 assert!(check_content("hi").is_ok());
96 let big = "x".repeat(MAX_CONTENT_BYTES + 1);
97 assert!(check_content(&big).is_err());
98 }
99
100 #[test]
101 fn query_bounds() {
102 assert!(check_query("").is_err());
103 assert!(check_query("rust").is_ok());
104 let big = "q".repeat(MAX_QUERY_BYTES + 1);
105 assert!(check_query(&big).is_err());
106 }
107
108 #[test]
109 fn short_ident_rejects_control_chars() {
110 assert!(check_short_ident("namespace", "personal").is_ok());
111 assert!(check_short_ident("namespace", "").is_err());
112 assert!(check_short_ident("namespace", "bad\nname").is_err());
113 let big = "n".repeat(MAX_IDENT_BYTES + 1);
114 assert!(check_short_ident("namespace", &big).is_err());
115 }
116
117 #[test]
118 fn top_k_bounds() {
119 assert!(check_top_k(None).is_ok());
120 assert!(check_top_k(Some(0)).is_err());
121 assert!(check_top_k(Some(10)).is_ok());
122 assert!(check_top_k(Some(MAX_TOP_K)).is_ok());
123 assert!(check_top_k(Some(MAX_TOP_K + 1)).is_err());
124 }
125}