cookie_agent_protocol/
setup_value.rs1use std::fmt;
2
3use crate::{BoundedSetupString, SafeCode, SafeSetupValue, SetupFieldDescriptor, SetupFieldType};
4
5#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct SetupValueParseError {
8 message: String,
9}
10
11impl SetupValueParseError {
12 fn new(field: &SetupFieldDescriptor, message: impl fmt::Display) -> Self {
13 Self {
14 message: format!("setup field `{}` {message}", field.id),
15 }
16 }
17}
18
19impl fmt::Display for SetupValueParseError {
20 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21 formatter.write_str(&self.message)
22 }
23}
24
25impl std::error::Error for SetupValueParseError {}
26
27pub fn parse_setup_value(
32 field: &SetupFieldDescriptor,
33 raw: &str,
34) -> Result<SafeSetupValue, SetupValueParseError> {
35 let length = u32::try_from(raw.len()).unwrap_or(u32::MAX);
36 if field
37 .validation
38 .min_length
39 .is_some_and(|minimum| length < minimum)
40 {
41 return Err(SetupValueParseError::new(
42 field,
43 "is shorter than the allowed minimum",
44 ));
45 }
46 if field
47 .validation
48 .max_length
49 .is_some_and(|maximum| length > maximum)
50 {
51 return Err(SetupValueParseError::new(
52 field,
53 "is longer than the allowed maximum",
54 ));
55 }
56
57 match field.validation.value_type {
58 SetupFieldType::String => BoundedSetupString::new(raw.to_owned())
59 .map(SafeSetupValue::String)
60 .map_err(|error| SetupValueParseError::new(field, error)),
61 SetupFieldType::Code => SafeCode::new(raw.to_owned())
62 .map(SafeSetupValue::Code)
63 .map_err(|error| SetupValueParseError::new(field, error)),
64 SetupFieldType::Integer => {
65 let value = raw
66 .parse::<i64>()
67 .map_err(|_| SetupValueParseError::new(field, "must be an integer"))?;
68 if field
69 .validation
70 .minimum
71 .is_some_and(|minimum| value < minimum)
72 || field
73 .validation
74 .maximum
75 .is_some_and(|maximum| value > maximum)
76 {
77 return Err(SetupValueParseError::new(
78 field,
79 "is outside the allowed range",
80 ));
81 }
82 Ok(SafeSetupValue::Integer(value))
83 }
84 SetupFieldType::Bool => match raw.to_ascii_lowercase().as_str() {
85 "true" | "yes" | "y" | "1" => Ok(SafeSetupValue::Bool(true)),
86 "false" | "no" | "n" | "0" => Ok(SafeSetupValue::Bool(false)),
87 _ => Err(SetupValueParseError::new(field, "must be true or false")),
88 },
89 }
90}
91
92#[must_use]
94pub fn setup_value_text(value: &SafeSetupValue) -> String {
95 match value {
96 SafeSetupValue::Bool(value) => value.to_string(),
97 SafeSetupValue::Integer(value) => value.to_string(),
98 SafeSetupValue::Code(value) => value.to_string(),
99 SafeSetupValue::String(value) => value.as_str().to_owned(),
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use crate::{
106 SafeDisplayText, SetupFieldId, SetupFieldValidation, SetupValueParseError,
107 parse_setup_value,
108 };
109
110 use super::{SafeSetupValue, SetupFieldDescriptor, SetupFieldType};
111
112 fn field(value_type: SetupFieldType) -> SetupFieldDescriptor {
113 SetupFieldDescriptor {
114 id: SetupFieldId::new("example").expect("field ID"),
115 display_name: SafeDisplayText::new("Example").expect("display name"),
116 help: SafeDisplayText::new("Example value").expect("help"),
117 required: true,
118 default: None,
119 validation: SetupFieldValidation {
120 value_type,
121 min_length: None,
122 max_length: None,
123 minimum: None,
124 maximum: None,
125 },
126 safe_to_project: true,
127 }
128 }
129
130 fn message(error: SetupValueParseError) -> String {
131 error.to_string()
132 }
133
134 #[test]
135 fn validates_string_length() {
136 let mut descriptor = field(SetupFieldType::String);
137 descriptor.validation.min_length = Some(2);
138 descriptor.validation.max_length = Some(4);
139
140 assert_eq!(
141 message(parse_setup_value(&descriptor, "a").unwrap_err()),
142 "setup field `example` is shorter than the allowed minimum"
143 );
144 assert_eq!(
145 message(parse_setup_value(&descriptor, "abcde").unwrap_err()),
146 "setup field `example` is longer than the allowed maximum"
147 );
148 assert!(matches!(
149 parse_setup_value(&descriptor, "abcd"),
150 Ok(SafeSetupValue::String(_))
151 ));
152 }
153
154 #[test]
155 fn validates_integer_range_and_format() {
156 let mut descriptor = field(SetupFieldType::Integer);
157 descriptor.validation.minimum = Some(2);
158 descriptor.validation.maximum = Some(4);
159
160 assert_eq!(
161 parse_setup_value(&descriptor, "3"),
162 Ok(SafeSetupValue::Integer(3))
163 );
164 assert_eq!(
165 message(parse_setup_value(&descriptor, "1").unwrap_err()),
166 "setup field `example` is outside the allowed range"
167 );
168 assert_eq!(
169 message(parse_setup_value(&descriptor, "three").unwrap_err()),
170 "setup field `example` must be an integer"
171 );
172 }
173
174 #[test]
175 fn parses_documented_boolean_variants_case_insensitively() {
176 let descriptor = field(SetupFieldType::Bool);
177 for raw in ["true", "TRUE", "yes", "Y", "1"] {
178 assert_eq!(
179 parse_setup_value(&descriptor, raw),
180 Ok(SafeSetupValue::Bool(true)),
181 "{raw}"
182 );
183 }
184 for raw in ["false", "FALSE", "no", "N", "0"] {
185 assert_eq!(
186 parse_setup_value(&descriptor, raw),
187 Ok(SafeSetupValue::Bool(false)),
188 "{raw}"
189 );
190 }
191 assert_eq!(
192 message(parse_setup_value(&descriptor, "maybe").unwrap_err()),
193 "setup field `example` must be true or false"
194 );
195 }
196}