Skip to main content

parse_net/
etag.rs

1//! Entity tags and the `If-Match` / `If-None-Match` conditions (RFC 9110
2//! sections 8.8.3 and 13.1).
3//!
4//! # Examples
5//!
6//! ```
7//! use parse_net::{self as pn, EntityTag};
8//!
9//! let v2 = EntityTag::strong(b"v2")?;
10//! assert!(pn::if_none_match(b"\"v1\", W/\"v2\"", &v2)?);         // weak comparison: 304
11//! assert!(!pn::if_match(b"W/\"v2\"", Some(&v2))?);               // strong comparison: 412
12//! assert!(pn::if_match(b"*", Some(&v2))? && !pn::if_match(b"*", None)?);
13//! # Ok::<(), pn::ParseError>(())
14//! ```
15
16use crate::{
17    class,
18    error::{ErrorKind, ParseError, Result, WriteError, err},
19    limits::Limits,
20    text::{skip_ows, trim_ows},
21    write::{Sink, reserve},
22};
23
24/// An entity tag: a weak flag and the opaque bytes between the quotes.
25///
26/// # Examples
27///
28/// ```
29/// use parse_net::EntityTag;
30///
31/// let t = EntityTag::parse(b"W/\"abc\"")?;
32/// assert!(t.is_weak());
33/// assert_eq!(t.opaque(), b"abc");
34/// assert!(t.weak_eq(&EntityTag::strong(b"abc")?));
35/// assert!(!t.strong_eq(&EntityTag::strong(b"abc")?));
36/// # Ok::<(), parse_net::ParseError>(())
37/// ```
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub struct EntityTag<'a> {
40    pub(crate) weak: bool,
41    pub(crate) opaque: &'a [u8],
42}
43
44/// The offset of the first byte of `opaque` that is not an `etagc`.
45const fn first_bad(opaque: &[u8]) -> Option<usize> {
46    let mut i = 0;
47    while i < opaque.len() {
48        // `i < opaque.len()` was just checked.
49        #[allow(clippy::indexing_slicing)]
50        let b = opaque[i];
51        if !class::is_etagc(b) {
52            return Some(i);
53        }
54        i += 1;
55    }
56    None
57}
58
59impl<'a> EntityTag<'a> {
60    /// Parses `[ "W/" ] DQUOTE *etagc DQUOTE` (OWS around it stripped).
61    /// `W/` is case-sensitive.
62    ///
63    /// # Errors
64    ///
65    /// [`ErrorKind::InvalidEntityTag`] (a lowercase `w/`, no quotes, a byte
66    /// outside `etagc`, `W/ "a"`); [`ErrorKind::TrailingData`].
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use parse_net::EntityTag;
72    ///
73    /// let t = EntityTag::parse(b" \"xyzzy\" ")?;
74    /// assert!(!t.is_weak() && t.opaque() == b"xyzzy");
75    /// assert!(EntityTag::parse(b"w/\"xyzzy\"").is_err());
76    /// # Ok::<(), parse_net::ParseError>(())
77    /// ```
78    pub fn parse(value: &'a [u8]) -> Result<Self> {
79        let lead = crate::text::skip_ows(value);
80        let (tag, used) = parse_prefix(trim_ows(value)).map_err(|e| e.shifted(lead))?;
81        if used != trim_ows(value).len() {
82            return err(ErrorKind::TrailingData, lead + used);
83        }
84        Ok(tag)
85    }
86
87    /// A strong tag with the given opaque bytes.
88    ///
89    /// # Errors
90    ///
91    /// [`ErrorKind::InvalidEntityTag`] at the first byte that is not an
92    /// `etagc`.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use parse_net::EntityTag;
98    ///
99    /// assert!(!EntityTag::strong(b"v1")?.is_weak());
100    /// assert!(EntityTag::strong(b"a\"b").is_err());
101    /// # Ok::<(), parse_net::ParseError>(())
102    /// ```
103    pub const fn strong(opaque: &'a [u8]) -> Result<Self> {
104        match first_bad(opaque) {
105            Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
106            None => Ok(Self {
107                weak: false,
108                opaque,
109            }),
110        }
111    }
112
113    /// A weak tag with the given opaque bytes.
114    ///
115    /// # Errors
116    ///
117    /// As [`strong`](Self::strong).
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// let t = parse_net::EntityTag::weak(b"v1")?;
123    /// assert!(t.is_weak() && t.opaque() == b"v1");
124    /// # Ok::<(), parse_net::ParseError>(())
125    /// ```
126    pub const fn weak(opaque: &'a [u8]) -> Result<Self> {
127        match first_bad(opaque) {
128            Some(at) => Err(ParseError::new(ErrorKind::InvalidEntityTag, at)),
129            None => Ok(Self { weak: true, opaque }),
130        }
131    }
132
133    /// Whether the tag is weak (`W/`).
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use parse_net::EntityTag;
139    ///
140    /// assert!(EntityTag::parse(b"W/\"1\"")?.is_weak());
141    /// assert!(!EntityTag::parse(b"\"1\"")?.is_weak());
142    /// # Ok::<(), parse_net::ParseError>(())
143    /// ```
144    #[must_use]
145    pub const fn is_weak(&self) -> bool {
146        self.weak
147    }
148
149    /// The opaque bytes, without the quotes.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use parse_net::EntityTag;
155    ///
156    /// assert_eq!(EntityTag::parse(b"W/\"abc\"")?.opaque(), b"abc");
157    /// assert_eq!(EntityTag::parse(b"\"\"")?.opaque(), b"");
158    /// # Ok::<(), parse_net::ParseError>(())
159    /// ```
160    #[must_use]
161    pub const fn opaque(&self) -> &'a [u8] {
162        self.opaque
163    }
164
165    /// Strong comparison (RFC 9110 section 8.8.3.2): both strong and equal.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use parse_net::EntityTag;
171    ///
172    /// let a = EntityTag::strong(b"1")?;
173    /// assert!(a.strong_eq(&EntityTag::parse(b"\"1\"")?));
174    /// assert!(!a.strong_eq(&EntityTag::weak(b"1")?));
175    /// # Ok::<(), parse_net::ParseError>(())
176    /// ```
177    #[must_use]
178    pub fn strong_eq(&self, other: &EntityTag<'_>) -> bool {
179        !self.weak && !other.weak && self.opaque == other.opaque
180    }
181
182    /// Weak comparison: equal opaque bytes, either may be weak.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use parse_net::EntityTag;
188    ///
189    /// let (w, s) = (EntityTag::weak(b"1")?, EntityTag::strong(b"1")?);
190    /// assert!(w.weak_eq(&s) && !w.weak_eq(&EntityTag::strong(b"2")?));
191    /// # Ok::<(), parse_net::ParseError>(())
192    /// ```
193    #[must_use]
194    pub fn weak_eq(&self, other: &EntityTag<'_>) -> bool {
195        self.opaque == other.opaque
196    }
197
198    /// Writes `W/"..."` or `"..."`.
199    ///
200    /// # Errors
201    ///
202    /// [`WriteError::Full`]; nothing is written when the sink reports its
203    /// room.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use parse_net::{EntityTag, SliceSink};
209    ///
210    /// let mut buf = [0u8; 16];
211    /// let mut out = SliceSink::new(&mut buf);
212    /// EntityTag::weak(b"v1")?.write_to(&mut out).expect("room");
213    /// assert_eq!(out.written(), b"W/\"v1\"");
214    /// # Ok::<(), parse_net::ParseError>(())
215    /// ```
216    pub fn write_to(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
217        let prefix: &[u8] = if self.weak { b"W/\"" } else { b"\"" };
218        reserve(out, prefix.len() + self.opaque.len() + 1)?;
219        out.put(prefix)?;
220        out.put(self.opaque)?;
221        out.put(b"\"")?;
222        Ok(())
223    }
224}
225
226/// Parses one entity tag at the start of `s`; returns it and the bytes used.
227fn parse_prefix(s: &[u8]) -> Result<(EntityTag<'_>, usize)> {
228    let (weak, start) = match s {
229        [b'W', b'/', ..] => (true, 2),
230        _ => (false, 0),
231    };
232    if s.get(start) != Some(&b'"') {
233        return err(ErrorKind::InvalidEntityTag, start);
234    }
235    let body = s.get(start + 1..).unwrap_or(&[]);
236    let close = body.iter().position(|&b| !class::is_etagc(b));
237    match close {
238        Some(i) if body.get(i) == Some(&b'"') => {
239            let opaque = body.get(..i).unwrap_or(&[]);
240            Ok((EntityTag { weak, opaque }, start + i + 2))
241        }
242        Some(i) => {
243            let b = body.get(i).copied().unwrap_or(0);
244            let kind = if crate::scan::is_control(b) {
245                crate::scan::control_kind(b)
246            } else {
247                ErrorKind::InvalidEntityTag
248            };
249            err(kind, start + 1 + i)
250        }
251        None => err(ErrorKind::InvalidEntityTag, s.len()),
252    }
253}
254
255/// An `If-Match` or `If-None-Match` value: `*` or a list of entity tags.
256///
257/// # Examples
258///
259/// ```
260/// use parse_net::{etag::parse_condition, Condition, Limits};
261///
262/// assert_eq!(parse_condition(b"*", &Limits::DEFAULT)?, Condition::Any);
263/// assert!(matches!(parse_condition(b"\"a\"", &Limits::DEFAULT)?, Condition::Tags(t) if t.len() == 1));
264/// # Ok::<(), parse_net::ParseError>(())
265/// ```
266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
267pub enum Condition<'a> {
268    /// `*`: any current representation.
269    Any,
270    /// The listed tags.
271    Tags(TagList<'a>),
272}
273
274/// The tags of a validated condition.
275///
276/// # Examples
277///
278/// ```
279/// use parse_net::{etag::parse_condition, Condition, Limits};
280///
281/// let Condition::Tags(tags) = parse_condition(b"\"a\", W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
282/// assert_eq!(tags.iter().filter(|t| t.is_weak()).count(), 1);
283/// # Ok::<(), parse_net::ParseError>(())
284/// ```
285#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
286pub struct TagList<'a> {
287    /// The validated value, OWS trimmed.
288    text: &'a [u8],
289    count: usize,
290}
291
292impl<'a> TagList<'a> {
293    /// The tags, in order.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use parse_net::{etag::parse_condition, Condition, Limits};
299    ///
300    /// let Condition::Tags(tags) = parse_condition(b"\"a\", W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
301    /// let opaque: Vec<_> = tags.iter().map(|t| t.opaque()).collect();
302    /// assert_eq!(opaque, [&b"a"[..], &b"b"[..]]);
303    /// # Ok::<(), parse_net::ParseError>(())
304    /// ```
305    pub fn iter(&self) -> impl Iterator<Item = EntityTag<'a>> + use<'a> {
306        TagIter { s: self.text, i: 0 }
307    }
308
309    /// The number of tags.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use parse_net::{etag::parse_condition, Condition, Limits};
315    ///
316    /// let Condition::Tags(tags) = parse_condition(b"\"a\", , \"b\"", &Limits::DEFAULT)? else { unreachable!() };
317    /// assert_eq!(tags.len(), 2);
318    /// # Ok::<(), parse_net::ParseError>(())
319    /// ```
320    #[must_use]
321    pub const fn len(&self) -> usize {
322        self.count
323    }
324
325    /// Whether there are no tags.
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use parse_net::{etag::parse_condition, Condition, Limits};
331    ///
332    /// let Condition::Tags(tags) = parse_condition(b"\"a\"", &Limits::DEFAULT)? else { unreachable!() };
333    /// assert!(!tags.is_empty());
334    /// # Ok::<(), parse_net::ParseError>(())
335    /// ```
336    #[must_use]
337    pub const fn is_empty(&self) -> bool {
338        self.count == 0
339    }
340}
341
342/// The tags of a validated [`TagList`].
343struct TagIter<'a> {
344    s: &'a [u8],
345    i: usize,
346}
347
348impl<'a> Iterator for TagIter<'a> {
349    type Item = EntityTag<'a>;
350
351    fn next(&mut self) -> Option<EntityTag<'a>> {
352        while matches!(self.s.get(self.i), Some(b' ' | b'\t' | b',')) {
353            self.i += 1;
354        }
355        let (tag, used) = parse_prefix(self.s.get(self.i..)?).ok()?;
356        self.i += used;
357        Some(tag)
358    }
359}
360
361/// What a condition value holds after [`walk`].
362enum Walked {
363    Any,
364    Tags(usize),
365}
366
367/// Validates `"*" / #entity-tag` in one pass over `v` (OWS already trimmed),
368/// calling `f` for every tag. Empty list elements are skipped up to
369/// `max_empty_elements`; `*` must stand alone.
370fn walk<'a>(v: &'a [u8], limits: &Limits, mut f: impl FnMut(EntityTag<'a>)) -> Result<Walked> {
371    let (mut i, mut count, mut empty, mut star) = (0usize, 0usize, 0usize, false);
372    loop {
373        while matches!(v.get(i), Some(b' ' | b'\t')) {
374            i += 1;
375        }
376        match v.get(i) {
377            None => break,
378            Some(b',') => {
379                empty += 1;
380                if empty > usize::from(limits.empty_elements) {
381                    return err(ErrorKind::TooManyEmptyElements, i);
382                }
383                i += 1;
384                continue;
385            }
386            Some(b'*') if !star && count == 0 => {
387                star = true;
388                i += 1;
389            }
390            Some(_) if star => return err(ErrorKind::Malformed, i),
391            Some(_) => {
392                let (tag, used) =
393                    parse_prefix(v.get(i..).unwrap_or(&[])).map_err(|e| match e.kind() {
394                        ErrorKind::InvalidEntityTag
395                            if e.offset() + i >= v.len() && v.get(i) == Some(&b'"') =>
396                        {
397                            ParseError::new(ErrorKind::UnterminatedQuote, v.len())
398                        }
399                        _ => e.shifted(i),
400                    })?;
401                count += 1;
402                if count > usize::from(limits.list_members) {
403                    return err(ErrorKind::TooManyMembers, i);
404                }
405                f(tag);
406                i += used;
407            }
408        }
409        while matches!(v.get(i), Some(b' ' | b'\t')) {
410            i += 1;
411        }
412        match v.get(i) {
413            None => break,
414            // The separator after a member is consumed here, so the next
415            // pass counts only commas with nothing before them as empty.
416            Some(b',') => i += 1,
417            Some(&b) if crate::scan::is_control(b) => return err(crate::scan::control_kind(b), i),
418            Some(_) if star => return err(ErrorKind::Malformed, i),
419            Some(_) => return err(ErrorKind::InvalidEntityTag, i),
420        }
421    }
422    Ok(if star {
423        Walked::Any
424    } else {
425        Walked::Tags(count)
426    })
427}
428
429/// Parses `"*" / #entity-tag`.
430///
431/// # Errors
432///
433/// [`ErrorKind::Malformed`] for `*` mixed with tags; [`ErrorKind::InvalidEntityTag`];
434/// the list errors.
435///
436/// # Examples
437///
438/// ```
439/// use parse_net::{etag::{parse_condition, Condition}, Limits};
440///
441/// let Condition::Tags(tags) = parse_condition(b"\"a\", , W/\"b\"", &Limits::DEFAULT)? else { unreachable!() };
442/// assert_eq!(tags.len(), 2);
443/// assert!(matches!(parse_condition(b"*", &Limits::DEFAULT)?, Condition::Any));
444/// assert!(parse_condition(b"*, \"a\"", &Limits::DEFAULT).is_err());
445/// # Ok::<(), parse_net::ParseError>(())
446/// ```
447pub fn parse_condition<'a>(value: &'a [u8], limits: &Limits) -> Result<Condition<'a>> {
448    let lead = skip_ows(value);
449    let v = trim_ows(value);
450    Ok(
451        match walk(v, limits, |_| {}).map_err(|e| e.shifted(lead))? {
452            Walked::Any => Condition::Any,
453            Walked::Tags(count) => Condition::Tags(TagList { text: v, count }),
454        },
455    )
456}
457
458/// Evaluates `If-None-Match` against the current representation's tag by
459/// weak comparison (RFC 9110 section 13.1.2).
460///
461/// `true` means the condition is false for the request: answer 304 (GET,
462/// HEAD) or 412 (other methods). `*` matches any current representation.
463///
464/// # Errors
465///
466/// As [`parse_condition`].
467///
468/// # Examples
469///
470/// ```
471/// use parse_net::{if_none_match, EntityTag};
472///
473/// assert!(if_none_match(b"W/\"x\"", &EntityTag::strong(b"x")?)?);
474/// assert!(!if_none_match(b"\"y\"", &EntityTag::strong(b"x")?)?);
475/// # Ok::<(), parse_net::ParseError>(())
476/// ```
477pub fn if_none_match(value: &[u8], current: &EntityTag<'_>) -> Result<bool> {
478    // One pass: the whole field is validated, and the comparison runs as each
479    // tag is read.
480    let lead = skip_ows(value);
481    let mut matched = false;
482    let walked = walk(trim_ows(value), &Limits::DEFAULT, |t| {
483        matched |= t.weak_eq(current)
484    })
485    .map_err(|e| e.shifted(lead))?;
486    Ok(matches!(walked, Walked::Any) || matched)
487}
488
489/// Evaluates `If-Match` by strong comparison (RFC 9110 section 13.1.1).
490///
491/// `true` means the precondition holds; `false` means answer 412. `*` holds
492/// when a current representation exists (`current` is `Some`).
493///
494/// # Errors
495///
496/// As [`parse_condition`].
497///
498/// # Examples
499///
500/// ```
501/// use parse_net::{if_match, EntityTag};
502///
503/// let current = EntityTag::strong(b"x")?;
504/// assert!(if_match(b"\"x\"", Some(&current))?);
505/// assert!(!if_match(b"W/\"x\"", Some(&current))?);
506/// assert!(!if_match(b"\"x\"", None)?);
507/// # Ok::<(), parse_net::ParseError>(())
508/// ```
509pub fn if_match(value: &[u8], current: Option<&EntityTag<'_>>) -> Result<bool> {
510    let lead = skip_ows(value);
511    let mut matched = false;
512    let walked = walk(trim_ows(value), &Limits::DEFAULT, |t| {
513        matched |= current.is_some_and(|c| t.strong_eq(c));
514    })
515    .map_err(|e| e.shifted(lead))?;
516    Ok(match walked {
517        Walked::Any => current.is_some(),
518        Walked::Tags(_) => matched,
519    })
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    #[test]
527    fn test_entity_tag_edge_cases() {
528        assert_eq!(EntityTag::parse(b"\"\"").unwrap().opaque(), b"");
529        assert!(EntityTag::parse(b"W/\"\"").unwrap().is_weak());
530        let k = |v: &[u8]| EntityTag::parse(v).unwrap_err().kind();
531        assert_eq!(k(b"w/\"a\""), ErrorKind::InvalidEntityTag);
532        assert_eq!(k(b"\"a\"b"), ErrorKind::TrailingData);
533        assert_eq!(k(b"\"a"), ErrorKind::InvalidEntityTag);
534        assert_eq!(k(b"W/ \"a\""), ErrorKind::InvalidEntityTag);
535        assert_eq!(k(b"\"a\x7f\""), ErrorKind::Control);
536        assert_eq!(k(b"\"a b\""), ErrorKind::InvalidEntityTag);
537        assert_eq!(k(b"abc"), ErrorKind::InvalidEntityTag);
538        assert_eq!(k(b""), ErrorKind::InvalidEntityTag);
539        assert!(EntityTag::parse(b"\"\x80\xff!\"").is_ok());
540        assert!(EntityTag::strong(b"a b").is_err());
541        assert!(EntityTag::weak(b"\"").is_err());
542    }
543
544    #[test]
545    fn test_comparison_table_rfc_9110() {
546        // RFC 9110 section 8.8.3.2.
547        let w1 = EntityTag::weak(b"1").unwrap();
548        let w2 = EntityTag::weak(b"2").unwrap();
549        let s1 = EntityTag::strong(b"1").unwrap();
550        assert!(!w1.strong_eq(&w1) && w1.weak_eq(&w1));
551        assert!(!w1.strong_eq(&w2) && !w1.weak_eq(&w2));
552        assert!(!w1.strong_eq(&s1) && w1.weak_eq(&s1));
553        assert!(s1.strong_eq(&s1) && s1.weak_eq(&s1));
554    }
555
556    #[test]
557    fn test_conditions() {
558        let l = Limits::DEFAULT;
559        assert_eq!(
560            parse_condition(b"*, \"a\"", &l).unwrap_err().kind(),
561            ErrorKind::Malformed
562        );
563        assert_eq!(
564            parse_condition(b"\"a\" \"b\"", &l).unwrap_err().kind(),
565            ErrorKind::InvalidEntityTag
566        );
567        let e = parse_condition(b"\"a\", w/\"b\"", &l).unwrap_err();
568        assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidEntityTag, 5));
569        let s = EntityTag::strong(b"b").unwrap();
570        assert!(if_none_match(b"\"a\", , \"b\"", &s).unwrap());
571        assert!(if_none_match(b"*", &s).unwrap());
572        assert!(!if_match(b"*", None).unwrap());
573        assert!(if_match(b"\"a\", \"b\"", Some(&s)).unwrap());
574        assert!(if_none_match(b"\"a", &s).is_err());
575    }
576
577    #[test]
578    fn test_condition_separators_are_not_empty_elements() {
579        // 0.6.0 counted the separator after each tag as an empty element, so
580        // a list of more than 16 tags was refused.
581        let l = Limits::DEFAULT;
582        let tags = |n: usize| {
583            (0..n)
584                .map(|i| std::format!("W/\"t{i}\""))
585                .collect::<std::vec::Vec<_>>()
586                .join(", ")
587        };
588        let count = |v: &str| match parse_condition(v.as_bytes(), &l) {
589            Ok(Condition::Tags(t)) => Ok(t.len()),
590            Ok(Condition::Any) => Ok(usize::MAX),
591            Err(e) => Err(e.kind()),
592        };
593        for n in [1, 16, 17, 100, 1024] {
594            assert_eq!(count(&tags(n)), Ok(n), "{n}");
595        }
596        assert_eq!(count(&tags(1025)), Err(ErrorKind::TooManyMembers));
597        let s = EntityTag::strong(b"t1023").unwrap();
598        assert!(if_none_match(tags(1024).as_bytes(), &s).unwrap());
599        assert!(if_match(tags(1024).replace("W/", "").as_bytes(), Some(&s)).unwrap());
600        // Empty elements are still counted, before, between, and after.
601        let empties = |n: usize| std::format!("\"a\",{}\"b\"", ", ".repeat(n));
602        assert_eq!(count(&empties(16)), Ok(2));
603        assert_eq!(count(&empties(17)), Err(ErrorKind::TooManyEmptyElements));
604        assert_eq!(count(&std::format!("{}\"a\"", ",".repeat(16))), Ok(1));
605        // After a tag the first comma is its separator; 16 more are empty.
606        assert_eq!(count(&std::format!("\"a\"{}", ",".repeat(17))), Ok(1));
607        assert_eq!(
608            count(&std::format!("\"a\"{}", ",".repeat(18))),
609            Err(ErrorKind::TooManyEmptyElements)
610        );
611        assert_eq!(count("\"a\",,\"b\","), Ok(2));
612    }
613
614    #[test]
615    fn test_write_round_trip() {
616        let mut buf = [0u8; 8];
617        let mut out = crate::SliceSink::new(&mut buf);
618        let t = EntityTag::strong(b"xyz").unwrap();
619        t.write_to(&mut out).unwrap();
620        assert_eq!(EntityTag::parse(out.written()).unwrap(), t);
621        let mut small = [0u8; 3];
622        let mut out = crate::SliceSink::new(&mut small);
623        assert_eq!(t.write_to(&mut out), Err(WriteError::Full));
624    }
625}