teamy_figue/
config_format.rs1use std::string::String;
21
22use facet::Facet;
23
24use crate::config_value::ConfigValue;
25
26#[derive(Facet, Debug)]
28pub struct ConfigFormatError {
29 pub message: String,
31
32 pub offset: Option<usize>,
34}
35
36impl ConfigFormatError {
37 pub fn new(message: impl Into<String>) -> Self {
39 Self {
40 message: message.into(),
41 offset: None,
42 }
43 }
44
45 pub fn with_offset(message: impl Into<String>, offset: usize) -> Self {
47 Self {
48 message: message.into(),
49 offset: Some(offset),
50 }
51 }
52}
53
54impl core::fmt::Display for ConfigFormatError {
55 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 if let Some(offset) = self.offset {
57 write!(f, "at byte {}: {}", offset, self.message)
58 } else {
59 write!(f, "{}", self.message)
60 }
61 }
62}
63
64impl core::error::Error for ConfigFormatError {}
65
66pub trait ConfigFormat: Send + Sync {
97 fn extensions(&self) -> &[&str];
101
102 fn parse(&self, contents: &str) -> Result<ConfigValue, ConfigFormatError>;
108}
109
110#[derive(Debug, Clone, Copy, Default)]
115pub struct JsonFormat;
116
117impl ConfigFormat for JsonFormat {
118 fn extensions(&self) -> &[&str] {
119 &["json"]
120 }
121
122 fn parse(&self, contents: &str) -> Result<ConfigValue, ConfigFormatError> {
123 facet_json::from_str(contents).map_err(|e| ConfigFormatError::new(e.to_string()))
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_json_format_extensions() {
133 let format = JsonFormat;
134 assert_eq!(format.extensions(), &["json"]);
135 }
136
137 #[test]
138 fn test_json_format_parse_object() {
139 let format = JsonFormat;
140 let result = format.parse(r#"{"port": 8080, "host": "localhost"}"#);
141 assert!(result.is_ok(), "parse failed: {:?}", result.err());
142 let value = result.unwrap();
143 assert!(matches!(value, ConfigValue::Object(_)));
144 }
145
146 #[test]
147 fn test_json_format_parse_nested() {
148 let format = JsonFormat;
149 let result = format.parse(r#"{"smtp": {"host": "mail.example.com", "port": 587}}"#);
150 assert!(result.is_ok(), "parse failed: {:?}", result.err());
151 }
152
153 #[test]
154 fn test_json_format_parse_array() {
155 let format = JsonFormat;
156 let result = format.parse(r#"["one", "two", "three"]"#);
157 assert!(result.is_ok(), "parse failed: {:?}", result.err());
158 let value = result.unwrap();
159 assert!(matches!(value, ConfigValue::Array(_)));
160 }
161
162 #[test]
163 fn test_json_format_parse_error() {
164 let format = JsonFormat;
165 let result = format.parse(r#"{"port": invalid}"#);
166 assert!(result.is_err());
167 let err = result.unwrap_err();
168 assert!(!err.message.is_empty());
169 }
170
171 #[test]
172 fn test_config_format_error_display() {
173 let err = ConfigFormatError::new("something went wrong");
174 assert_eq!(err.to_string(), "something went wrong");
175
176 let err = ConfigFormatError::with_offset("unexpected token", 42);
177 assert_eq!(err.to_string(), "at byte 42: unexpected token");
178 }
179}