1#![cfg_attr(docsrs, feature(doc_cfg))]
10
11use core::ops::Range;
12
13use winnow::{
14 error::{ContextError, ErrMode},
15 prelude::*,
16 stream::LocatingSlice,
17};
18
19mod parsing;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Field<'a> {
30 inner: &'a [u8],
31 name: Range<usize>,
32 value: Range<usize>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct FieldName<'a> {
38 slice: &'a [u8],
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct FieldValue<'a> {
44 slice: &'a [u8],
45}
46
47impl<'a> Field<'a> {
48 pub fn try_from_slice(input: &'a [u8]) -> Result<Self, ErrMode<ContextError>> {
50 let mut located = LocatingSlice::new(input);
51 let indices = parsing::parse_field_indices.parse_next(&mut located)?;
52
53 Ok(Self {
54 inner: input,
55 name: indices.name,
56 value: indices.value,
57 })
58 }
59
60 #[inline]
62 pub fn as_bytes(&self) -> &'a [u8] {
63 self.inner
64 }
65
66 #[inline]
68 pub fn name_indices(&self) -> Range<usize> {
69 self.name.clone()
70 }
71
72 #[inline]
74 pub fn name(&self) -> FieldName<'a> {
75 FieldName {
76 slice: slice_range(self.inner, &self.name),
77 }
78 }
79
80 #[inline]
82 pub fn value_indices(&self) -> Range<usize> {
83 self.value.clone()
84 }
85
86 #[inline]
88 pub fn value(&self) -> FieldValue<'a> {
89 FieldValue {
90 slice: slice_range(self.inner, &self.value),
91 }
92 }
93}
94
95impl<'a> FieldName<'a> {
96 #[inline]
98 pub fn from_slice(slice: &'a [u8]) -> Self {
99 Self { slice }
100 }
101
102 pub fn try_from_slice(
104 slice: &'a [u8],
105 ) -> Result<Self, winnow::error::ParseError<&'a [u8], ContextError>> {
106 parsing::parse_field_name.parse(slice)?;
107
108 Ok(Self { slice })
109 }
110
111 #[inline]
113 pub fn as_slice(&self) -> &'a [u8] {
114 self.slice
115 }
116}
117
118impl<'a> FieldValue<'a> {
119 #[inline]
121 pub fn from_slice(slice: &'a [u8]) -> Self {
122 Self { slice }
123 }
124
125 pub fn try_from_slice(
127 slice: &'a [u8],
128 ) -> Result<Self, winnow::error::ParseError<&'a [u8], ContextError>> {
129 parsing::parse_field_value.parse(slice)?;
130
131 Ok(Self { slice })
132 }
133
134 #[inline]
136 pub fn as_slice(&self) -> &'a [u8] {
137 self.slice
138 }
139}
140
141#[inline]
142fn slice_range<'a>(bytes: &'a [u8], range: &Range<usize>) -> &'a [u8] {
143 &bytes[range.start..range.end]
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn parses_field_line() {
152 let field = Field::try_from_slice(b"content-type: text/plain; charset=utf-8").unwrap();
153
154 assert_eq!(field.as_bytes(), b"content-type: text/plain; charset=utf-8");
155 assert_eq!(field.name_indices(), 0..12);
156 assert_eq!(field.name().as_slice(), b"content-type");
157 assert_eq!(field.value_indices(), 14..39);
158 assert_eq!(field.value().as_slice(), b"text/plain; charset=utf-8");
159 }
160
161 #[test]
162 fn trims_optional_whitespace_around_value() {
163 let field = Field::try_from_slice(b"accept:\t application/json \t").unwrap();
164
165 assert_eq!(field.name().as_slice(), b"accept");
166 assert_eq!(field.value().as_slice(), b"application/json");
167 }
168
169 #[test]
170 fn allows_empty_field_value() {
171 let field = Field::try_from_slice(b"x-empty:\t ").unwrap();
172
173 assert_eq!(field.value().as_slice(), b"");
174 assert_eq!(field.value_indices(), 10..10);
175 }
176
177 #[test]
178 fn validates_name_and_value_components() {
179 assert!(FieldName::try_from_slice(b"content-type").is_ok());
180 assert!(FieldName::try_from_slice(b"content type").is_err());
181
182 assert!(FieldValue::try_from_slice(b"text/plain; charset=utf-8").is_ok());
183 assert!(FieldValue::try_from_slice(b"text/plain\r").is_err());
184 }
185}