1use thiserror::Error;
6
7#[derive(Debug, Clone, PartialEq, Hash, Eq, Error)]
8pub enum NumberError {
9 #[error("Number contains control characters")]
10 ControlCharacters,
11 #[error("Number contains internal quotes")]
12 InternalQuotes,
13 #[error("Number is not enclosed in delimiting quotes")]
14 NotQuoted,
15}
16
17pub type FidlNumber = String;
19#[derive(Debug, Clone, PartialEq, Hash, Default, Eq)]
25pub struct Number(String);
26
27fn is_quoted(s: &str) -> bool {
28 s.starts_with('"') && s.ends_with('"') && s.len() >= 2
29}
30
31impl Number {
32 const NUMBER_FORMAT: i64 = 129;
35
36 pub fn type_(&self) -> i64 {
39 Number::NUMBER_FORMAT
40 }
41
42 pub fn to_non_at_string(&self) -> String {
44 if is_quoted(&self.0) {
45 let string = self.0.clone();
46 let mut chars = string.chars();
47 let _front_must_exist = chars.next();
48 let _back_must_exist = chars.next_back();
49 String::from(chars.as_str())
50 } else {
51 self.0.clone()
52 }
53 }
54
55 pub fn to_at_string(&self) -> String {
58 self.0.clone()
59 }
60
61 pub fn from_non_at_string(s: &str) -> Result<Self, NumberError> {
62 if is_quoted(s) {
63 Self::from_at_string(s)
64 } else {
65 let quoted = format!("\"{}\"", s);
66 Self::from_at_string("ed)
67 }
68 }
69
70 pub fn from_at_string(s: &str) -> Result<Self, NumberError> {
73 if s.chars().any(|c| c.is_ascii_control()) {
74 return Err(NumberError::ControlCharacters);
75 }
76
77 if s.is_empty() {
79 return Ok(Self(String::from(s)));
80 }
81 if !is_quoted(s) {
82 return Err(NumberError::NotQuoted);
83 }
84
85 let inner_s = &s[1..s.len() - 1];
86
87 if inner_s.contains('"') {
88 return Err(NumberError::InternalQuotes);
89 }
90
91 Ok(Self(String::from(s)))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[fuchsia::test]
100 fn number_type_in_valid_range() {
101 let number = Number(String::from("\"1234567\""));
102 assert!(number.type_() >= 128);
104 assert!(number.type_() <= 175);
105 }
106
107 #[fuchsia::test]
108 fn number_str_delimiters() {
109 {
111 let actual_number = Number::from_non_at_string("1234567").unwrap();
112 let expected_number = Number(String::from("\"1234567\""));
113 assert_eq!(actual_number, expected_number);
114 }
115
116 {
118 let actual_string = Number(String::from("\"1234567\"")).to_non_at_string();
119 let expected_string = String::from("1234567");
120 assert_eq!(actual_string, expected_string);
121 }
122
123 {
125 let actual_number = Number::from_non_at_string("\"1234567\"").unwrap();
126 let expected_number = Number(String::from("\"1234567\""));
127 assert_eq!(actual_number, expected_number);
128 }
129
130 {
132 let actual_number = Number::from_at_string("\"1234567\"").unwrap();
133 let expected_number = Number(String::from("\"1234567\""));
134 assert_eq!(actual_number, expected_number);
135 }
136
137 {
139 let actual_string = Number(String::from("\"1234567\"")).to_at_string();
140 let expected_string = String::from("\"1234567\"");
141 assert_eq!(actual_string, expected_string);
142 }
143 }
144
145 #[fuchsia::test]
146 fn number_validation_success() {
147 assert!(Number::from_non_at_string("").is_ok());
148 assert!(Number::from_non_at_string("123456").is_ok());
149 assert!(Number::from_non_at_string("\"123456\"").is_ok());
150 assert!(Number::from_at_string("").is_ok());
151 assert!(Number::from_at_string("\"123456\"").is_ok());
152 }
153
154 #[fuchsia::test]
155 fn number_validation_error() {
156 assert!(matches!(
158 Number::from_non_at_string("123\r\n456"),
159 Err(NumberError::ControlCharacters)
160 ));
161 assert!(matches!(
162 Number::from_at_string("\"123\0456\""),
163 Err(NumberError::ControlCharacters)
164 ));
165
166 assert!(matches!(Number::from_at_string("123456"), Err(NumberError::NotQuoted)));
168
169 assert!(matches!(Number::from_non_at_string("123\"456"), Err(NumberError::InternalQuotes)));
171 assert!(matches!(
172 Number::from_non_at_string("\"123\"456\""),
173 Err(NumberError::InternalQuotes)
174 ));
175 assert!(matches!(Number::from_at_string("\"123\"456\""), Err(NumberError::InternalQuotes)));
176 }
177}