1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
2use copybook_error::Error;
9pub use copybook_safe_index::{safe_divide, safe_slice_get};
10pub use copybook_safe_text::{
11 parse_isize, parse_usize, safe_parse_u16, safe_string_char_at, safe_write, safe_write_str,
12};
13
14pub type Result<T> = std::result::Result<T, Error>;
16
17#[inline]
23#[must_use = "Handle the Result or propagate the error"]
24pub fn safe_array_bound(
25 base: usize,
26 count: usize,
27 item_size: usize,
28 context: &str,
29) -> Result<usize> {
30 copybook_overflow::safe_array_bound(base, count, item_size, context)
31}
32
33#[inline]
39#[must_use = "Handle the Result or propagate the error"]
40pub fn safe_u64_to_u32(value: u64, context: &str) -> Result<u32> {
41 copybook_overflow::safe_u64_to_u32(value, context)
42}
43
44#[inline]
50#[must_use = "Handle the Result or propagate the error"]
51pub fn safe_u64_to_u16(value: u64, context: &str) -> Result<u16> {
52 copybook_overflow::safe_u64_to_u16(value, context)
53}
54
55#[inline]
61#[must_use = "Handle the Result or propagate the error"]
62pub fn safe_usize_to_u32(value: usize, context: &str) -> Result<u32> {
63 copybook_overflow::safe_usize_to_u32(value, context)
64}
65
66#[cfg(test)]
67#[allow(clippy::expect_used, clippy::unwrap_used)]
68mod tests {
69 use super::*;
70 use copybook_error::ErrorCode;
71 use proptest::prelude::*;
72
73 #[test]
74 fn safe_parse_usize() {
75 assert_eq!(parse_usize("123", "test").expect("parse usize"), 123);
76 }
77
78 #[test]
79 fn safe_parse_usize_invalid() {
80 assert!(matches!(
81 parse_usize("invalid", "test"),
82 Err(error) if error.code == ErrorCode::CBKP001_SYNTAX
83 ));
84 }
85
86 #[test]
87 fn safe_parse_isize() {
88 assert_eq!(parse_isize("-42", "test").expect("parse isize"), -42);
89 }
90
91 #[test]
92 fn safe_divide_returns_quotient_or_syntax_error() {
93 assert_eq!(safe_divide(10, 2, "test").expect("divide"), 5);
94 assert!(matches!(
95 safe_divide(10, 0, "test"),
96 Err(error) if error.code == ErrorCode::CBKP001_SYNTAX
97 ));
98 }
99
100 #[test]
101 fn safe_array_bound_behavior() {
102 assert_eq!(safe_array_bound(10, 3, 4, "test").expect("array bound"), 22);
103
104 assert!(matches!(
105 safe_array_bound(0, usize::MAX, 2, "overflow"),
106 Err(error) if error.code == ErrorCode::CBKP021_ODO_NOT_TAIL
107 ));
108 }
109
110 #[test]
111 fn safe_parse_u16_valid_and_invalid() {
112 assert_eq!(safe_parse_u16("42", "test").expect("parse u16"), 42);
113 assert!(matches!(
114 safe_parse_u16("99999", "test"),
115 Err(error) if error.code == ErrorCode::CBKP001_SYNTAX
116 ));
117 }
118
119 #[test]
120 fn safe_string_char_at_behavior() {
121 assert_eq!(
122 safe_string_char_at("abc", 1, "test").expect("char index"),
123 'b'
124 );
125 assert!(matches!(
126 safe_string_char_at("abc", 3, "test"),
127 Err(error) if error.code == ErrorCode::CBKP001_SYNTAX
128 ));
129 }
130
131 proptest! {
132 #[test]
133 fn safe_parse_usize_round_trip(value in 0u64..1_000_000u64) {
134 let value = value as usize;
135 let text = value.to_string();
136 let parsed = parse_usize(&text, "prop").expect("roundtrip parse");
137 prop_assert_eq!(parsed, value);
138 }
139
140 #[test]
141 fn safe_slice_get_round_trip(value in 1u32..200u32) {
142 let vec: Vec<u32> = (0..value).collect();
143 let index = (value as usize) / 2;
144 let got = safe_slice_get(&vec, index, "prop").expect("slice index");
145 prop_assert_eq!(got, index as u32);
146 }
147
148 #[test]
149 fn safe_array_bound_matches_reference(
150 base in 0u32..1000u32,
151 count in 0u32..1000u32,
152 item in 1u32..100u32,
153 ) {
154 let base = base as usize;
155 let count = count as usize;
156 let item = item as usize;
157
158 let expected = (count as u128)
159 .checked_mul(item as u128)
160 .and_then(|total| (base as u128).checked_add(total));
161
162 match expected.and_then(|total| usize::try_from(total).ok()) {
163 Some(expected) => {
164 prop_assert_eq!(safe_array_bound(base, count, item, "prop").expect("bounded"), expected);
165 }
166 None => {
167 prop_assert!(safe_array_bound(base, count, item, "prop").is_err());
168 }
169 }
170 }
171 }
172
173 proptest! {
174 #[test]
175 fn safe_u64_to_u32_round_trip(value in 0u64..=u32::MAX as u64) {
176 prop_assert_eq!(
177 safe_u64_to_u32(value, "prop").expect("u64->u32"),
178 value as u32
179 );
180 }
181
182 #[test]
183 fn safe_u64_to_u16_round_trip(value in 0u64..=u16::MAX as u64) {
184 prop_assert_eq!(
185 safe_u64_to_u16(value, "prop").expect("u64->u16"),
186 value as u16
187 );
188 }
189 }
190}