1use super::ImplementationError;
6
7#[derive(Clone, Debug, PartialEq, Hash, Eq)]
9pub enum HTTPFieldParameters {
10 Sf,
13 Key(String),
16 Bs,
19 Tr,
21 Req,
23}
24
25#[derive(Clone, Debug, PartialEq, Hash, Eq)]
28pub struct HTTPFieldParametersSet(pub Vec<HTTPFieldParameters>);
29
30#[derive(Clone, Debug, PartialEq, Hash, Eq)]
32pub struct HTTPField {
33 pub name: String,
35 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 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 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#[derive(Clone, Debug, PartialEq, Hash, Eq)]
237pub enum QueryParamParameters {
238 Name(String),
240 Req,
242}
243
244#[derive(Clone, Debug, PartialEq, Hash, Eq)]
248pub enum DerivedComponent {
249 Authority {
251 req: bool,
253 },
254 TargetUri {
256 req: bool,
258 },
259 RequestTarget {
261 req: bool,
263 },
264 Method {
266 req: bool,
268 },
269 Path {
271 req: bool,
273 },
274 Scheme {
276 req: bool,
278 },
279 Query {
281 req: bool,
283 },
284 QueryParams {
286 parameters: QueryParamParametersSet,
288 },
289 Status {
291 req: bool,
293 },
294}
295
296#[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 ¶meters.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#[derive(Clone, Debug, PartialEq, Hash, Eq)]
455pub enum CoveredComponent {
456 HTTP(HTTPField),
458 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}