web_bot_auth/
components.rs

1// Copyright (c) 2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3//     https://opensource.org/licenses/Apache-2.0
4
5use super::ImplementationError;
6
7/// [Signature component parameters](https://www.rfc-editor.org/rfc/rfc9421#name-http-signature-component-pa) for HTTP fields.
8#[derive(Clone, Debug, PartialEq, Hash, Eq)]
9pub enum HTTPFieldParameters {
10    /// Indicates whether this HTTP header was both a structured field value and should be strictly serialized in its
11    /// signature base representation.
12    Sf,
13    /// Indicates this HTTP header was a Dictionary structured field value and should be serialized to the `key`'s value
14    /// in the signature base representation.
15    Key(String),
16    /// Indicates all instances of this HTTP header should be wrapped as binary structures before being combined. Typically
17    /// only used when an HTTP header appears multple times and cannot be safely concatenated.
18    Bs,
19    /// Indicates this HTTP header appeared in the trailer, not the header section.
20    Tr,
21    /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response.
22    Req,
23}
24
25/// A container that represents an ordered list of signature component fields. Order is significant during signing and
26/// verifying.
27#[derive(Clone, Debug, PartialEq, Hash, Eq)]
28pub struct HTTPFieldParametersSet(pub Vec<HTTPFieldParameters>);
29
30/// Represents an HTTP header, informally.
31#[derive(Clone, Debug, PartialEq, Hash, Eq)]
32pub struct HTTPField {
33    /// Uniquely identified by name
34    pub name: String,
35    /// Parameters associated with this HTTPField. An ordered container is needed
36    /// as order of serialization matters.
37    pub parameters: HTTPFieldParametersSet,
38}
39
40impl TryFrom<sfv::Parameters> for HTTPFieldParametersSet {
41    type Error = ImplementationError;
42
43    fn try_from(value: sfv::Parameters) -> Result<Self, Self::Error> {
44        let mut output: Vec<HTTPFieldParameters> = vec![];
45
46        // we don't need `req_seen` or `tr_seen` because sfv merges duplicates into last one
47        let (mut bs_seen, mut sf_seen, mut key_seen) = (false, false, false);
48
49        for (key, bare_item) in &value {
50            match key.as_str() {
51                "sf" => {
52                    if bare_item
53                        .as_boolean()
54                        .ok_or(ImplementationError::ParsingError(
55                            "sf parameter was present on HTTP field component, but not a boolean"
56                                .into(),
57                        ))?
58                    {
59                        if bs_seen || key_seen {
60                            return Err(ImplementationError::ParsingError(
61                                "`bs`, `key` and `sf` parameter not simultaneously allowed".into(),
62                            ));
63                        }
64                        output.push(HTTPFieldParameters::Sf);
65                        sf_seen = true;
66                    }
67                }
68                "bs" => {
69                    if bare_item
70                        .as_boolean()
71                        .ok_or(ImplementationError::ParsingError(
72                            "bs parameter was present on HTTP field component, but not a boolean"
73                                .into(),
74                        ))?
75                    {
76                        if sf_seen || key_seen {
77                            return Err(ImplementationError::ParsingError(
78                                "`bs`, `key` and `sf` parameter not simultaneously allowed".into(),
79                            ));
80                        }
81                        output.push(HTTPFieldParameters::Bs);
82                        bs_seen = true;
83                    }
84                }
85                "tr" => {
86                    if bare_item
87                        .as_boolean()
88                        .ok_or(ImplementationError::ParsingError(
89                            "tr parameter was present on HTTP field component, but not a boolean"
90                                .into(),
91                        ))?
92                    {
93                        output.push(HTTPFieldParameters::Tr);
94                    }
95                }
96                "req" => {
97                    if bare_item
98                        .as_boolean()
99                        .ok_or(ImplementationError::ParsingError(
100                            "req parameter was present on HTTP field component, but not a boolean"
101                                .into(),
102                        ))?
103                    {
104                        output.push(HTTPFieldParameters::Req);
105                    }
106                }
107                "key" => {
108                    if sf_seen || bs_seen {
109                        return Err(ImplementationError::ParsingError(
110                            "`bs`, `key` and `sf` parameter not simultaneously allowed".into(),
111                        ));
112                    }
113                    let name = bare_item
114                        .as_string()
115                        .ok_or(ImplementationError::ParsingError(
116                            "key parameter was present on HTTP field component, but not a string"
117                                .into(),
118                        ))?
119                        .as_str();
120                    output.push(HTTPFieldParameters::Key(name.to_string()));
121                    key_seen = true;
122                }
123                parameter_name => {
124                    return Err(ImplementationError::ParsingError(format!(
125                        "Unexpected parameter `{parameter_name}` when parsing HTTP field component, only sf / bs / key / req / tr allowed"
126                    )));
127                }
128            }
129        }
130        Ok(HTTPFieldParametersSet(output))
131    }
132}
133
134impl TryFrom<HTTPFieldParametersSet> for sfv::Parameters {
135    type Error = ImplementationError;
136
137    fn try_from(value: HTTPFieldParametersSet) -> Result<Self, Self::Error> {
138        let mut parameters = sfv::Parameters::new();
139
140        // Test for duplicates
141        let (mut req_set, mut bs_set, mut sf_set, mut key_set, mut tr_set) =
142            (false, false, false, false, false);
143
144        for param in &value.0 {
145            match param {
146                HTTPFieldParameters::Sf => {
147                    if sf_set {
148                        return Err(ImplementationError::ParsingError(
149                            "`sf` parameter not allowed as duplicate".into(),
150                        ));
151                    }
152                    if bs_set || key_set {
153                        return Err(ImplementationError::ParsingError(
154                            "`bs`, `key` and `sf` parameter not simultaneously allowed".into(),
155                        ));
156                    }
157                    let key = sfv::KeyRef::constant("sf").to_owned();
158                    parameters.insert(key, sfv::BareItem::Boolean(true));
159                    sf_set = true;
160                }
161                HTTPFieldParameters::Bs => {
162                    if bs_set {
163                        return Err(ImplementationError::ParsingError(
164                            "`bs` parameter not allowed as duplicate".into(),
165                        ));
166                    }
167                    if sf_set || key_set {
168                        return Err(ImplementationError::ParsingError(
169                            "`bs`, `key` and `sf` parameter not simultaneously allowed".into(),
170                        ));
171                    }
172                    let key = sfv::KeyRef::constant("bs").to_owned();
173                    parameters.insert(key, sfv::BareItem::Boolean(true));
174                    bs_set = true;
175                }
176                HTTPFieldParameters::Tr => {
177                    if tr_set {
178                        return Err(ImplementationError::ParsingError(
179                            "`tr` parameter not allowed as duplicate".into(),
180                        ));
181                    }
182                    let key = sfv::KeyRef::constant("tr").to_owned();
183                    parameters.insert(key, sfv::BareItem::Boolean(true));
184                    tr_set = true;
185                }
186                HTTPFieldParameters::Req => {
187                    if req_set {
188                        return Err(ImplementationError::ParsingError(
189                            "`tr` parameter not allowed as duplicate".into(),
190                        ));
191                    }
192                    let key = sfv::KeyRef::constant("req").to_owned();
193                    parameters.insert(key, sfv::BareItem::Boolean(true));
194                    req_set = true;
195                }
196                HTTPFieldParameters::Key(name) => {
197                    if key_set {
198                        return Err(ImplementationError::ParsingError(
199                            "`key` parameter not allowed as duplicate".into(),
200                        ));
201                    }
202                    let key = sfv::KeyRef::constant("key").to_owned();
203                    let value = sfv::String::from_string(name.clone())
204                        .map_err(|(e, _)| ImplementationError::ImpossibleSfvError(e))?;
205                    parameters.insert(key, sfv::BareItem::String(value));
206                    key_set = true;
207                }
208            }
209        }
210        Ok(parameters)
211    }
212}
213
214impl TryFrom<HTTPField> for sfv::Item {
215    type Error = ImplementationError;
216
217    fn try_from(value: HTTPField) -> Result<Self, Self::Error> {
218        let error_message_fragment = format!(
219            "Could not coerce HTTP field name `{}` into a valid sfv Item: ",
220            &value.name
221        );
222
223        Ok(sfv::Item {
224            bare_item: sfv::BareItem::String(sfv::String::from_string(value.name).map_err(
225                |(_, s)| {
226                    ImplementationError::ParsingError(format!("{error_message_fragment}: {s}"))
227                },
228            )?),
229            params: value.parameters.try_into()?,
230        })
231    }
232}
233
234/// [Signature component parameters](https://www.rfc-editor.org/rfc/rfc9421#name-http-signature-component-pa)
235/// specifically for the `@query-params` derived component.
236#[derive(Clone, Debug, PartialEq, Hash, Eq)]
237pub enum QueryParamParameters {
238    /// Unique identifier for the query param
239    Name(String),
240    /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response.
241    Req,
242}
243
244/// A list of derived components, used to specify attributes of an HTTP message that otherwise can't be referenced
245/// via an HTTP header but nevertheless can be used in generating a signature base. Each component here, with the sole
246/// exception of `QueryParameters`, accepts a single component parameter `req.
247#[derive(Clone, Debug, PartialEq, Hash, Eq)]
248pub enum DerivedComponent {
249    /// Represents `@authority` derived component
250    Authority {
251        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
252        req: bool,
253    },
254    /// Represents `@target-uri` derived component
255    TargetUri {
256        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
257        req: bool,
258    },
259    /// Represents `@request-target` derived component
260    RequestTarget {
261        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
262        req: bool,
263    },
264    /// Represents `@method` derived component
265    Method {
266        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
267        req: bool,
268    },
269    /// Represents `@path` derived component
270    Path {
271        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
272        req: bool,
273    },
274    /// Represents `@scheme` derived component
275    Scheme {
276        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
277        req: bool,
278    },
279    /// Represents `@query` derived component
280    Query {
281        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
282        req: bool,
283    },
284    /// Represents the `@query-params` derived component
285    QueryParams {
286        /// The list of parameters associated with this field
287        parameters: QueryParamParametersSet,
288    },
289    /// Represents `@status` derived component
290    Status {
291        /// Indicates this HTTP header value was obtained from the request. Typically only used in a signed response
292        req: bool,
293    },
294}
295
296/// A container that represents an ordered list of signature component fields. Order is significant during signing and
297/// verifying.
298#[derive(Clone, Debug, PartialEq, Hash, Eq)]
299pub struct QueryParamParametersSet(pub Vec<QueryParamParameters>);
300
301impl TryFrom<sfv::Parameters> for QueryParamParametersSet {
302    type Error = ImplementationError;
303
304    fn try_from(value: sfv::Parameters) -> Result<Self, Self::Error> {
305        let mut output: Vec<QueryParamParameters> = vec![];
306
307        let (mut req_seen, mut name_seen) = (false, false);
308
309        for (key, bare_item) in &value {
310            match key.as_str() {
311                "req" => {
312                    if bare_item
313                        .as_boolean()
314                        .ok_or(ImplementationError::ParsingError(
315                            "`req` parameter was present on `@query-param`, but not a boolean"
316                                .into(),
317                        ))?
318                    {
319                        if req_seen {
320                            return Err(ImplementationError::ParsingError(
321                                "`req` parameter not allowed as duplicate".into(),
322                            ));
323                        }
324                        output.push(QueryParamParameters::Req);
325                        req_seen = true;
326                    }
327                }
328                "name" => {
329                    let name = bare_item
330                        .as_string()
331                        .ok_or(ImplementationError::ParsingError(
332                            "`name` parameter was present on `@query-param`, but not a string"
333                                .into(),
334                        ))?
335                        .as_str();
336                    if name_seen {
337                        return Err(ImplementationError::ParsingError(
338                            "`name` parameter not allowed as duplicate".into(),
339                        ));
340                    }
341                    output.push(QueryParamParameters::Name(name.to_string()));
342                    name_seen = true;
343                }
344                parameter_name => {
345                    return Err(ImplementationError::ParsingError(format!(
346                        "Unexpected parameter `{parameter_name}` when parsing `@query-param, only name / req allowed"
347                    )));
348                }
349            }
350        }
351        Ok(QueryParamParametersSet(output.into_iter().collect()))
352    }
353}
354
355impl TryFrom<QueryParamParametersSet> for sfv::Parameters {
356    type Error = ImplementationError;
357
358    fn try_from(value: QueryParamParametersSet) -> Result<Self, Self::Error> {
359        let mut sfv_parameters = sfv::Parameters::new();
360        let (mut req_seen, mut name_seen) = (false, false);
361        for param in &value.0 {
362            match param {
363                QueryParamParameters::Req => {
364                    if req_seen {
365                        return Err(ImplementationError::ParsingError(
366                            "`req` parameter not allowed as duplicate".into(),
367                        ));
368                    }
369                    let key = sfv::KeyRef::constant("req").to_owned();
370                    sfv_parameters.insert(key, sfv::BareItem::Boolean(true));
371                    req_seen = true;
372                }
373                QueryParamParameters::Name(name) => {
374                    if name_seen {
375                        return Err(ImplementationError::ParsingError(
376                            "`name` parameter not allowed as duplicate".into(),
377                        ));
378                    }
379                    let key = sfv::KeyRef::constant("name").to_owned();
380                    let value = sfv::String::from_string(name.clone())
381                        .map_err(|(_, s)| ImplementationError::ParsingError(format!(
382                            "Could not coerce `@query-param` parameter `{}` into a valid sfv Parameter: {}",
383                            &name, s
384                        )))?;
385                    sfv_parameters.insert(key, sfv::BareItem::String(value));
386                    name_seen = true;
387                }
388            }
389        }
390        Ok(sfv_parameters)
391    }
392}
393
394impl TryFrom<DerivedComponent> for sfv::Item {
395    type Error = ImplementationError;
396
397    fn try_from(value: DerivedComponent) -> Result<Self, Self::Error> {
398        fn template(name: &str, req: bool) -> Result<sfv::Item, ImplementationError> {
399            let mut parameters = sfv::Parameters::new();
400            if req {
401                let key = sfv::KeyRef::constant("req").to_owned();
402                parameters.insert(key, sfv::BareItem::Boolean(true));
403            }
404
405            Ok(sfv::Item {
406                bare_item: sfv::BareItem::String(
407                    sfv::String::from_string(name.to_string())
408                        .map_err(|(e, _)| ImplementationError::ImpossibleSfvError(e))?,
409                ),
410                params: parameters,
411            })
412        }
413
414        match value {
415            DerivedComponent::Authority { req } => template("@authority", req),
416            DerivedComponent::Method { req } => template("@method", req),
417            DerivedComponent::Path { req } => template("@path", req),
418            DerivedComponent::TargetUri { req } => template("@target-uri", req),
419            DerivedComponent::RequestTarget { req } => template("@request-target", req),
420            DerivedComponent::Scheme { req } => template("@scheme", req),
421            DerivedComponent::Status { req } => template("@status", req),
422            DerivedComponent::Query { req } => template("@query", req),
423            DerivedComponent::QueryParams { parameters } => {
424                let mut sfv_parameters = sfv::Parameters::new();
425                for param in &parameters.0 {
426                    match param {
427                        QueryParamParameters::Req => {
428                            let key = sfv::KeyRef::constant("req").to_owned();
429                            sfv_parameters.insert(key, sfv::BareItem::Boolean(true));
430                        }
431                        QueryParamParameters::Name(name) => {
432                            let key = sfv::KeyRef::constant("name").to_owned();
433                            let value = sfv::String::from_string(name.clone())
434                                .map_err(|(e, _)| ImplementationError::ImpossibleSfvError(e))?;
435                            sfv_parameters.insert(key, sfv::BareItem::String(value));
436                        }
437                    }
438                }
439
440                Ok(sfv::Item {
441                    bare_item: sfv::BareItem::String(
442                        sfv::String::from_string("@query-param".to_string())
443                            .map_err(|(e, _)| ImplementationError::ImpossibleSfvError(e))?,
444                    ),
445                    params: parameters.try_into()?,
446                })
447            }
448        }
449    }
450}
451
452/// Represents *any* component that can be used during message signing or verifying. See documentation
453/// about each wrapped variant to learn more.
454#[derive(Clone, Debug, PartialEq, Hash, Eq)]
455pub enum CoveredComponent {
456    /// Represents an HTTP field that can be used as part of the `Signature-Input` field
457    HTTP(HTTPField),
458    /// Represents a derived component - message data not accessible as an HTTP header -
459    /// that can be used as part of the `Signature-Input` field.
460    Derived(DerivedComponent),
461}
462
463impl TryFrom<sfv::Item> for CoveredComponent {
464    type Error = ImplementationError;
465
466    fn try_from(value: sfv::Item) -> Result<Self, Self::Error> {
467        fn fetch_req(
468            params: sfv::Parameters,
469            component_name: &str,
470        ) -> Result<bool, ImplementationError> {
471            match params.len() {
472                0 => Ok(false),
473                1 => {
474                    for (key, val) in params {
475                        if key.as_str() == "req" {
476                            return val.as_boolean().ok_or(ImplementationError::ParsingError(
477                                format!(
478                                    "`req` parameter was present on `{component_name}`, but not a boolean"
479                                ),
480                            ));
481                        }
482                    }
483                    Err(ImplementationError::ParsingError(format!(
484                        "Encountered another parameter name on `{component_name}`, but only `req` allowed"
485                    )))
486                }
487                2.. => Err(ImplementationError::ParsingError(format!(
488                    "Encountered multiple parameter names on `{component_name}`, but only `req` allowed"
489                ))),
490            }
491        }
492
493        match value.bare_item {
494            sfv::BareItem::String(inner_string) => {
495                let component = match inner_string.as_str() {
496                    "@authority" => CoveredComponent::Derived(DerivedComponent::Authority {
497                        req: fetch_req(value.params, "@authority")?,
498                    }),
499                    "@method" => CoveredComponent::Derived(DerivedComponent::Method {
500                        req: fetch_req(value.params, "@method")?,
501                    }),
502                    "@path" => CoveredComponent::Derived(DerivedComponent::Path {
503                        req: fetch_req(value.params, "@path")?,
504                    }),
505                    "@target-uri" => CoveredComponent::Derived(DerivedComponent::TargetUri {
506                        req: fetch_req(value.params, "@target-uri")?,
507                    }),
508                    "@scheme" => CoveredComponent::Derived(DerivedComponent::Scheme {
509                        req: fetch_req(value.params, "@scheme")?,
510                    }),
511                    "@status" => CoveredComponent::Derived(DerivedComponent::Status {
512                        req: fetch_req(value.params, "@status")?,
513                    }),
514                    "@query" => CoveredComponent::Derived(DerivedComponent::Query {
515                        req: fetch_req(value.params, "@query")?,
516                    }),
517                    "@request-target" => {
518                        CoveredComponent::Derived(DerivedComponent::RequestTarget {
519                            req: fetch_req(value.params, "@request-target")?,
520                        })
521                    }
522                    "@query-param" => {
523                        let component = DerivedComponent::QueryParams {
524                            parameters: value.params.try_into()?,
525                        };
526
527                        return Ok(CoveredComponent::Derived(component));
528                    }
529                    field if field.starts_with('@') => {
530                        return Err(ImplementationError::ParsingError(format!(
531                            "Encountered invald derived component name `{field}`, consult RFC 9421 for valid names"
532                        )));
533                    }
534                    http => {
535                        return Ok(CoveredComponent::HTTP(HTTPField {
536                            name: http.to_string().to_ascii_lowercase(),
537                            parameters: value.params.try_into()?,
538                        }));
539                    }
540                };
541
542                Ok(component)
543            }
544            other_type => Err(ImplementationError::ParsingError(format!(
545                "Expected a stringified sfv::BareItem when parsing into a CoveredComponent, but encountered a different type {other_type:?}"
546            ))),
547        }
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use sfv::SerializeValue;
554
555    use super::*;
556
557    #[test]
558    fn test_parsing_valid_derived_components() {
559        for case in [
560            r#""@authority""#,
561            r#""@authority";req"#,
562            r#""@method""#,
563            r#""@method";req"#,
564            r#""@path""#,
565            r#""@path";req"#,
566            r#""@target-uri""#,
567            r#""@target-uri";req"#,
568            r#""@scheme""#,
569            r#""@scheme";req"#,
570            r#""@status""#,
571            r#""@status";req"#,
572            r#""@request-target""#,
573            r#""@request-target";req"#,
574            r#""@query-param";name="foo""#,
575            r#""@query-param";name="foo";req"#,
576        ]
577        .iter()
578        {
579            let component: CoveredComponent = sfv::Parser::new(case)
580                .parse_item()
581                .unwrap()
582                .try_into()
583                .unwrap();
584            let CoveredComponent::Derived(derived) = component else {
585                panic!("Expected derived components, got HTTP")
586            };
587            let value: sfv::Item = derived.try_into().unwrap();
588            assert_eq!(&value.serialize_value(), case);
589        }
590    }
591
592    #[test]
593    fn test_parsing_valid_http_fields() {
594        for case in [
595            r#""content-length""#,
596            r#""content-length";sf"#,
597            r#""content-length";bs"#,
598            r#""content-length";tr"#,
599            r#""content-length";req"#,
600            r#""content-length";key="foo""#,
601            r#""Content-Length";req"#,
602        ]
603        .iter()
604        {
605            let component: CoveredComponent = sfv::Parser::new(case)
606                .parse_item()
607                .unwrap()
608                .try_into()
609                .unwrap();
610            let CoveredComponent::HTTP(http) = component else {
611                panic!("Expected HTTP field, got derived")
612            };
613            let value: sfv::Item = http.try_into().unwrap();
614            assert_eq!(value.serialize_value(), case.to_ascii_lowercase());
615        }
616    }
617
618    #[test]
619    fn test_parsing_invalid_http_fields() {
620        for case in [
621            r#""content-length";sf;bs"#,
622            r#""content-length";bs;sf"#,
623            r#""content-length";req;tr;key"#,
624            r#""content-length";key=1"#,
625            r#""content-length";sf;req;tr;key="foo""#,
626            r#""content-length";bs;req;tr;key="foo""#,
627        ]
628        .iter()
629        {
630            let item: sfv::Item = sfv::Parser::new(case).parse_item().unwrap();
631            CoveredComponent::try_from(item).expect_err("This case should error");
632        }
633    }
634
635    #[test]
636    fn test_known_edge_cases_in_http_parsing() {
637        for (case, expected) in [
638            (r#""content-length";sf;sf"#, r#""content-length";sf"#),
639            (r#""content-length";bs;bs"#, r#""content-length";bs"#),
640            (r#""content-length";req;req"#, r#""content-length";req"#),
641            (r#""content-length";tr;tr"#, r#""content-length";tr"#),
642            (
643                r#""content-length";key="foo";key="bar""#,
644                r#""content-length";key="bar""#,
645            ),
646        ]
647        .iter()
648        {
649            {
650                let component: CoveredComponent = sfv::Parser::new(case)
651                    .parse_item()
652                    .unwrap()
653                    .try_into()
654                    .unwrap();
655                let CoveredComponent::HTTP(http) = component else {
656                    panic!("Expected HTTP field, got derived")
657                };
658                let value: sfv::Item = http.try_into().unwrap();
659                assert_eq!(value.serialize_value(), expected.to_ascii_lowercase());
660            }
661        }
662    }
663
664    #[test]
665    fn test_known_edge_cases_in_derived_component_parsing() {
666        for (case, expected) in [(
667            r#""@query-param";name="foo";name="bar""#,
668            r#""@query-param";name="bar""#,
669        )]
670        .iter()
671        {
672            {
673                let component: CoveredComponent = sfv::Parser::new(case)
674                    .parse_item()
675                    .unwrap()
676                    .try_into()
677                    .unwrap();
678                let CoveredComponent::Derived(derived) = component else {
679                    panic!("Expected derived field, got HTTP")
680                };
681                let value: sfv::Item = derived.try_into().unwrap();
682                assert_eq!(value.serialize_value(), expected.to_ascii_lowercase());
683            }
684        }
685    }
686
687    #[test]
688    fn test_parsing_invalid_derived_components() {
689        for case in [
690            r#""@notacomponent""#,
691            r#""@authority";req=true"#,
692            r#""@authority";req=1"#,
693            r#""@authority";req=:fff:"#,
694            r#""@authority";req="ddd""#,
695            r#""@authority";invalid"#,
696            r#""@method";invalid"#,
697        ]
698        .iter()
699        {
700            let item: sfv::Item = sfv::Parser::new(case).parse_item().unwrap();
701            CoveredComponent::try_from(item).expect_err("This case should error");
702        }
703    }
704
705    #[test]
706    fn test_http_parameter_parsing_does_not_allow_duplicates_or_invalid_sets() {
707        for content in [
708            vec![HTTPFieldParameters::Req, HTTPFieldParameters::Req],
709            vec![HTTPFieldParameters::Sf, HTTPFieldParameters::Sf],
710            vec![HTTPFieldParameters::Bs, HTTPFieldParameters::Bs],
711            vec![HTTPFieldParameters::Tr, HTTPFieldParameters::Tr],
712            vec![
713                HTTPFieldParameters::Key("foo".into()),
714                HTTPFieldParameters::Key("bar".into()),
715            ],
716        ]
717        .into_iter()
718        {
719            sfv::Parameters::try_from(HTTPFieldParametersSet(content))
720                .expect_err("This case should error");
721        }
722    }
723
724    #[test]
725    fn test_query_param_parsing_does_not_allow_duplicates_or_invalid_sets() {
726        for content in [
727            vec![QueryParamParameters::Req, QueryParamParameters::Req],
728            vec![
729                QueryParamParameters::Name("foo".into()),
730                QueryParamParameters::Name("bar".into()),
731            ],
732        ]
733        .into_iter()
734        {
735            sfv::Parameters::try_from(QueryParamParametersSet(content))
736                .expect_err("This case should error");
737        }
738    }
739}