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(¤t))?);
505/// assert!(!if_match(b"W/\"x\"", Some(¤t))?);
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}