Skip to main content

reifydb_type/value/boolean/
parse.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use crate::{
5	error::{Error, TypeError},
6	fragment::Fragment,
7};
8
9pub fn parse_bool(fragment: Fragment) -> Result<bool, Error> {
10	// Fragment is already owned, no conversion needed
11	let value = fragment.text().trim();
12
13	if value.is_empty() {
14		return Err(TypeError::EmptyBooleanValue {
15			fragment,
16		}
17		.into());
18	}
19
20	// Fast path: byte-level matching for common cases
21	match value.as_bytes() {
22		b"true" | b"TRUE" | b"True" => return Ok(true),
23		b"false" | b"FALSE" | b"False" => return Ok(false),
24		b"1" | b"1.0" => return Ok(true),
25		b"0" | b"0.0" => return Ok(false),
26		_ => {}
27	}
28
29	// Slow path: case-insensitive matching for mixed case
30	match value.len() {
31		4 if value.eq_ignore_ascii_case("true") => Ok(true),
32		5 if value.eq_ignore_ascii_case("false") => Ok(false),
33		3 if value == "1.0" => Ok(true),
34		3 if value == "0.0" => Ok(false),
35		_ => {
36			// Check if the value contains numbers - if so, use
37			// numeric boolean diagnostic
38			if value.as_bytes().iter().any(|&b| b.is_ascii_digit()) {
39				Err(TypeError::InvalidNumberBoolean {
40					fragment,
41				}
42				.into())
43			} else {
44				Err(TypeError::InvalidBooleanFormat {
45					fragment,
46				}
47				.into())
48			}
49		}
50	}
51}
52
53#[cfg(test)]
54pub mod tests {
55	use super::*;
56
57	#[test]
58	fn test_valid_true() {
59		assert_eq!(parse_bool(Fragment::testing("true")), Ok(true));
60	}
61
62	#[test]
63	fn test_valid_false() {
64		assert_eq!(parse_bool(Fragment::testing("false")), Ok(false));
65	}
66
67	#[test]
68	fn test_valid_true_with_spaces() {
69		assert_eq!(parse_bool(Fragment::testing("  true  ")), Ok(true));
70	}
71
72	#[test]
73	fn test_valid_false_with_spaces() {
74		assert_eq!(parse_bool(Fragment::testing("  false  ")), Ok(false));
75	}
76
77	#[test]
78	fn test_case_mismatch_true() {
79		assert_eq!(parse_bool(Fragment::testing("True")), Ok(true));
80		assert_eq!(parse_bool(Fragment::testing("TRUE")), Ok(true));
81		assert_eq!(parse_bool(Fragment::testing("tRuE")), Ok(true));
82	}
83
84	#[test]
85	fn test_case_mismatch_false() {
86		assert_eq!(parse_bool(Fragment::testing("False")), Ok(false));
87		assert_eq!(parse_bool(Fragment::testing("FALSE")), Ok(false));
88		assert_eq!(parse_bool(Fragment::testing("fAlSe")), Ok(false));
89	}
90
91	#[test]
92	fn test_valid_numeric_boolean() {
93		assert_eq!(parse_bool(Fragment::testing("1")), Ok(true));
94		assert_eq!(parse_bool(Fragment::testing("0")), Ok(false));
95		assert_eq!(parse_bool(Fragment::testing("1.0")), Ok(true));
96		assert_eq!(parse_bool(Fragment::testing("0.0")), Ok(false));
97	}
98
99	#[test]
100	fn test_invalid_numeric_boolean() {
101		assert!(parse_bool(Fragment::testing("2")).is_err());
102		assert!(parse_bool(Fragment::testing("1.5")).is_err());
103		assert!(parse_bool(Fragment::testing("0.5")).is_err());
104		assert!(parse_bool(Fragment::testing("-1")).is_err());
105		assert!(parse_bool(Fragment::testing("100")).is_err());
106	}
107
108	#[test]
109	fn test_empty_boolean_value() {
110		assert!(parse_bool(Fragment::testing("")).is_err());
111		assert!(parse_bool(Fragment::testing("   ")).is_err());
112	}
113
114	#[test]
115	fn test_ambiguous_boolean_value() {
116		assert!(parse_bool(Fragment::testing("yes")).is_err());
117		assert!(parse_bool(Fragment::testing("no")).is_err());
118		assert!(parse_bool(Fragment::testing("y")).is_err());
119		assert!(parse_bool(Fragment::testing("n")).is_err());
120		assert!(parse_bool(Fragment::testing("on")).is_err());
121		assert!(parse_bool(Fragment::testing("off")).is_err());
122		assert!(parse_bool(Fragment::testing("t")).is_err());
123		assert!(parse_bool(Fragment::testing("f")).is_err());
124	}
125
126	#[test]
127	fn test_invalid_boolean_format() {
128		assert!(parse_bool(Fragment::testing("invalid")).is_err());
129		assert!(parse_bool(Fragment::testing("123")).is_err());
130		assert!(parse_bool(Fragment::testing("abc")).is_err());
131		assert!(parse_bool(Fragment::testing("maybe")).is_err());
132	}
133
134	#[test]
135	fn test_case_insensitive_ambiguous() {
136		assert!(parse_bool(Fragment::testing("Yes")).is_err());
137		assert!(parse_bool(Fragment::testing("NO")).is_err());
138		assert!(parse_bool(Fragment::testing("On")).is_err());
139		assert!(parse_bool(Fragment::testing("OFF")).is_err());
140	}
141}