facet_format_toml/
serializer.rs1#![allow(dead_code)]
4
5extern crate alloc;
6
7use alloc::{string::String, vec::Vec};
8use core::fmt::Write;
9
10use facet_format::{FormatSerializer, ScalarValue, SerializeError};
11
12#[derive(Debug, Clone, Default)]
14pub struct SerializeOptions {
15 pub inline_tables: bool,
17}
18
19impl SerializeOptions {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn inline_tables(mut self) -> Self {
27 self.inline_tables = true;
28 self
29 }
30}
31
32#[derive(Debug)]
33pub struct TomlSerializeError {
34 msg: String,
35}
36
37impl core::fmt::Display for TomlSerializeError {
38 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39 f.write_str(&self.msg)
40 }
41}
42
43impl std::error::Error for TomlSerializeError {}
44
45#[derive(Debug, Clone)]
46enum Ctx {
47 Root { first: bool },
49 Table { first: bool, path: Vec<String> },
51 InlineTable { first: bool },
53 Array { first: bool },
55}
56
57pub struct TomlSerializer {
59 out: String,
60 stack: Vec<Ctx>,
61 options: SerializeOptions,
62 current_path: Vec<String>,
64}
65
66impl TomlSerializer {
67 pub fn new() -> Self {
69 Self::with_options(SerializeOptions::default())
70 }
71
72 pub fn with_options(options: SerializeOptions) -> Self {
74 Self {
75 out: String::new(),
76 stack: Vec::new(),
77 options,
78 current_path: Vec::new(),
79 }
80 }
81
82 pub fn finish(self) -> String {
84 self.out
85 }
86
87 fn is_inline_context(&self) -> bool {
89 matches!(
90 self.stack.last(),
91 Some(Ctx::InlineTable { .. }) | Some(Ctx::Array { .. })
92 )
93 }
94
95 fn write_toml_string(&mut self, s: &str) {
97 self.out.push('"');
98 for c in s.chars() {
99 match c {
100 '"' => self.out.push_str(r#"\""#),
101 '\\' => self.out.push_str(r"\\"),
102 '\n' => self.out.push_str(r"\n"),
103 '\r' => self.out.push_str(r"\r"),
104 '\t' => self.out.push_str(r"\t"),
105 c if c.is_control() => {
106 write!(self.out, "\\u{:04X}", c as u32).unwrap();
107 }
108 c => self.out.push(c),
109 }
110 }
111 self.out.push('"');
112 }
113}
114
115impl Default for TomlSerializer {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl FormatSerializer for TomlSerializer {
122 type Error = TomlSerializeError;
123
124 fn begin_struct(&mut self) -> Result<(), Self::Error> {
125 match self.stack.last_mut() {
126 None => {
127 self.stack.push(Ctx::Root { first: true });
129 Ok(())
130 }
131 Some(Ctx::InlineTable { .. }) | Some(Ctx::Array { .. }) => {
132 self.out.push_str("{ ");
134 self.stack.push(Ctx::InlineTable { first: true });
135 Ok(())
136 }
137 Some(Ctx::Root { .. }) | Some(Ctx::Table { .. }) => {
138 self.out.push_str("{ ");
141 self.stack.push(Ctx::InlineTable { first: true });
142 Ok(())
143 }
144 }
145 }
146
147 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
148 match self.stack.last_mut() {
149 Some(Ctx::Root { first }) | Some(Ctx::Table { first, .. }) => {
150 if !*first {
152 self.out.push('\n');
153 }
154 *first = false;
155
156 if key
158 .chars()
159 .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
160 {
161 self.out.push_str(key);
163 } else {
164 self.write_toml_string(key);
166 }
167 self.out.push_str(" = ");
168 Ok(())
169 }
170 Some(Ctx::InlineTable { first }) => {
171 if !*first {
173 self.out.push_str(", ");
174 }
175 *first = false;
176
177 if key
179 .chars()
180 .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
181 {
182 self.out.push_str(key);
183 } else {
184 self.write_toml_string(key);
185 }
186 self.out.push_str(" = ");
187 Ok(())
188 }
189 _ => Err(TomlSerializeError {
190 msg: "field_key called outside of a struct context".into(),
191 }),
192 }
193 }
194
195 fn end_struct(&mut self) -> Result<(), Self::Error> {
196 match self.stack.pop() {
197 Some(Ctx::Root { .. }) => {
198 if !self.out.is_empty() && !self.out.ends_with('\n') {
200 self.out.push('\n');
201 }
202 Ok(())
203 }
204 Some(Ctx::InlineTable { .. }) => {
205 self.out.push_str(" }");
206 Ok(())
207 }
208 Some(Ctx::Table { .. }) => {
209 Ok(())
211 }
212 _ => Err(TomlSerializeError {
213 msg: "end_struct called without matching begin_struct".into(),
214 }),
215 }
216 }
217
218 fn begin_seq(&mut self) -> Result<(), Self::Error> {
219 self.out.push('[');
220 self.stack.push(Ctx::Array { first: true });
221 Ok(())
222 }
223
224 fn end_seq(&mut self) -> Result<(), Self::Error> {
225 match self.stack.pop() {
226 Some(Ctx::Array { .. }) => {
227 self.out.push(']');
228 Ok(())
229 }
230 _ => Err(TomlSerializeError {
231 msg: "end_seq called without matching begin_seq".into(),
232 }),
233 }
234 }
235
236 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
237 if let Some(Ctx::Array { first }) = self.stack.last_mut() {
239 if !*first {
240 self.out.push_str(", ");
241 }
242 *first = false;
243 }
244
245 match scalar {
246 ScalarValue::Null => {
247 return Err(TomlSerializeError {
249 msg: "TOML does not support null values".into(),
250 });
251 }
252 ScalarValue::Bool(v) => {
253 self.out.push_str(if v { "true" } else { "false" });
254 }
255 ScalarValue::I64(v) => {
256 write!(self.out, "{}", v).unwrap();
257 }
258 ScalarValue::U64(v) => {
259 write!(self.out, "{}", v).unwrap();
260 }
261 ScalarValue::I128(v) => {
262 write!(self.out, "{}", v).unwrap();
263 }
264 ScalarValue::U128(v) => {
265 write!(self.out, "{}", v).unwrap();
266 }
267 ScalarValue::F64(v) => {
268 if v.is_nan() {
269 self.out.push_str("nan");
270 } else if v.is_infinite() {
271 if v.is_sign_positive() {
272 self.out.push_str("inf");
273 } else {
274 self.out.push_str("-inf");
275 }
276 } else {
277 write!(self.out, "{}", v).unwrap();
278 }
279 }
280 ScalarValue::Str(s) => {
281 self.write_toml_string(&s);
282 }
283 ScalarValue::Bytes(_) => {
284 return Err(TomlSerializeError {
285 msg: "TOML does not natively support byte arrays".into(),
286 });
287 }
288 }
289 Ok(())
290 }
291}
292
293pub fn to_vec<'facet, T>(value: &T) -> Result<Vec<u8>, SerializeError<TomlSerializeError>>
295where
296 T: facet_core::Facet<'facet>,
297{
298 let mut ser = TomlSerializer::new();
299 facet_format::serialize_root(&mut ser, facet_reflect::Peek::new(value))?;
300 Ok(ser.finish().into_bytes())
301}
302
303pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<TomlSerializeError>>
305where
306 T: facet_core::Facet<'facet>,
307{
308 let mut ser = TomlSerializer::new();
309 facet_format::serialize_root(&mut ser, facet_reflect::Peek::new(value))?;
310 Ok(ser.finish())
311}