1extern crate alloc;
4
5use alloc::string::String;
6use alloc::vec::Vec;
7
8use facet_core::Facet;
9use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
10use facet_reflect::Peek;
11
12#[derive(Debug)]
14pub struct KdlSerializeError {
15 msg: &'static str,
16}
17
18impl core::fmt::Display for KdlSerializeError {
19 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20 f.write_str(self.msg)
21 }
22}
23
24impl std::error::Error for KdlSerializeError {}
25
26#[derive(Debug, Clone)]
28enum Ctx {
29 Root {
31 name: Option<String>,
33 },
34 Struct {
36 name: String,
38 opened_brace: bool,
40 properties: Vec<(String, String)>,
42 arguments: Vec<String>,
44 },
45 Seq {
47 wrapper_name: String,
49 opened: bool,
51 },
52}
53
54pub struct KdlSerializer {
56 out: Vec<u8>,
57 stack: Vec<Ctx>,
58 pending_field: Option<String>,
59 pending_is_property: bool,
60 pending_is_argument: bool,
61 pending_is_child: bool,
62 indent_level: usize,
63}
64
65impl KdlSerializer {
66 pub fn new() -> Self {
68 Self {
69 out: Vec::new(),
70 stack: vec![Ctx::Root { name: None }],
71 pending_field: None,
72 pending_is_property: false,
73 pending_is_argument: false,
74 pending_is_child: false,
75 indent_level: 0,
76 }
77 }
78
79 pub fn finish(self) -> Vec<u8> {
81 self.out
82 }
83
84 fn write_indent(&mut self) {
85 for _ in 0..self.indent_level {
86 self.out.extend_from_slice(b" ");
87 }
88 }
89
90 fn scalar_to_string(&self, scalar: &ScalarValue<'_>) -> String {
91 match scalar {
92 ScalarValue::Null => "#null".to_string(),
93 ScalarValue::Bool(true) => "#true".to_string(),
94 ScalarValue::Bool(false) => "#false".to_string(),
95 ScalarValue::Char(c) => {
96 let mut result = String::with_capacity(3);
97 result.push('"');
98 result.push(*c);
99 result.push('"');
100 result
101 }
102 ScalarValue::I64(n) => {
103 #[cfg(feature = "fast")]
104 return itoa::Buffer::new().format(*n).to_string();
105 #[cfg(not(feature = "fast"))]
106 n.to_string()
107 }
108 ScalarValue::U64(n) => {
109 #[cfg(feature = "fast")]
110 return itoa::Buffer::new().format(*n).to_string();
111 #[cfg(not(feature = "fast"))]
112 n.to_string()
113 }
114 ScalarValue::I128(n) => {
115 #[cfg(feature = "fast")]
116 return itoa::Buffer::new().format(*n).to_string();
117 #[cfg(not(feature = "fast"))]
118 n.to_string()
119 }
120 ScalarValue::U128(n) => {
121 #[cfg(feature = "fast")]
122 return itoa::Buffer::new().format(*n).to_string();
123 #[cfg(not(feature = "fast"))]
124 n.to_string()
125 }
126 ScalarValue::F64(n) => {
127 if n.is_nan() {
128 "#nan".to_string()
129 } else if n.is_infinite() {
130 if *n > 0.0 {
131 "#inf".to_string()
132 } else {
133 "#-inf".to_string()
134 }
135 } else {
136 #[cfg(feature = "fast")]
137 return zmij::Buffer::new().format(*n).to_string();
138 #[cfg(not(feature = "fast"))]
139 n.to_string()
140 }
141 }
142 ScalarValue::Str(s) | ScalarValue::StringlyTyped(s) => {
143 let mut result = String::with_capacity(s.len() + 2);
145 result.push('"');
146 for c in s.chars() {
147 match c {
148 '"' => result.push_str("\\\""),
149 '\\' => result.push_str("\\\\"),
150 '\n' => result.push_str("\\n"),
151 '\r' => result.push_str("\\r"),
152 '\t' => result.push_str("\\t"),
153 '\u{0008}' => result.push_str("\\b"), '\u{000C}' => result.push_str("\\f"), c if c.is_control() => {
156 result.push_str(&format!("\\u{{{:04X}}}", c as u32));
158 }
159 _ => result.push(c),
160 }
161 }
162 result.push('"');
163 result
164 }
165 ScalarValue::Bytes(_) => {
166 "\"<bytes>\"".to_string()
168 }
169 }
170 }
171
172 fn ensure_struct_opened(&mut self) {
174 if let Some(Ctx::Struct {
175 name,
176 opened_brace,
177 properties,
178 arguments,
179 }) = self.stack.last_mut()
180 && !*opened_brace
181 {
182 self.out.extend_from_slice(name.as_bytes());
184
185 for arg in arguments.drain(..) {
187 self.out.push(b' ');
188 self.out.extend_from_slice(arg.as_bytes());
189 }
190
191 for (k, v) in properties.drain(..) {
193 self.out.push(b' ');
194 self.out.extend_from_slice(k.as_bytes());
195 self.out.push(b'=');
196 self.out.extend_from_slice(v.as_bytes());
197 }
198
199 self.out.extend_from_slice(b" {");
201 *opened_brace = true;
202 self.indent_level += 1;
203 }
204 }
205
206 fn ensure_seq_opened(&mut self) {
208 let needs_open = matches!(self.stack.last(), Some(Ctx::Seq { opened: false, .. }));
210
211 if needs_open
212 && let Some(Ctx::Seq {
213 wrapper_name,
214 opened,
215 }) = self.stack.last_mut()
216 {
217 let name = wrapper_name.clone();
218 *opened = true;
219 self.out.push(b'\n');
221 self.write_indent();
222 self.out.extend_from_slice(name.as_bytes());
223 self.out.extend_from_slice(b" {");
224 self.indent_level += 1;
225 }
226 }
227}
228
229impl Default for KdlSerializer {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl FormatSerializer for KdlSerializer {
236 type Error = KdlSerializeError;
237
238 fn struct_metadata(&mut self, shape: &facet_core::Shape) -> Result<(), Self::Error> {
239 let element_name = shape
241 .get_builtin_attr_value::<&str>("rename")
242 .map(|s| s.to_string())
243 .unwrap_or_else(|| to_kebab_case(shape.type_identifier));
244
245 if let Some(Ctx::Root { name }) = self.stack.last_mut() {
247 *name = Some(element_name);
248 }
249
250 Ok(())
251 }
252
253 fn begin_struct(&mut self) -> Result<(), Self::Error> {
254 enum Action {
256 Root(Option<String>),
257 NestedStruct,
258 SeqItem,
259 NoStack,
260 }
261
262 let action = match self.stack.last_mut() {
263 Some(Ctx::Root { name }) => Action::Root(name.take()),
264 Some(Ctx::Struct { .. }) => Action::NestedStruct,
265 Some(Ctx::Seq { .. }) => Action::SeqItem,
266 None => Action::NoStack,
267 };
268
269 let node_name = match action {
270 Action::Root(name) => name.unwrap_or_else(|| "root".to_string()),
271 Action::NestedStruct => {
272 self.ensure_struct_opened();
274 self.out.push(b'\n');
275 self.write_indent();
276 self.pending_field
278 .take()
279 .unwrap_or_else(|| "node".to_string())
280 }
281 Action::SeqItem => {
282 self.ensure_seq_opened();
284 self.out.push(b'\n');
285 self.write_indent();
286 "item".to_string()
288 }
289 Action::NoStack => "root".to_string(),
290 };
291
292 self.stack.push(Ctx::Struct {
293 name: node_name,
294 opened_brace: false,
295 properties: Vec::new(),
296 arguments: Vec::new(),
297 });
298
299 Ok(())
300 }
301
302 fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
303 self.pending_field = Some(key.to_string());
304 Ok(())
307 }
308
309 fn end_struct(&mut self) -> Result<(), Self::Error> {
310 match self.stack.pop() {
311 Some(Ctx::Struct {
312 name,
313 opened_brace,
314 arguments,
315 properties,
316 }) => {
317 if opened_brace {
318 self.indent_level = self.indent_level.saturating_sub(1);
320 self.out.push(b'\n');
321 self.write_indent();
322 self.out.push(b'}');
323 } else {
324 self.out.extend_from_slice(name.as_bytes());
326 for arg in arguments {
327 self.out.push(b' ');
328 self.out.extend_from_slice(arg.as_bytes());
329 }
330 for (k, v) in properties {
331 self.out.push(b' ');
332 self.out.extend_from_slice(k.as_bytes());
333 self.out.push(b'=');
334 self.out.extend_from_slice(v.as_bytes());
335 }
336 }
337 Ok(())
338 }
339 Some(Ctx::Root { name }) => {
340 if let Some(n) = name {
342 self.out.extend_from_slice(n.as_bytes());
343 }
344 Ok(())
345 }
346 _ => Err(KdlSerializeError {
347 msg: "end_struct without matching begin_struct",
348 }),
349 }
350 }
351
352 fn begin_seq(&mut self) -> Result<(), Self::Error> {
353 let is_nested_seq = matches!(self.stack.last(), Some(Ctx::Seq { .. }));
356
357 if is_nested_seq {
358 self.ensure_seq_opened();
360 self.out.push(b'\n');
361 self.write_indent();
362 self.out.extend_from_slice(b"item {");
363 self.indent_level += 1;
364
365 self.stack.push(Ctx::Seq {
367 wrapper_name: "item".to_string(), opened: true, });
370 } else if self.pending_is_child {
371 self.ensure_struct_opened();
374
375 self.stack.push(Ctx::Seq {
377 wrapper_name: String::new(), opened: true, });
380
381 self.pending_field = None;
383 } else {
384 let wrapper_name = self
386 .pending_field
387 .take()
388 .unwrap_or_else(|| "items".to_string());
389
390 self.ensure_struct_opened();
392
393 self.stack.push(Ctx::Seq {
394 wrapper_name,
395 opened: false,
396 });
397 }
398 Ok(())
399 }
400
401 fn end_seq(&mut self) -> Result<(), Self::Error> {
402 match self.stack.pop() {
403 Some(Ctx::Seq {
404 opened,
405 wrapper_name,
406 }) => {
407 if opened && !wrapper_name.is_empty() {
410 self.indent_level = self.indent_level.saturating_sub(1);
412 self.out.push(b'\n');
413 self.write_indent();
414 self.out.push(b'}');
415 }
416 Ok(())
417 }
418 _ => Err(KdlSerializeError {
419 msg: "end_seq without matching begin_seq",
420 }),
421 }
422 }
423
424 fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
425 let value_str = self.scalar_to_string(&scalar);
426
427 match self.stack.last_mut() {
428 Some(Ctx::Struct {
429 opened_brace,
430 properties,
431 arguments,
432 ..
433 }) => {
434 if self.pending_is_property {
435 if let Some(key) = self.pending_field.take() {
437 properties.push((key, value_str));
438 }
439 } else if self.pending_is_argument {
440 arguments.push(value_str);
442 self.pending_field = None;
443 } else if self.pending_is_child || *opened_brace {
444 if !*opened_brace {
447 self.ensure_struct_opened();
448 }
449 self.out.push(b'\n');
450 self.write_indent();
451 if let Some(key) = self.pending_field.take() {
452 self.out.extend_from_slice(key.as_bytes());
453 self.out.push(b' ');
454 }
455 self.out.extend_from_slice(value_str.as_bytes());
456 } else {
457 self.ensure_struct_opened();
459 self.out.push(b'\n');
460 self.write_indent();
461 if let Some(key) = self.pending_field.take() {
462 self.out.extend_from_slice(key.as_bytes());
463 self.out.push(b' ');
464 }
465 self.out.extend_from_slice(value_str.as_bytes());
466 }
467 }
468 Some(Ctx::Seq { .. }) => {
469 self.ensure_seq_opened();
471 self.out.push(b'\n');
472 self.write_indent();
473 self.out.extend_from_slice(b"item ");
474 self.out.extend_from_slice(value_str.as_bytes());
475 }
476 Some(Ctx::Root { .. }) | None => {
477 self.out.extend_from_slice(b"value ");
479 self.out.extend_from_slice(value_str.as_bytes());
480 }
481 }
482
483 self.pending_field = None;
484 Ok(())
485 }
486
487 fn field_metadata(&mut self, field: &facet_reflect::FieldItem) -> Result<(), Self::Error> {
488 let Some(field_def) = field.field else {
490 self.pending_is_property = true;
491 self.pending_is_argument = false;
492 self.pending_is_child = false;
493 return Ok(());
494 };
495
496 self.pending_is_property = field_def.get_attr(Some("kdl"), "property").is_some();
498 self.pending_is_argument = field_def.get_attr(Some("kdl"), "argument").is_some()
499 || field_def.get_attr(Some("kdl"), "arguments").is_some();
500 self.pending_is_child = field_def.get_attr(Some("kdl"), "child").is_some()
501 || field_def.get_attr(Some("kdl"), "children").is_some();
502 Ok(())
503 }
504}
505
506fn to_kebab_case(s: &str) -> String {
508 s.to_lowercase()
510}
511
512pub fn to_vec<'facet, T>(value: &T) -> Result<Vec<u8>, SerializeError<KdlSerializeError>>
514where
515 T: Facet<'facet> + ?Sized,
516{
517 let mut serializer = KdlSerializer::new();
518 serialize_root(&mut serializer, Peek::new(value))?;
519 Ok(serializer.finish())
520}
521
522pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<KdlSerializeError>>
524where
525 T: Facet<'facet> + ?Sized,
526{
527 let bytes = to_vec(value)?;
528 Ok(String::from_utf8(bytes).expect("KDL output should always be valid UTF-8"))
529}