1use crate::error::{Error, Result};
7
8pub const MAX_QUERY_LENGTH: usize = 1_000_000; pub const MAX_PARAM_NAME_LENGTH: usize = 128;
13
14pub const MAX_HOSTNAME_LENGTH: usize = 253;
16
17pub const MIN_FRAME_BYTES: usize = 1;
19
20pub const MIN_JSON_DEPTH: usize = 1;
22
23pub fn query(q: &str) -> Result<()> {
39 if q.is_empty() {
40 return Err(Error::validation("Query cannot be empty"));
41 }
42
43 if q.len() > MAX_QUERY_LENGTH {
44 return Err(Error::validation(format!(
45 "Query exceeds maximum length of {} bytes",
46 MAX_QUERY_LENGTH
47 )));
48 }
49
50 if q.contains('\0') {
52 return Err(Error::validation("Query contains invalid null character"));
53 }
54
55 Ok(())
56}
57
58pub fn param_name(name: &str) -> Result<()> {
77 if name.is_empty() {
78 return Err(Error::validation("Parameter name cannot be empty"));
79 }
80
81 if name.len() > MAX_PARAM_NAME_LENGTH {
82 return Err(Error::validation(format!(
83 "Parameter name exceeds maximum length of {} characters",
84 MAX_PARAM_NAME_LENGTH
85 )));
86 }
87
88 let mut chars = name.chars();
89
90 match chars.next() {
92 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
93 Some(c) => {
94 return Err(Error::validation(format!(
95 "Parameter name must start with a letter or underscore, found '{}'",
96 c
97 )));
98 }
99 None => unreachable!(), }
101
102 for c in chars {
104 if !c.is_ascii_alphanumeric() && c != '_' {
105 return Err(Error::validation(format!(
106 "Parameter name contains invalid character '{}'",
107 c
108 )));
109 }
110 }
111
112 Ok(())
113}
114
115pub fn hostname(host: &str) -> Result<()> {
137 if host.is_empty() {
138 return Err(Error::validation("Hostname cannot be empty"));
139 }
140
141 if host.len() > MAX_HOSTNAME_LENGTH {
142 return Err(Error::validation(format!(
143 "Hostname exceeds maximum length of {} characters",
144 MAX_HOSTNAME_LENGTH
145 )));
146 }
147
148 if host.starts_with('[') && host.ends_with(']') {
150 let ipv6 = &host[1..host.len() - 1];
151 return validate_ipv6(ipv6);
152 }
153
154 if host.contains(':') && !host.contains('.') {
156 return validate_ipv6(host);
157 }
158
159 if host.chars().all(|c| c.is_ascii_digit() || c == '.') {
161 return validate_ipv4(host);
162 }
163
164 validate_hostname_labels(host)
166}
167
168fn validate_ipv4(addr: &str) -> Result<()> {
169 let parts: Vec<&str> = addr.split('.').collect();
170 if parts.len() != 4 {
171 return Err(Error::validation("Invalid IPv4 address format"));
172 }
173
174 for part in parts {
175 match part.parse::<u8>() {
176 Ok(_) => {}
177 Err(_) => {
178 return Err(Error::validation(format!("Invalid IPv4 octet: {}", part)));
179 }
180 }
181 }
182
183 Ok(())
184}
185
186fn validate_ipv6(addr: &str) -> Result<()> {
187 for c in addr.chars() {
189 if !c.is_ascii_hexdigit() && c != ':' {
190 return Err(Error::validation(format!(
191 "Invalid character in IPv6 address: {}",
192 c
193 )));
194 }
195 }
196
197 if !addr.contains(':') {
199 return Err(Error::validation("Invalid IPv6 address format"));
200 }
201
202 Ok(())
203}
204
205fn validate_hostname_labels(host: &str) -> Result<()> {
206 let labels: Vec<&str> = host.split('.').collect();
207
208 for label in labels {
209 if label.is_empty() {
210 return Err(Error::validation("Hostname contains empty label"));
211 }
212
213 if label.len() > 63 {
214 return Err(Error::validation(format!(
215 "Hostname label '{}' exceeds 63 characters",
216 label
217 )));
218 }
219
220 if label.starts_with('-') || label.ends_with('-') {
221 return Err(Error::validation(format!(
222 "Hostname label '{}' cannot start or end with hyphen",
223 label
224 )));
225 }
226
227 for c in label.chars() {
228 if !c.is_ascii_alphanumeric() && c != '-' {
229 return Err(Error::validation(format!(
230 "Hostname contains invalid character '{}'",
231 c
232 )));
233 }
234 }
235 }
236
237 Ok(())
238}
239
240pub fn port(p: u16) -> Result<()> {
256 if p == 0 {
257 return Err(Error::validation("Port 0 is reserved and cannot be used"));
258 }
259 Ok(())
260}
261
262pub fn page_size(size: usize) -> Result<()> {
278 if size == 0 {
279 return Err(Error::validation("Page size must be at least 1"));
280 }
281
282 if size > 100_000 {
283 return Err(Error::validation("Page size cannot exceed 100,000 rows"));
284 }
285
286 Ok(())
287}
288
289pub fn max_frame_bytes(size: usize) -> Result<()> {
294 if size < MIN_FRAME_BYTES {
295 return Err(Error::validation("Max frame size must be at least 1 byte"));
296 }
297 Ok(())
298}
299
300pub fn max_rows(rows: usize) -> Result<()> {
305 if rows == 0 {
306 return Err(Error::validation("Max rows must be at least 1"));
307 }
308 Ok(())
309}
310
311pub fn max_pages(pages: usize) -> Result<()> {
316 if pages == 0 {
317 return Err(Error::validation("Max pages must be at least 1"));
318 }
319 Ok(())
320}
321
322pub fn max_json_depth(depth: usize) -> Result<()> {
327 if depth < MIN_JSON_DEPTH {
328 return Err(Error::validation("Max JSON depth must be at least 1"));
329 }
330 Ok(())
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
340 fn test_query_valid() {
341 assert!(query("MATCH (n) RETURN n").is_ok());
342 assert!(query("RETURN 1").is_ok());
343 assert!(query("CREATE (n:Person {name: 'Alice'})").is_ok());
344 }
345
346 #[test]
347 fn test_query_empty() {
348 let result = query("");
349 assert!(result.is_err());
350 assert!(result.unwrap_err().to_string().contains("empty"));
351 }
352
353 #[test]
354 fn test_query_too_long() {
355 let long_query = "x".repeat(MAX_QUERY_LENGTH + 1);
356 let result = query(&long_query);
357 assert!(result.is_err());
358 assert!(result.unwrap_err().to_string().contains("maximum length"));
359 }
360
361 #[test]
362 fn test_query_with_null() {
363 let result = query("RETURN \0 AS x");
364 assert!(result.is_err());
365 assert!(result.unwrap_err().to_string().contains("null"));
366 }
367
368 #[test]
369 fn test_query_unicode() {
370 assert!(query("RETURN '日本語' AS text").is_ok());
371 assert!(query("CREATE (n {emoji: '🚀'})").is_ok());
372 }
373
374 #[test]
375 fn test_query_whitespace_only() {
376 assert!(query(" ").is_ok());
378 }
379
380 #[test]
383 fn test_param_name_valid() {
384 assert!(param_name("user_id").is_ok());
385 assert!(param_name("_private").is_ok());
386 assert!(param_name("x").is_ok());
387 assert!(param_name("userName123").is_ok());
388 assert!(param_name("_").is_ok());
389 assert!(param_name("__double__").is_ok());
390 }
391
392 #[test]
393 fn test_param_name_empty() {
394 let result = param_name("");
395 assert!(result.is_err());
396 assert!(result.unwrap_err().to_string().contains("empty"));
397 }
398
399 #[test]
400 fn test_param_name_starts_with_digit() {
401 let result = param_name("123invalid");
402 assert!(result.is_err());
403 assert!(result.unwrap_err().to_string().contains("start with"));
404 }
405
406 #[test]
407 fn test_param_name_invalid_chars() {
408 assert!(param_name("user-id").is_err()); assert!(param_name("user.id").is_err()); assert!(param_name("user id").is_err()); assert!(param_name("user@id").is_err()); }
413
414 #[test]
415 fn test_param_name_too_long() {
416 let long_name = "a".repeat(MAX_PARAM_NAME_LENGTH + 1);
417 let result = param_name(&long_name);
418 assert!(result.is_err());
419 assert!(result.unwrap_err().to_string().contains("maximum length"));
420 }
421
422 #[test]
425 fn test_hostname_valid() {
426 assert!(hostname("localhost").is_ok());
427 assert!(hostname("geode.example.com").is_ok());
428 assert!(hostname("my-server").is_ok());
429 assert!(hostname("server1").is_ok());
430 assert!(hostname("a.b.c.d.e").is_ok());
431 }
432
433 #[test]
434 fn test_hostname_empty() {
435 let result = hostname("");
436 assert!(result.is_err());
437 assert!(result.unwrap_err().to_string().contains("empty"));
438 }
439
440 #[test]
441 fn test_hostname_ipv4() {
442 assert!(hostname("192.168.1.1").is_ok());
443 assert!(hostname("127.0.0.1").is_ok());
444 assert!(hostname("0.0.0.0").is_ok());
445 assert!(hostname("255.255.255.255").is_ok());
446 }
447
448 #[test]
449 fn test_hostname_ipv4_invalid() {
450 assert!(hostname("256.1.1.1").is_err()); assert!(hostname("1.2.3").is_err()); assert!(hostname("1.2.3.4.5").is_err()); }
454
455 #[test]
456 fn test_hostname_ipv6() {
457 assert!(hostname("::1").is_ok());
458 assert!(hostname("fe80::1").is_ok());
459 assert!(hostname("[::1]").is_ok());
460 assert!(hostname("[fe80::1]").is_ok());
461 }
462
463 #[test]
464 fn test_hostname_label_hyphen() {
465 assert!(hostname("-invalid").is_err());
466 assert!(hostname("invalid-").is_err());
467 assert!(hostname("valid-host").is_ok());
468 }
469
470 #[test]
471 fn test_hostname_label_too_long() {
472 let long_label = "a".repeat(64);
473 let result = hostname(&long_label);
474 assert!(result.is_err());
475 assert!(result.unwrap_err().to_string().contains("63"));
476 }
477
478 #[test]
479 fn test_hostname_too_long() {
480 let long_host = format!("{}.example.com", "a".repeat(250));
481 let result = hostname(&long_host);
482 assert!(result.is_err());
483 }
484
485 #[test]
486 fn test_hostname_invalid_chars() {
487 assert!(hostname("invalid_host").is_err()); assert!(hostname("invalid host").is_err()); assert!(hostname("invalid@host").is_err()); }
491
492 #[test]
495 fn test_port_valid() {
496 assert!(port(1).is_ok());
497 assert!(port(80).is_ok());
498 assert!(port(443).is_ok());
499 assert!(port(3141).is_ok());
500 assert!(port(8443).is_ok());
501 assert!(port(65535).is_ok());
502 }
503
504 #[test]
505 fn test_port_zero() {
506 let result = port(0);
507 assert!(result.is_err());
508 assert!(result.unwrap_err().to_string().contains("reserved"));
509 }
510
511 #[test]
514 fn test_page_size_valid() {
515 assert!(page_size(1).is_ok());
516 assert!(page_size(100).is_ok());
517 assert!(page_size(1000).is_ok());
518 assert!(page_size(100_000).is_ok());
519 }
520
521 #[test]
522 fn test_page_size_zero() {
523 let result = page_size(0);
524 assert!(result.is_err());
525 assert!(result.unwrap_err().to_string().contains("at least 1"));
526 }
527
528 #[test]
529 fn test_page_size_too_large() {
530 let result = page_size(100_001);
531 assert!(result.is_err());
532 assert!(result.unwrap_err().to_string().contains("100,000"));
533 }
534
535 #[test]
538 fn test_max_frame_bytes_valid() {
539 assert!(max_frame_bytes(1).is_ok());
540 assert!(max_frame_bytes(1024).is_ok());
541 }
542
543 #[test]
544 fn test_max_frame_bytes_zero() {
545 let result = max_frame_bytes(0);
546 assert!(result.is_err());
547 assert!(result.unwrap_err().to_string().contains("Max frame size"));
548 }
549
550 #[test]
553 fn test_max_rows_valid() {
554 assert!(max_rows(1).is_ok());
555 assert!(max_rows(10_000).is_ok());
556 }
557
558 #[test]
559 fn test_max_rows_zero() {
560 let result = max_rows(0);
561 assert!(result.is_err());
562 assert!(result.unwrap_err().to_string().contains("Max rows"));
563 }
564
565 #[test]
568 fn test_max_pages_valid() {
569 assert!(max_pages(1).is_ok());
570 assert!(max_pages(100).is_ok());
571 }
572
573 #[test]
574 fn test_max_pages_zero() {
575 let result = max_pages(0);
576 assert!(result.is_err());
577 assert!(result.unwrap_err().to_string().contains("Max pages"));
578 }
579
580 #[test]
583 fn test_max_json_depth_valid() {
584 assert!(max_json_depth(1).is_ok());
585 assert!(max_json_depth(64).is_ok());
586 }
587
588 #[test]
589 fn test_max_json_depth_zero() {
590 let result = max_json_depth(0);
591 assert!(result.is_err());
592 assert!(result.unwrap_err().to_string().contains("Max JSON depth"));
593 }
594}