Skip to main content

parse_net/sfv/
write.rs

1//! RFC 9651 section 4.1: canonical serialization, as a streaming writer.
2
3use super::{BareItem, parse::INTEGER_MAX};
4use crate::{
5    class,
6    error::WriteError,
7    write::{CountSink, Sink, reserve, write_decimal_u64},
8};
9
10type WResult<T> = core::result::Result<T, WriteError>;
11
12/// What the writer produces.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14enum Kind {
15    Item,
16    List,
17    Dictionary,
18}
19
20/// Where the writer is.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22enum State {
23    /// Nothing written yet.
24    Empty,
25    /// After a member (an item or a closed inner list): its parameters may
26    /// follow.
27    Member,
28    /// After a dictionary key with no value yet (Boolean true so far).
29    Key,
30    /// Inside an inner list, before its first item.
31    Inner,
32    /// Inside an inner list, after an item: its parameters may follow.
33    InnerItem,
34}
35
36/// A value to write after the separator.
37#[derive(Clone, Copy)]
38enum Val<'v> {
39    Bare(&'v BareItem<'v>),
40    Integer(i64),
41    Decimal(i64),
42    String(&'v str),
43    Token(&'v str),
44    Boolean(bool),
45    Date(i64),
46    Display(&'v str),
47}
48
49/// A canonical Structured Field writer (RFC 9651 section 4.1).
50///
51/// Calls must follow the structure: for an Item, one bare item and its
52/// parameters; for a List, members (bare items or inner lists), each with
53/// parameters; for a Dictionary, a key before each member. A dictionary
54/// member whose value is Boolean true is written as its key alone. Each call
55/// reserves its own room, so a call that fails writes nothing; the earlier
56/// calls' output stays in the sink, and a caller that gets an error discards
57/// the field.
58///
59/// # Examples
60///
61/// ```
62/// use parse_net::{sfv::{BareItem, Writer}, SliceSink};
63///
64/// let mut buf = [0u8; 64];
65/// let mut out = SliceSink::new(&mut buf);
66/// let mut w = Writer::dictionary(&mut out);
67/// w.key("u")?.integer(1)?;
68/// w.key("i")?.boolean(true)?;
69/// w.key("lang")?.open_inner_list()?.token("en")?.token("de")?.close_inner_list()?;
70/// w.key("q")?.decimal(500)?.param("x", &BareItem::Token("a"))?;
71/// w.finish()?;
72/// assert_eq!(out.written(), b"u=1, i, lang=(en de), q=0.5;x=a");
73/// # Ok::<(), parse_net::WriteError>(())
74/// ```
75#[derive(Debug)]
76pub struct Writer<'s, S: Sink> {
77    out: &'s mut S,
78    kind: Kind,
79    state: State,
80    /// A parameter key was written by `param_key`; the next value call is
81    /// its value.
82    pending: bool,
83}
84
85impl<'s, S: Sink> Writer<'s, S> {
86    /// A writer for an Item.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use parse_net::{sfv::Writer, SliceSink};
92    ///
93    /// let mut buf = [0u8; 32];
94    /// let mut out = SliceSink::new(&mut buf);
95    /// let mut w = Writer::item(&mut out);
96    /// w.integer(5)?.param_key("a")?.token("b")?;
97    /// w.finish()?;
98    /// assert_eq!(out.written(), b"5;a=b");
99    /// # Ok::<(), parse_net::WriteError>(())
100    /// ```
101    pub fn item(out: &'s mut S) -> Self {
102        Self {
103            out,
104            kind: Kind::Item,
105            state: State::Empty,
106            pending: false,
107        }
108    }
109
110    /// A writer for a List.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use parse_net::{sfv::Writer, SliceSink};
116    ///
117    /// let mut buf = [0u8; 32];
118    /// let mut out = SliceSink::new(&mut buf);
119    /// let mut w = Writer::list(&mut out);
120    /// w.token("a")?.token("b")?;
121    /// w.finish()?;
122    /// assert_eq!(out.written(), b"a, b");
123    /// # Ok::<(), parse_net::WriteError>(())
124    /// ```
125    pub fn list(out: &'s mut S) -> Self {
126        Self {
127            out,
128            kind: Kind::List,
129            state: State::Empty,
130            pending: false,
131        }
132    }
133
134    /// A writer for a Dictionary.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use parse_net::{sfv::Writer, SliceSink};
140    ///
141    /// let mut buf = [0u8; 32];
142    /// let mut out = SliceSink::new(&mut buf);
143    /// let mut w = Writer::dictionary(&mut out);
144    /// w.key("a")?.integer(1)?;
145    /// w.key("b")?;
146    /// w.finish()?;
147    /// assert_eq!(out.written(), b"a=1, b");
148    /// # Ok::<(), parse_net::WriteError>(())
149    /// ```
150    pub fn dictionary(out: &'s mut S) -> Self {
151        Self {
152            out,
153            kind: Kind::Dictionary,
154            state: State::Empty,
155            pending: false,
156        }
157    }
158
159    /// Writes `prefix`, then `key`, then `=` and `value` when both are given
160    /// (or `value` alone): sized first, so an error writes nothing.
161    fn emit(&mut self, prefix: &[u8], key: Option<&str>, value: Option<Val<'_>>) -> WResult<()> {
162        let mut count = CountSink::new();
163        put(prefix, key, value, &mut count)?;
164        reserve(&*self.out, count.count())?;
165        put(prefix, key, value, &mut *self.out)
166    }
167
168    /// The next member value (a bare item), according to the state.
169    fn value(&mut self, v: Val<'_>) -> WResult<&mut Self> {
170        if self.pending {
171            if !matches!(v, Val::Boolean(true) | Val::Bare(BareItem::Boolean(true))) {
172                self.emit(b"=", None, Some(v))?;
173            }
174            self.pending = false;
175            return Ok(self);
176        }
177        match (self.kind, self.state) {
178            (Kind::Item, State::Empty) => self.emit(b"", None, Some(v))?,
179            (Kind::List, State::Empty) => self.emit(b"", None, Some(v))?,
180            (Kind::List, State::Member) => self.emit(b", ", None, Some(v))?,
181            (Kind::Dictionary, State::Key) => {
182                // A Boolean true value is the key alone.
183                if !matches!(v, Val::Boolean(true) | Val::Bare(BareItem::Boolean(true))) {
184                    self.emit(b"=", None, Some(v))?;
185                }
186            }
187            (_, State::Inner) => {
188                self.emit(b"", None, Some(v))?;
189                self.state = State::InnerItem;
190                return Ok(self);
191            }
192            (_, State::InnerItem) => {
193                self.emit(b" ", None, Some(v))?;
194                return Ok(self);
195            }
196            _ => return Err(WriteError::InvalidSequence),
197        }
198        self.state = State::Member;
199        Ok(self)
200    }
201
202    /// Writes a bare item.
203    ///
204    /// # Errors
205    ///
206    /// As the typed methods; [`WriteError::InvalidSequence`] out of order.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use parse_net::{sfv::{BareItem, Writer}, SliceSink};
212    ///
213    /// let mut buf = [0u8; 32];
214    /// let mut out = SliceSink::new(&mut buf);
215    /// let mut w = Writer::list(&mut out);
216    /// w.bare(&BareItem::Token("x"))?.bare(&BareItem::Integer(-2))?;
217    /// w.finish()?;
218    /// assert_eq!(out.written(), b"x, -2");
219    /// # Ok::<(), parse_net::WriteError>(())
220    /// ```
221    pub fn bare(&mut self, b: &BareItem<'_>) -> WResult<&mut Self> {
222        self.value(Val::Bare(b))
223    }
224
225    /// Writes an Integer.
226    ///
227    /// # Errors
228    ///
229    /// [`WriteError::OutOfRange`] beyond 15 digits; [`WriteError::Full`];
230    /// [`WriteError::InvalidSequence`].
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// use parse_net::{sfv::Writer, SliceSink};
236    ///
237    /// let mut buf = [0u8; 32];
238    /// let mut out = SliceSink::new(&mut buf);
239    /// let mut w = Writer::list(&mut out);
240    /// w.integer(-42)?;
241    /// assert!(w.integer(1_000_000_000_000_000).is_err());
242    /// w.finish()?;
243    /// assert_eq!(out.written(), b"-42");
244    /// # Ok::<(), parse_net::WriteError>(())
245    /// ```
246    pub fn integer(&mut self, v: i64) -> WResult<&mut Self> {
247        self.value(Val::Integer(v))
248    }
249
250    /// Writes a Decimal given in thousandths, with the shortest fractional
251    /// part (`1.5`, and `1.0` for a whole number).
252    ///
253    /// # Errors
254    ///
255    /// [`WriteError::OutOfRange`] beyond 12 integer digits;
256    /// [`WriteError::Full`]; [`WriteError::InvalidSequence`].
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use parse_net::{sfv::Writer, SliceSink};
262    ///
263    /// let mut buf = [0u8; 32];
264    /// let mut out = SliceSink::new(&mut buf);
265    /// let mut w = Writer::list(&mut out);
266    /// w.decimal(1500)?.decimal(2000)?.decimal(-125)?;
267    /// w.finish()?;
268    /// assert_eq!(out.written(), b"1.5, 2.0, -0.125");
269    /// # Ok::<(), parse_net::WriteError>(())
270    /// ```
271    pub fn decimal(&mut self, thousandths: i64) -> WResult<&mut Self> {
272        self.value(Val::Decimal(thousandths))
273    }
274
275    /// Writes a String, escaping only `"` and `\`.
276    ///
277    /// # Errors
278    ///
279    /// [`WriteError::Forbidden`] for a byte outside `%x20-7E` (use
280    /// [`display_string`](Self::display_string) for other text);
281    /// [`WriteError::Full`]; [`WriteError::InvalidSequence`].
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use parse_net::{sfv::Writer, SliceSink};
287    ///
288    /// let mut buf = [0u8; 32];
289    /// let mut out = SliceSink::new(&mut buf);
290    /// let mut w = Writer::item(&mut out);
291    /// w.string("say \"hi\"")?;
292    /// w.finish()?;
293    /// assert_eq!(out.written(), br#""say \"hi\"""#);
294    /// # Ok::<(), parse_net::WriteError>(())
295    /// ```
296    pub fn string(&mut self, s: &str) -> WResult<&mut Self> {
297        self.value(Val::String(s))
298    }
299
300    /// Writes a Token.
301    ///
302    /// # Errors
303    ///
304    /// [`WriteError::NotToken`]; [`WriteError::Full`];
305    /// [`WriteError::InvalidSequence`].
306    ///
307    /// # Examples
308    ///
309    /// ```
310    /// use parse_net::{sfv::Writer, SliceSink};
311    ///
312    /// let mut buf = [0u8; 32];
313    /// let mut out = SliceSink::new(&mut buf);
314    /// let mut w = Writer::list(&mut out);
315    /// w.token("gzip")?;
316    /// assert!(w.token("a b").is_err());
317    /// w.finish()?;
318    /// assert_eq!(out.written(), b"gzip");
319    /// # Ok::<(), parse_net::WriteError>(())
320    /// ```
321    pub fn token(&mut self, t: &str) -> WResult<&mut Self> {
322        self.value(Val::Token(t))
323    }
324
325    /// Writes a Boolean (`?1` or `?0`).
326    ///
327    /// # Errors
328    ///
329    /// [`WriteError::Full`]; [`WriteError::InvalidSequence`].
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use parse_net::{sfv::Writer, SliceSink};
335    ///
336    /// let mut buf = [0u8; 32];
337    /// let mut out = SliceSink::new(&mut buf);
338    /// let mut w = Writer::list(&mut out);
339    /// w.boolean(true)?.boolean(false)?;
340    /// w.finish()?;
341    /// assert_eq!(out.written(), b"?1, ?0");
342    /// # Ok::<(), parse_net::WriteError>(())
343    /// ```
344    pub fn boolean(&mut self, b: bool) -> WResult<&mut Self> {
345        self.value(Val::Boolean(b))
346    }
347
348    /// Writes a Date: `@` and the seconds since the Unix epoch.
349    ///
350    /// # Errors
351    ///
352    /// [`WriteError::OutOfRange`] outside years 1 to 9999 (D-030);
353    /// [`WriteError::Full`]; [`WriteError::InvalidSequence`].
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use parse_net::{sfv::Writer, SliceSink};
359    ///
360    /// let mut buf = [0u8; 32];
361    /// let mut out = SliceSink::new(&mut buf);
362    /// let mut w = Writer::item(&mut out);
363    /// w.date(1_659_578_233)?;
364    /// w.finish()?;
365    /// assert_eq!(out.written(), b"@1659578233");
366    /// # Ok::<(), parse_net::WriteError>(())
367    /// ```
368    pub fn date(&mut self, secs: i64) -> WResult<&mut Self> {
369        self.value(Val::Date(secs))
370    }
371
372    /// Writes a Display String: `%"`, the text with `%`, `"`, and every byte
373    /// outside `%x20-7E` as lowercase `%xx`, then `"`.
374    ///
375    /// # Errors
376    ///
377    /// [`WriteError::Full`]; [`WriteError::InvalidSequence`].
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use parse_net::{sfv::Writer, SliceSink};
383    ///
384    /// let mut buf = [0u8; 32];
385    /// let mut out = SliceSink::new(&mut buf);
386    /// let mut w = Writer::item(&mut out);
387    /// w.display_string("caf\u{e9}")?;
388    /// w.finish()?;
389    /// assert_eq!(out.written(), b"%\"caf%c3%a9\"");
390    /// # Ok::<(), parse_net::WriteError>(())
391    /// ```
392    pub fn display_string(&mut self, s: &str) -> WResult<&mut Self> {
393        self.value(Val::Display(s))
394    }
395
396    /// Writes a parameter of the last item or inner list. A Boolean true
397    /// value is written as the key alone.
398    ///
399    /// # Errors
400    ///
401    /// [`WriteError::NotToken`] for a key that is not
402    /// `( lcalpha / "*" ) *( lcalpha / DIGIT / "_" / "-" / "." / "*" )`;
403    /// the value's errors; [`WriteError::InvalidSequence`] before any item;
404    /// [`WriteError::Full`].
405    ///
406    /// # Examples
407    ///
408    /// ```
409    /// use parse_net::{sfv::{BareItem, Writer}, SliceSink};
410    ///
411    /// let mut buf = [0u8; 32];
412    /// let mut out = SliceSink::new(&mut buf);
413    /// let mut w = Writer::item(&mut out);
414    /// w.token("a")?.param("q", &BareItem::Integer(1))?.param("x", &BareItem::Boolean(true))?;
415    /// w.finish()?;
416    /// assert_eq!(out.written(), b"a;q=1;x");
417    /// # Ok::<(), parse_net::WriteError>(())
418    /// ```
419    pub fn param(&mut self, key: &str, v: &BareItem<'_>) -> WResult<&mut Self> {
420        if self.pending || !matches!(self.state, State::Member | State::Key | State::InnerItem) {
421            return Err(WriteError::InvalidSequence);
422        }
423        let value = (!matches!(v, BareItem::Boolean(true))).then_some(Val::Bare(v));
424        self.emit(b";", Some(key), value)?;
425        if self.state == State::Key {
426            self.state = State::Member;
427        }
428        Ok(self)
429    }
430
431    /// Writes the key of a parameter of the last item or inner list; the next
432    /// value call ([`string`](Self::string), [`integer`](Self::integer), and
433    /// the others) writes its value, and a Boolean true writes nothing more.
434    /// For values that are not already a parsed [`BareItem`].
435    ///
436    /// # Errors
437    ///
438    /// As [`param`](Self::param).
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// use parse_net::{sfv::Writer, SliceSink};
444    ///
445    /// let mut buf = [0u8; 32];
446    /// let mut out = SliceSink::new(&mut buf);
447    /// let mut w = Writer::item(&mut out);
448    /// w.token("text")?.param_key("lang")?.string("en \"GB\"")?.param_key("x")?.boolean(true)?;
449    /// w.finish()?;
450    /// assert_eq!(out.written(), br#"text;lang="en \"GB\"";x"#);
451    /// # Ok::<(), parse_net::WriteError>(())
452    /// ```
453    pub fn param_key(&mut self, key: &str) -> WResult<&mut Self> {
454        if self.pending || !matches!(self.state, State::Member | State::Key | State::InnerItem) {
455            return Err(WriteError::InvalidSequence);
456        }
457        self.emit(b";", Some(key), None)?;
458        if self.state == State::Key {
459            self.state = State::Member;
460        }
461        self.pending = true;
462        Ok(self)
463    }
464
465    /// Writes a dictionary key; its value follows (none means Boolean true).
466    ///
467    /// # Errors
468    ///
469    /// [`WriteError::NotToken`] for an invalid key;
470    /// [`WriteError::InvalidSequence`] outside a dictionary or inside an
471    /// inner list; [`WriteError::Full`].
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// use parse_net::{sfv::Writer, SliceSink};
477    ///
478    /// let mut buf = [0u8; 32];
479    /// let mut out = SliceSink::new(&mut buf);
480    /// let mut w = Writer::dictionary(&mut out);
481    /// w.key("a")?;
482    /// w.key("b")?.integer(2)?;
483    /// w.finish()?;
484    /// assert_eq!(out.written(), b"a, b=2");
485    /// # Ok::<(), parse_net::WriteError>(())
486    /// ```
487    pub fn key(&mut self, key: &str) -> WResult<&mut Self> {
488        if self.pending {
489            return Err(WriteError::InvalidSequence);
490        }
491        let prefix: &[u8] = match (self.kind, self.state) {
492            (Kind::Dictionary, State::Empty) => b"",
493            (Kind::Dictionary, State::Member | State::Key) => b", ",
494            _ => return Err(WriteError::InvalidSequence),
495        };
496        self.emit(prefix, Some(key), None)?;
497        self.state = State::Key;
498        Ok(self)
499    }
500
501    /// Opens an inner list as the next list member or dictionary value.
502    ///
503    /// # Errors
504    ///
505    /// [`WriteError::InvalidSequence`] in an Item writer, inside an inner
506    /// list, or in a dictionary before a key; [`WriteError::Full`].
507    ///
508    /// # Examples
509    ///
510    /// ```
511    /// use parse_net::{sfv::Writer, SliceSink};
512    ///
513    /// let mut buf = [0u8; 32];
514    /// let mut out = SliceSink::new(&mut buf);
515    /// let mut w = Writer::list(&mut out);
516    /// w.open_inner_list()?.token("a")?.token("b")?.close_inner_list()?;
517    /// w.token("c")?;
518    /// w.finish()?;
519    /// assert_eq!(out.written(), b"(a b), c");
520    /// # Ok::<(), parse_net::WriteError>(())
521    /// ```
522    pub fn open_inner_list(&mut self) -> WResult<&mut Self> {
523        if self.pending {
524            return Err(WriteError::InvalidSequence);
525        }
526        let prefix: &[u8] = match (self.kind, self.state) {
527            (Kind::List, State::Empty) => b"(",
528            (Kind::List, State::Member) => b", (",
529            (Kind::Dictionary, State::Key) => b"=(",
530            _ => return Err(WriteError::InvalidSequence),
531        };
532        self.emit(prefix, None, None)?;
533        self.state = State::Inner;
534        Ok(self)
535    }
536
537    /// Closes the open inner list; its parameters may follow.
538    ///
539    /// # Errors
540    ///
541    /// [`WriteError::InvalidSequence`] with no inner list open;
542    /// [`WriteError::Full`].
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// use parse_net::{sfv::{BareItem, Writer}, SliceSink};
548    ///
549    /// let mut buf = [0u8; 32];
550    /// let mut out = SliceSink::new(&mut buf);
551    /// let mut w = Writer::list(&mut out);
552    /// w.open_inner_list()?.integer(1)?.close_inner_list()?.param("q", &BareItem::Integer(9))?;
553    /// w.finish()?;
554    /// assert_eq!(out.written(), b"(1);q=9");
555    /// # Ok::<(), parse_net::WriteError>(())
556    /// ```
557    pub fn close_inner_list(&mut self) -> WResult<&mut Self> {
558        if self.pending || !matches!(self.state, State::Inner | State::InnerItem) {
559            return Err(WriteError::InvalidSequence);
560        }
561        self.emit(b")", None, None)?;
562        self.state = State::Member;
563        Ok(self)
564    }
565
566    /// Ends the field.
567    ///
568    /// # Errors
569    ///
570    /// [`WriteError::InvalidSequence`] with an inner list open, a
571    /// parameter key without its value, or with nothing written (an empty List or Dictionary is not serialized at
572    /// all: omit the field, RFC 9651 section 4.1).
573    ///
574    /// # Examples
575    ///
576    /// ```
577    /// use parse_net::{sfv::Writer, SliceSink};
578    ///
579    /// let mut buf = [0u8; 8];
580    /// let mut out = SliceSink::new(&mut buf);
581    /// assert!(Writer::list(&mut out).finish().is_err());
582    /// let mut w = Writer::list(&mut out);
583    /// w.token("a")?;
584    /// w.finish()?;
585    /// assert_eq!(out.written(), b"a");
586    /// # Ok::<(), parse_net::WriteError>(())
587    /// ```
588    pub fn finish(self) -> WResult<()> {
589        if self.pending {
590            return Err(WriteError::InvalidSequence);
591        }
592        match self.state {
593            State::Member | State::Key => Ok(()),
594            State::Empty | State::Inner | State::InnerItem => Err(WriteError::InvalidSequence),
595        }
596    }
597}
598
599/// One byte of a Display String in canonical form: `%`, `"`, and every byte
600/// outside `%x20-7E` as lowercase `%xx`.
601fn put_display_byte(b: u8, out: &mut impl Sink) -> WResult<()> {
602    if b == b'%' || b == b'"' || !(0x20..=0x7e).contains(&b) {
603        const HEX: &[u8; 16] = b"0123456789abcdef";
604        let hi = HEX.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
605        let lo = HEX.get(usize::from(b & 0xf)).copied().unwrap_or(b'0');
606        out.put(&[b'%', hi, lo])?;
607    } else {
608        out.put(&[b])?;
609    }
610    Ok(())
611}
612
613/// The value of a base64 symbol (validated at parse).
614const fn b64_value(c: u8) -> u8 {
615    match c {
616        b'A'..=b'Z' => c - b'A',
617        b'a'..=b'z' => c - b'a' + 26,
618        b'0'..=b'9' => c - b'0' + 52,
619        b'+' => 62,
620        _ => 63,
621    }
622}
623
624/// The base64 symbol for a 6-bit value.
625const fn b64_symbol(v: u8) -> u8 {
626    match v {
627        0..=25 => b'A' + v,
628        26..=51 => b'a' + (v - 26),
629        52..=61 => b'0' + (v - 52),
630        62 => b'+',
631        _ => b'/',
632    }
633}
634
635fn is_key(k: &[u8]) -> bool {
636    matches!(k.first(), Some(&b) if class::is_sf_key_start(b))
637        && k.iter().skip(1).all(|&b| class::is_sf_key_char(b))
638}
639
640fn is_sf_token(t: &[u8]) -> bool {
641    matches!(t.first(), Some(&b) if b == b'*' || b.is_ascii_alphabetic())
642        && t.iter().skip(1).all(|&b| class::is_sf_token_char(b))
643}
644
645fn put(
646    prefix: &[u8],
647    key: Option<&str>,
648    value: Option<Val<'_>>,
649    out: &mut impl Sink,
650) -> WResult<()> {
651    out.put(prefix)?;
652    if let Some(k) = key {
653        if !is_key(k.as_bytes()) {
654            return Err(WriteError::NotToken);
655        }
656        out.put(k.as_bytes())?;
657        if value.is_some() {
658            out.put(b"=")?;
659        }
660    }
661    match value {
662        None => Ok(()),
663        Some(v) => put_value(v, out),
664    }
665}
666
667fn put_integer(v: i64, out: &mut impl Sink) -> WResult<()> {
668    if !(-INTEGER_MAX..=INTEGER_MAX).contains(&v) {
669        return Err(WriteError::OutOfRange);
670    }
671    if v < 0 {
672        out.put(b"-")?;
673    }
674    write_decimal_u64(v.unsigned_abs(), out)
675}
676
677fn put_value(v: Val<'_>, out: &mut impl Sink) -> WResult<()> {
678    match v {
679        Val::Integer(i) => put_integer(i, out),
680        Val::Decimal(t) => {
681            if !(-INTEGER_MAX..=INTEGER_MAX).contains(&t) {
682                return Err(WriteError::OutOfRange);
683            }
684            if t < 0 {
685                out.put(b"-")?;
686            }
687            let a = t.unsigned_abs();
688            write_decimal_u64(a / 1000, out)?;
689            out.put(b".")?;
690            let frac = a % 1000;
691            let digits = [
692                b'0' + (frac / 100) as u8,
693                b'0' + (frac / 10 % 10) as u8,
694                b'0' + (frac % 10) as u8,
695            ];
696            let keep = if frac % 100 == 0 {
697                1
698            } else if frac % 10 == 0 {
699                2
700            } else {
701                3
702            };
703            out.put(digits.get(..keep).unwrap_or(&[]))?;
704            Ok(())
705        }
706        Val::String(s) => {
707            if let Some(at) = s.bytes().position(|b| !(0x20..=0x7e).contains(&b)) {
708                return Err(WriteError::forbidden(at));
709            }
710            out.put(b"\"")?;
711            for chunk in s.as_bytes().split_inclusive(|&b| b == b'"' || b == b'\\') {
712                match chunk.split_last() {
713                    Some((&last, head)) if last == b'"' || last == b'\\' => {
714                        out.put(head)?;
715                        out.put(b"\\")?;
716                        out.put(&[last])?;
717                    }
718                    _ => out.put(chunk)?,
719                }
720            }
721            out.put(b"\"")?;
722            Ok(())
723        }
724        Val::Token(t) => {
725            if !is_sf_token(t.as_bytes()) {
726                return Err(WriteError::NotToken);
727            }
728            out.put(t.as_bytes())?;
729            Ok(())
730        }
731        Val::Boolean(b) => {
732            out.put(if b { b"?1" } else { b"?0" })?;
733            Ok(())
734        }
735        Val::Date(d) => {
736            if d < crate::HttpDate::MIN.unix() || d > crate::HttpDate::MAX.unix() {
737                return Err(WriteError::OutOfRange);
738            }
739            out.put(b"@")?;
740            put_integer(d, out)
741        }
742        Val::Display(s) => {
743            out.put(b"%\"")?;
744            for &b in s.as_bytes() {
745                put_display_byte(b, out)?;
746            }
747            out.put(b"\"")?;
748            Ok(())
749        }
750        Val::Bare(b) => match *b {
751            BareItem::Integer(i) => put_integer(i, out),
752            BareItem::Decimal(d) => put_value(Val::Decimal(d.thousandths()), out),
753            BareItem::String(s) => {
754                // Validated at parse: only `\"` and `\\` escapes.
755                out.put(b"\"")?;
756                out.put(s.raw().as_bytes())?;
757                out.put(b"\"")?;
758                Ok(())
759            }
760            BareItem::Token(t) => put_value(Val::Token(t), out),
761            BareItem::ByteSeq(s) => {
762                let body: &[u8] = s.raw().split(|&b| b == b'=').next().unwrap_or(&[]);
763                out.put(b":")?;
764                // Canonical base64 has zero pad bits: the last symbol of a
765                // partial quantum keeps only the bits that carry data.
766                let (pad, mask): (&[u8], u8) = match body.len() % 4 {
767                    2 => (b"==", 0x30),
768                    3 => (b"=", 0x3c),
769                    _ => (b"", 0x3f),
770                };
771                if let Some((&last, head)) = body.split_last() {
772                    out.put(head)?;
773                    out.put(&[b64_symbol(b64_value(last) & mask)])?;
774                }
775                out.put(pad)?;
776                out.put(b":")?;
777                Ok(())
778            }
779            BareItem::Boolean(v) => put_value(Val::Boolean(v), out),
780            BareItem::Date(d) => put_value(Val::Date(d), out),
781            BareItem::DisplayString(s) => {
782                // Validated at parse: lowercase `%xx` and printable ASCII.
783                // Each byte is decoded and written in canonical form, so an
784                // over-encoded `%61` comes out as `a`.
785                out.put(b"%\"")?;
786                let raw = s.raw();
787                let mut i = 0usize;
788                while let Some(&b) = raw.get(i) {
789                    let byte = if b == b'%' {
790                        let hi = raw
791                            .get(i + 1)
792                            .copied()
793                            .and_then(class::hex_value)
794                            .unwrap_or(0);
795                        let lo = raw
796                            .get(i + 2)
797                            .copied()
798                            .and_then(class::hex_value)
799                            .unwrap_or(0);
800                        i += 3;
801                        (hi << 4) | lo
802                    } else {
803                        i += 1;
804                        b
805                    };
806                    put_display_byte(byte, out)?;
807                }
808                out.put(b"\"")?;
809                Ok(())
810            }
811        },
812    }
813}
814
815#[cfg(test)]
816// The builder methods return `&mut Self` for chaining; a test that does
817// not chain drops it.
818#[allow(unused_results)]
819mod tests {
820    use super::*;
821    use crate::{SliceSink, sfv};
822
823    fn item(f: impl FnOnce(&mut Writer<'_, SliceSink<'_>>) -> WResult<()>) -> std::string::String {
824        let mut buf = [0u8; 256];
825        let mut out = SliceSink::new(&mut buf);
826        let mut w = Writer::item(&mut out);
827        f(&mut w).unwrap();
828        w.finish().unwrap();
829        std::string::String::from_utf8(out.written().to_vec()).unwrap()
830    }
831
832    #[test]
833    fn test_writer_bare_items_canonical() {
834        assert_eq!(
835            item(|w| w.integer(-999_999_999_999_999).map(|_| ())),
836            "-999999999999999"
837        );
838        assert_eq!(item(|w| w.decimal(1500).map(|_| ())), "1.5");
839        assert_eq!(item(|w| w.decimal(-1000).map(|_| ())), "-1.0");
840        assert_eq!(item(|w| w.decimal(-5).map(|_| ())), "-0.005");
841        assert_eq!(item(|w| w.decimal(1230).map(|_| ())), "1.23");
842        assert_eq!(item(|w| w.string("a\"b\\c").map(|_| ())), "\"a\\\"b\\\\c\"");
843        assert_eq!(item(|w| w.token("*foo/bar:1").map(|_| ())), "*foo/bar:1");
844        assert_eq!(item(|w| w.boolean(false).map(|_| ())), "?0");
845        assert_eq!(item(|w| w.date(0).map(|_| ())), "@0");
846        assert_eq!(
847            item(|w| w.display_string("f\u{fc}%\"").map(|_| ())),
848            "%\"f%c3%bc%25%22\""
849        );
850        let parsed = sfv::parse_item(b":YQ:").unwrap();
851        assert_eq!(item(|w| w.bare(&parsed.bare).map(|_| ())), ":YQ==:");
852        // Non-zero pad bits are accepted on parse and cleared on write.
853        let parsed = sfv::parse_item(b":iZ==:").unwrap();
854        assert_eq!(item(|w| w.bare(&parsed.bare).map(|_| ())), ":iQ==:");
855        let parsed = sfv::parse_item(b":YWJ=:").unwrap();
856        assert_eq!(item(|w| w.bare(&parsed.bare).map(|_| ())), ":YWI=:");
857        let parsed = sfv::parse_item(b"::").unwrap();
858        assert_eq!(item(|w| w.bare(&parsed.bare).map(|_| ())), "::");
859    }
860
861    #[test]
862    fn test_writer_refusals_write_nothing() {
863        let mut buf = [0u8; 64];
864        let mut out = SliceSink::new(&mut buf);
865        let mut w = Writer::item(&mut out);
866        assert_eq!(
867            w.integer(1_000_000_000_000_000).err(),
868            Some(WriteError::OutOfRange)
869        );
870        assert_eq!(
871            w.decimal(1_000_000_000_000_000).err(),
872            Some(WriteError::OutOfRange)
873        );
874        assert_eq!(w.date(253_402_300_800).err(), Some(WriteError::OutOfRange));
875        assert_eq!(
876            w.string("caf\u{e9}").err(),
877            Some(WriteError::Forbidden { at: 3 })
878        );
879        assert_eq!(
880            w.string("a\nb").err(),
881            Some(WriteError::Forbidden { at: 1 })
882        );
883        assert_eq!(w.token("1a").err(), Some(WriteError::NotToken));
884        assert_eq!(w.token("").err(), Some(WriteError::NotToken));
885        assert_eq!(
886            w.param("a", &BareItem::Integer(1)).err(),
887            Some(WriteError::InvalidSequence)
888        );
889        assert_eq!(w.key("a").err(), Some(WriteError::InvalidSequence));
890        assert_eq!(w.open_inner_list().err(), Some(WriteError::InvalidSequence));
891        w.integer(1).unwrap();
892        assert_eq!(w.integer(2).err(), Some(WriteError::InvalidSequence));
893        assert_eq!(
894            w.param("A", &BareItem::Integer(1)).err(),
895            Some(WriteError::NotToken)
896        );
897        assert_eq!(
898            w.param("1a", &BareItem::Integer(1)).err(),
899            Some(WriteError::NotToken)
900        );
901        w.finish().unwrap();
902        assert_eq!(out.written(), b"1");
903    }
904
905    #[test]
906    fn test_writer_list_and_inner_lists() {
907        let mut buf = [0u8; 128];
908        let mut out = SliceSink::new(&mut buf);
909        let mut w = Writer::list(&mut out);
910        w.token("a")
911            .unwrap()
912            .param("x", &BareItem::Boolean(true))
913            .unwrap();
914        w.open_inner_list().unwrap();
915        assert_eq!(
916            w.param("p", &BareItem::Integer(1)).err(),
917            Some(WriteError::InvalidSequence)
918        );
919        w.integer(1)
920            .unwrap()
921            .param("q", &BareItem::Boolean(false))
922            .unwrap()
923            .integer(2)
924            .unwrap();
925        w.close_inner_list()
926            .unwrap()
927            .param("lvl", &BareItem::Integer(5))
928            .unwrap();
929        w.open_inner_list().unwrap().close_inner_list().unwrap();
930        w.finish().unwrap();
931        assert_eq!(out.written(), b"a;x, (1;q=?0 2);lvl=5, ()");
932        assert!(sfv::parse_list(out.written()).is_ok());
933    }
934
935    #[test]
936    fn test_writer_param_key_then_value() {
937        let mut buf = [0u8; 64];
938        let mut out = SliceSink::new(&mut buf);
939        let mut w = Writer::list(&mut out);
940        w.token("a")
941            .unwrap()
942            .param_key("s")
943            .unwrap()
944            .string("x y")
945            .unwrap();
946        assert_eq!(
947            w.param_key("t").unwrap().key("k").err(),
948            Some(WriteError::InvalidSequence)
949        );
950        assert_eq!(
951            w.param("u", &BareItem::Integer(1)).err(),
952            Some(WriteError::InvalidSequence)
953        );
954        w.decimal(250).unwrap();
955        w.open_inner_list()
956            .unwrap()
957            .integer(1)
958            .unwrap()
959            .param_key("d")
960            .unwrap()
961            .display_string("\u{e9}")
962            .unwrap();
963        w.close_inner_list()
964            .unwrap()
965            .param_key("b")
966            .unwrap()
967            .boolean(true)
968            .unwrap();
969        w.finish().unwrap();
970        assert_eq!(out.written(), br#"a;s="x y";t=0.25, (1;d=%"%c3%a9");b"#);
971        let mut buf = [0u8; 16];
972        let mut out = SliceSink::new(&mut buf);
973        let mut w = Writer::item(&mut out);
974        w.integer(1).unwrap().param_key("k").unwrap();
975        assert_eq!(w.finish(), Err(WriteError::InvalidSequence));
976    }
977
978    #[test]
979    fn test_writer_finish_refusals() {
980        let mut buf = [0u8; 16];
981        let mut out = SliceSink::new(&mut buf);
982        assert_eq!(
983            Writer::list(&mut out).finish(),
984            Err(WriteError::InvalidSequence)
985        );
986        assert_eq!(
987            Writer::dictionary(&mut out).finish(),
988            Err(WriteError::InvalidSequence)
989        );
990        assert_eq!(
991            Writer::item(&mut out).finish(),
992            Err(WriteError::InvalidSequence)
993        );
994        let mut w = Writer::list(&mut out);
995        w.open_inner_list().unwrap();
996        assert_eq!(w.finish(), Err(WriteError::InvalidSequence));
997        let mut w = Writer::dictionary(&mut out);
998        assert_eq!(w.integer(1).err(), Some(WriteError::InvalidSequence));
999        assert_eq!(w.open_inner_list().err(), Some(WriteError::InvalidSequence));
1000    }
1001
1002    #[test]
1003    fn test_writer_dictionary_true_is_key_alone() {
1004        let mut buf = [0u8; 128];
1005        let mut out = SliceSink::new(&mut buf);
1006        let mut w = Writer::dictionary(&mut out);
1007        w.key("a").unwrap().boolean(true).unwrap();
1008        w.key("b").unwrap();
1009        w.key("c")
1010            .unwrap()
1011            .param("p", &BareItem::Integer(1))
1012            .unwrap();
1013        w.key("d").unwrap().boolean(false).unwrap();
1014        w.finish().unwrap();
1015        assert_eq!(out.written(), b"a, b, c;p=1, d=?0");
1016        assert!(sfv::parse_dictionary(out.written()).is_ok());
1017    }
1018
1019    #[test]
1020    fn test_writer_full_writes_nothing_for_the_call() {
1021        let mut buf = [0u8; 4];
1022        let mut out = SliceSink::new(&mut buf);
1023        let mut w = Writer::item(&mut out);
1024        assert_eq!(w.string("abcd").err(), Some(WriteError::Full));
1025        w.token("ab").unwrap();
1026        assert_eq!(
1027            w.param("cd", &BareItem::Integer(1)).err(),
1028            Some(WriteError::Full)
1029        );
1030        w.finish().unwrap();
1031        assert_eq!(out.written(), b"ab");
1032    }
1033}