1use std::{borrow::Cow, fmt, ops::Deref, str::FromStr};
2
3#[cfg(feature = "serde")]
4use serde::Serialize;
5
6use crate::error::{InvalidNameError, InvalidValueError};
7
8#[derive(Debug, PartialEq, Eq, Clone)]
22#[cfg_attr(feature = "serde", derive(Serialize))]
23pub struct Attribute<'a> {
24 pub name: Name<'a>,
26 #[cfg_attr(feature = "serde", serde(rename = "values"))]
28 pub value: Value<'a>,
29}
30
31impl<'a> Attribute<'a> {
32 #[must_use]
34 pub fn new(name: Name<'a>, value: Value<'a>) -> Self {
35 Self { name, value }
36 }
37
38 #[cfg(test)]
39 pub(crate) fn unchecked_single<V>(name: &'a str, value: V) -> Self
40 where
41 V: Into<Option<&'a str>>,
42 {
43 let name = Name::unchecked(name);
44 let value = Value::unchecked_single(value);
45 Self { name, value }
46 }
47
48 #[cfg(test)]
49 pub(crate) fn unchecked_multi<I, V>(name: &'a str, values: I) -> Self
50 where
51 I: IntoIterator<Item = V>,
52 V: Into<Option<&'a str>>,
53 {
54 let name = Name::unchecked(name);
55 let value = Value::unchecked_multi(values);
56 Self { name, value }
57 }
58}
59
60impl fmt::Display for Attribute<'_> {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 let values = self.value.values();
63
64 let first_value = values.first().expect("must contain at least one value");
65 match first_value {
66 Some(value) => {
67 writeln!(f, "{:16}{}", format!("{}:", self.name), value)?;
68 }
69 None => writeln!(f, "{}:", self.name)?,
70 }
71
72 let remaining_values = &values[1..];
73 for value in remaining_values {
74 match value {
75 Some(value) => {
76 writeln!(f, "{:16}{}", " ", value)?;
77 }
78 None => {
79 writeln!(f, " ")?;
80 }
81 }
82 }
83
84 Ok(())
85 }
86}
87
88#[derive(Debug, PartialEq, Eq, Clone)]
90#[cfg_attr(feature = "serde", derive(Serialize), serde(transparent))]
91pub struct Name<'a>(Cow<'a, str>);
92
93impl<'a> Name<'a> {
94 pub(crate) fn unchecked(name: &'a str) -> Self {
95 Self(Cow::Borrowed(name))
96 }
97
98 fn validate(name: &str) -> Result<(), InvalidNameError> {
99 if name.trim().is_empty() {
100 return Err(InvalidNameError::Empty);
101 } else if !name.is_ascii() {
102 return Err(InvalidNameError::NonAscii);
103 } else if !name.chars().next().unwrap().is_ascii_alphabetic() {
104 return Err(InvalidNameError::NonAsciiAlphabeticFirstChar);
105 } else if !name.chars().last().unwrap().is_ascii_alphanumeric() {
106 return Err(InvalidNameError::NonAsciiAlphanumericLastChar);
107 }
108
109 Ok(())
110 }
111}
112
113impl FromStr for Name<'_> {
114 type Err = InvalidNameError;
115
116 fn from_str(name: &str) -> Result<Self, Self::Err> {
124 Self::validate(name)?;
125 Ok(Self(Cow::Owned(name.to_string())))
126 }
127}
128
129impl Deref for Name<'_> {
130 type Target = str;
131
132 fn deref(&self) -> &Self::Target {
133 self.0.as_ref()
134 }
135}
136
137impl PartialEq<&str> for Name<'_> {
138 fn eq(&self, other: &&str) -> bool {
139 self.0 == *other
140 }
141}
142
143impl fmt::Display for Name<'_> {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 write!(f, "{}", self.0)
146 }
147}
148
149#[derive(Debug, PartialEq, Eq, Clone)]
153#[cfg_attr(
154 feature = "serde",
155 derive(Serialize),
156 serde(into = "Vec<Option<String>>")
157)]
158pub enum Value<'a> {
159 SingleLine(Option<Cow<'a, str>>),
173 MultiLine(Vec<Option<Cow<'a, str>>>),
189}
190
191impl<'a> Value<'a> {
192 pub(crate) fn unchecked_single<V>(value: V) -> Self
195 where
196 V: Into<Option<&'a str>>,
197 {
198 Self::SingleLine(value.into().and_then(coerce_empty_value).map(Cow::Borrowed))
199 }
200
201 pub(crate) fn unchecked_multi<I, V>(values: I) -> Self
204 where
205 I: IntoIterator<Item = V>,
206 V: Into<Option<&'a str>>,
207 {
208 let s = Self::MultiLine(
209 values
210 .into_iter()
211 .map(|v| v.into().and_then(coerce_empty_value).map(Cow::Borrowed))
212 .collect(),
213 );
214 assert!(s.lines() > 1, "multi line values need at least two lines");
215 s
216 }
217
218 fn validate(value: &str) -> Result<(), InvalidValueError> {
219 value.chars().try_for_each(Self::validate_char)
220 }
221
222 #[inline]
225 pub(crate) fn validate_char(c: char) -> Result<(), InvalidValueError> {
226 if !is_extended_ascii(c) {
227 return Err(InvalidValueError::NonExtendedAscii);
228 } else if c.is_ascii_control() {
229 return Err(InvalidValueError::ContainsControlChar);
230 }
231
232 Ok(())
233 }
234
235 #[must_use]
255 pub fn lines(&self) -> usize {
256 match &self {
257 Self::SingleLine(_) => 1,
258 Self::MultiLine(values) => values.len(),
259 }
260 }
261
262 fn values(&'a self) -> Vec<Option<&'a str>> {
263 match self {
264 Value::SingleLine(value) => {
265 vec![value.as_ref().map(std::convert::AsRef::as_ref)]
266 }
267 Value::MultiLine(values) => values
268 .iter()
269 .map(|v| v.as_ref().map(std::convert::AsRef::as_ref))
270 .collect(),
271 }
272 }
273
274 pub fn with_content(&self) -> Vec<&str> {
291 match self {
292 Self::SingleLine(v) => {
293 if let Some(v) = v {
294 vec![v]
295 } else {
296 vec![]
297 }
298 }
299 Self::MultiLine(v) => v.iter().flatten().map(AsRef::as_ref).collect(),
300 }
301 }
302}
303
304impl FromStr for Value<'_> {
305 type Err = InvalidValueError;
306
307 fn from_str(value: &str) -> Result<Self, Self::Err> {
314 Self::validate(value)?;
315 Ok(Self::SingleLine(
316 coerce_empty_value(value).map(|value| Cow::Owned(value.to_string())),
317 ))
318 }
319}
320
321impl TryFrom<Vec<&str>> for Value<'_> {
322 type Error = InvalidValueError;
323
324 fn try_from(values: Vec<&str>) -> Result<Self, Self::Error> {
337 if values.len() == 1 {
338 let value = values[0].parse()?;
339 return Ok(value);
340 }
341 let values = values
342 .into_iter()
343 .map(|v| {
344 Self::validate(v)?;
345 Ok(coerce_empty_value(v).map(std::string::ToString::to_string))
346 })
347 .collect::<Result<Vec<Option<String>>, InvalidValueError>>()?;
348
349 Ok(Self::MultiLine(
350 values.into_iter().map(|v| v.map(Cow::Owned)).collect(),
351 ))
352 }
353}
354
355#[allow(clippy::from_over_into)]
356impl Into<Vec<Option<String>>> for Value<'_> {
357 fn into(self) -> Vec<Option<String>> {
358 match self {
359 Self::SingleLine(value) => {
360 vec![value.map(|v| v.to_string())]
361 }
362 Self::MultiLine(values) => values
363 .into_iter()
364 .map(|v| v.map(|v| v.to_string()))
365 .collect(),
366 }
367 }
368}
369
370impl PartialEq<&str> for Value<'_> {
371 fn eq(&self, other: &&str) -> bool {
372 match &self {
373 Self::MultiLine(_) => false,
374 Self::SingleLine(value) => match value {
375 Some(value) => value == *other,
376 None => coerce_empty_value(other).is_none(),
377 },
378 }
379 }
380}
381
382impl PartialEq<Vec<&str>> for Value<'_> {
383 fn eq(&self, other: &Vec<&str>) -> bool {
384 if self.lines() != other.len() {
385 return false;
386 }
387
388 match &self {
389 Self::SingleLine(value) => {
390 let s = value.as_deref();
391 let other_coerced = coerce_empty_value(other[0]);
392 s == other_coerced
393 }
394 Self::MultiLine(values) => {
395 let s = values.iter().map(|v| v.as_deref());
396 let other_coerced = other.iter().map(|&v| coerce_empty_value(v));
397 s.eq(other_coerced)
398 }
399 }
400 }
401}
402
403impl PartialEq<Vec<Option<&str>>> for Value<'_> {
404 fn eq(&self, other: &Vec<Option<&str>>) -> bool {
405 if self.lines() != other.len() {
406 return false;
407 }
408
409 match &self {
410 Self::SingleLine(value) => {
411 let s = value.as_deref();
412 let other = other[0];
413 s == other
414 }
415 Self::MultiLine(values) => {
416 let s = values.iter().map(|v| v.as_deref());
417 let other = other.iter().map(|v| v.as_deref());
418 s.eq(other)
419 }
420 }
421 }
422}
423
424fn coerce_empty_value<S>(value: S) -> Option<S>
426where
427 S: AsRef<str>,
428{
429 if value.as_ref().trim().is_empty() {
430 None
431 } else {
432 Some(value)
433 }
434}
435
436#[inline]
438fn is_extended_ascii(char: char) -> bool {
439 matches!(char, '\u{0000}'..='\u{00FF}')
440}
441
442#[cfg(test)]
443mod tests {
444 use proptest::prelude::*;
445 use rstest::*;
446 #[cfg(feature = "serde")]
447 use serde_test::{assert_ser_tokens, Token};
448
449 use super::*;
450
451 #[rstest]
452 #[case(
453 Attribute::unchecked_single("ASNumber", "32934"),
454 "ASNumber: 32934\n"
455 )]
456 #[case(Attribute::unchecked_single("ASNumber", None), "ASNumber:\n")]
457 #[case(
458 Attribute::unchecked_single("ASName", "FACEBOOK"),
459 "ASName: FACEBOOK\n"
460 )]
461 #[case(
462 Attribute::unchecked_single("RegDate", "2004-08-24"),
463 "RegDate: 2004-08-24\n"
464 )]
465 #[case(
466 Attribute::unchecked_single("Ref", "https://rdap.arin.net/registry/autnum/32934"),
467 "Ref: https://rdap.arin.net/registry/autnum/32934\n"
468 )]
469 fn attribute_display_single_line(#[case] attribute: Attribute, #[case] expected: &str) {
470 assert_eq!(attribute.to_string(), expected);
471 }
472
473 #[rstest]
474 #[case(
475 Attribute::unchecked_multi(
476 "remarks",
477 [
478 "AS1299 is matching RPKI validation state and reject",
479 "invalid prefixes from peers and customers."
480 ]
481
482 ),
483 concat!(
484 "remarks: AS1299 is matching RPKI validation state and reject\n",
485 " invalid prefixes from peers and customers.\n",
486 )
487 )]
488 #[case(
489 Attribute::unchecked_multi(
490 "remarks",
491 [
492 None,
493 None
494 ]
495 ),
496 concat!(
497 "remarks:\n",
498 " \n",
499 )
500 )]
501 fn attribute_display_multi_line(#[case] attribute: Attribute, #[case] expected: &str) {
502 assert_eq!(attribute.to_string(), expected);
503 }
504
505 #[rstest]
506 #[case(
507 Attribute::unchecked_single("ASNumber", "32934"),
508 &[
509 Token::Struct { name: "Attribute", len: 2 },
510 Token::Str("name"),
511 Token::Str("ASNumber"),
512 Token::Str("values"),
513 Token::Seq { len: Some(1) },
514 Token::Some,
515 Token::Str("32934"),
516 Token::SeqEnd,
517 Token::StructEnd,
518 ],
519 )]
520 #[case(
521 Attribute::unchecked_multi(
522 "address",
523 ["Packet Street 6", "128 Series of Tubes", "Internet"]
524 ),
525 &[
526 Token::Struct { name: "Attribute", len: 2 },
527 Token::Str("name"),
528 Token::Str("address"),
529 Token::Str("values"),
530 Token::Seq { len: Some(3) },
531 Token::Some,
532 Token::Str("Packet Street 6"),
533 Token::Some,
534 Token::Str("128 Series of Tubes"),
535 Token::Some,
536 Token::Str("Internet"),
537 Token::SeqEnd,
538 Token::StructEnd,
539 ],
540 )]
541 #[cfg(feature = "serde")]
542 fn attribute_serialize(#[case] attribute: Attribute, #[case] expected: &[Token]) {
543 assert_ser_tokens(&attribute, expected);
544 }
545
546 #[test]
547 fn name_display() {
548 let name_display = Name::unchecked("address").to_string();
549 assert_eq!(name_display, "address");
550 }
551
552 #[rstest]
553 #[case("role")]
554 #[case("person")]
555 fn name_deref(#[case] s: &str) {
556 let name = Name::unchecked(s);
557 assert_eq!(*name, *s);
558 }
559
560 #[rstest]
561 #[case("role")]
562 #[case("person")]
563 fn name_from_str(#[case] s: &str) {
564 assert_eq!(Name::from_str(s).unwrap(), Name(Cow::Owned(s.to_string())));
565 }
566
567 proptest! {
568 #[test]
569 fn name_from_str_space_only_is_err(n in r"\s") {
570 assert!(Name::from_str(&n).is_err());
571 }
572
573 #[test]
574 fn name_from_str_non_ascii_is_err(n in r"[^[[:ascii:]]]") {
575 assert!(Name::from_str(&n).is_err());
576 }
577
578 #[test]
579 fn name_from_str_non_letter_first_char_is_err(n in r"[^a-zA-Z][[:ascii:]]*") {
580 assert!(Name::from_str(&n).is_err());
581 }
582
583 #[test]
584 fn name_from_str_non_letter_or_digit_last_char_is_err(n in r"[[:ascii:]]*[^a-zA-Z0-9]") {
585 assert!(Name::from_str(&n).is_err());
586 }
587 }
588
589 #[rstest]
590 #[case(Name::unchecked("ASNumber"), Token::Str("ASNumber"))]
591 #[cfg(feature = "serde")]
592 fn name_serialize(#[case] name: Name, #[case] expected: Token) {
593 assert_ser_tokens(&name, &[expected]);
594 }
595
596 #[rstest]
597 #[case("This is a valid attribute value", Value::SingleLine(Some(Cow::Owned("This is a valid attribute value".to_string()))))]
598 #[case(" ", Value::SingleLine(None))]
599 fn value_from_str(#[case] s: &str, #[case] expected: Value) {
600 assert_eq!(Value::from_str(s).unwrap(), expected);
601 }
602
603 #[rstest]
604 fn value_from_empty_str(#[values("", " ")] s: &str) {
605 assert_eq!(Value::from_str(s).unwrap(), Value::SingleLine(None));
606 }
607
608 proptest! {
609 #[test]
610 fn value_validation_any_non_control_extended_ascii_valid(
611 s in r"[\x00-\xFF]+"
612 .prop_filter("Must not contain control chars", |s| !s.chars().any(|c| c.is_ascii_control())))
613 {
614 Value::validate(&s).unwrap();
615 }
616
617 #[test]
618 fn value_validation_any_non_extended_ascii_is_err(s in r"[^\x00-\xFF]+") {
619 matches!(Value::validate(&s).unwrap_err(), InvalidValueError::NonExtendedAscii);
620 }
621
622 #[test]
623 fn value_validation_any_ascii_control_is_err(s in r"[\x00-\x1F\x7F]+") {
624 matches!(Value::validate(&s).unwrap_err(), InvalidValueError::ContainsControlChar);
625 }
626 }
627
628 #[rstest]
629 #[case(
630 Value::unchecked_single("32934"),
631 &[
632 Token::Seq { len: Some(1) },
633 Token::Some,
634 Token::Str("32934"),
635 Token::SeqEnd,
636 ],
637 )]
638 #[case(
639 Value::unchecked_single(""),
640 &[
641 Token::Seq { len: Some(1) },
642 Token::None,
643 Token::SeqEnd,
644 ],
645 )]
646 #[case(
647 Value::unchecked_multi(["Packet Street 6", "128 Series of Tubes", "Internet"]),
648 &[
649 Token::Seq { len: Some(3) },
650 Token::Some,
651 Token::Str("Packet Street 6"),
652 Token::Some,
653 Token::Str("128 Series of Tubes"),
654 Token::Some,
655 Token::Str("Internet"),
656 Token::SeqEnd,
657 ],
658 )]
659 #[cfg(feature = "serde")]
660 fn value_serialize(#[case] value: Value, #[case] expected: &[Token]) {
661 assert_ser_tokens(&value, expected);
662 }
663
664 #[rstest]
665 #[case(Value::unchecked_single(""), Value::unchecked_single(None))]
666 #[case(Value::unchecked_single(" "), Value::unchecked_single(None))]
667 #[case(Value::unchecked_multi(["", " ", " "]), Value::unchecked_multi([None, None, None]))]
668 fn value_unchecked_empty_is_none(#[case] value: Value, #[case] expected: Value) {
670 assert_eq!(value, expected);
671 }
672
673 #[test]
674 #[should_panic(expected = "multi line values need at least two lines")]
675 fn value_unchecked_multi_with_singe_value_panics() {
677 Value::unchecked_multi(["just one"]);
678 }
679
680 #[rstest]
681 #[case(
682 vec!["Packet Street 6", "128 Series of Tubes", "Internet"],
683 Value::MultiLine(vec![
684 Some(Cow::Owned("Packet Street 6".to_string())),
685 Some(Cow::Owned("128 Series of Tubes".to_string())),
686 Some(Cow::Owned("Internet".to_string()))
687 ])
688 )]
689 #[case(
690 vec!["", "128 Series of Tubes", "Internet"],
691 Value::MultiLine(vec![
692 None,
693 Some(Cow::Owned("128 Series of Tubes".to_string())),
694 Some(Cow::Owned("Internet".to_string()))
695 ])
696 )]
697 #[case(
698 vec!["", " ", " "],
699 Value::MultiLine(vec![None, None, None])
700 )]
701 fn value_from_vec_of_str(#[case] v: Vec<&str>, #[case] expected: Value) {
702 let value = Value::try_from(v).unwrap();
703 assert_eq!(value, expected);
704 }
705
706 #[test]
707 fn value_from_vec_w_1_value_is_single_line() {
708 assert_eq!(
709 Value::try_from(vec!["Packet Street 6"]).unwrap(),
710 Value::SingleLine(Some(Cow::Owned("Packet Street 6".to_string())))
711 );
712 }
713
714 #[rstest]
715 #[case("single value", 1)]
716 #[case(vec!["multi", "value", "attribute"].try_into().unwrap(), 3)]
717 fn value_lines(#[case] value: Value, #[case] expected: usize) {
718 assert_eq!(value.lines(), expected);
719 }
720
721 #[rstest]
722 #[case(
723 Value::unchecked_single(None),
724 vec![]
725 )]
726 #[case(
727 Value::unchecked_single("single value"),
728 vec!["single value"]
729 )]
730 #[case(
731 Value::unchecked_multi(vec![
732 None,
733 Some("128 Series of Tubes"),
734 Some("Internet"),
735 ]),
736 vec!["128 Series of Tubes", "Internet"]
737 )]
738 #[case(
739 Value::unchecked_multi([
740 "Packet Street 6",
741 "128 Series of Tubes",
742 "Internet"
743 ]),
744 vec!["Packet Street 6", "128 Series of Tubes", "Internet"]
745 )]
746 fn value_with_content(#[case] value: Value, #[case] expected: Vec<&str>) {
747 let content = value.with_content();
748 assert_eq!(content, expected);
749 }
750
751 #[rstest]
752 #[case("a value")]
753 #[case("single value")]
754 fn value_partialeq_str_eq_is_eq(#[case] s: &str) {
756 let value = Value::unchecked_single(s);
757 assert_eq!(value, s);
758 }
759
760 #[rstest]
761 #[case(Value::unchecked_single("a value"), "a different value")]
762 #[case(
763 Value::unchecked_multi([
764 "multi",
765 "value"
766 ]),
767 "single value"
768 )]
769 fn value_partialeq_str_ne_is_ne(#[case] value: Value, #[case] s: &str) {
771 assert_ne!(value, s);
772 }
773
774 #[rstest]
775 #[case(
776 Value::unchecked_single("single value"),
777 vec!["single value"]
778 )]
779 #[case(
780 Value::unchecked_single(None),
781 vec![" "]
782 )]
783 #[case(
784 Value::unchecked_multi([
785 "multi",
786 "value",
787 "attribute"
788 ]),
789 vec!["multi", "value", "attribute"]
790 )]
791 #[case(
792 Value::unchecked_multi([
793 Some("multi"),
794 None,
795 Some("attribute")
796 ]),
797 vec!["multi", " ", "attribute"]
798 )]
799 fn value_partialeq_vec_str_eq_is_eq(#[case] value: Value, #[case] v: Vec<&str>) {
801 assert_eq!(value, v);
802 }
803
804 #[rstest]
805 #[case(
806 Value::unchecked_single("single value"),
807 vec!["multi", "value"]
808 )]
809 #[case(
810 Value::unchecked_single("single value"),
811 vec!["other single value"]
812 )]
813 #[case(
814 Value::unchecked_multi([
815 "multi",
816 "value",
817 "attribute"
818 ]),
819 vec!["different", "multi", "value", "attribute"]
820 )]
821 fn value_partialeq_vec_str_ne_is_ne(#[case] value: Value, #[case] v: Vec<&str>) {
823 assert_ne!(value, v);
824 }
825
826 #[rstest]
827 #[case(
828 Value::unchecked_single("single value"),
829 vec![Some("single value")]
830 )]
831 #[case(
832 Value::unchecked_multi([
833 "multi",
834 "value",
835 "attribute"
836 ]),
837 vec![Some("multi"), Some("value"), Some("attribute")]
838 )]
839 #[case(
840 Value::unchecked_multi([Some("multi"), None, Some("attribute")]),
841 vec![Some("multi"), None, Some("attribute")]
842 )]
843 fn value_partialeq_vec_option_str_eq_is_eq(#[case] value: Value, #[case] v: Vec<Option<&str>>) {
845 assert_eq!(value, v);
846 }
847
848 #[rstest]
849 #[case(
850 Value::unchecked_single("single value"),
851 vec![Some("multi"), Some("value")]
852 )]
853 #[case(
854 Value::unchecked_single("single value"),
855 vec![Some("other single value")]
856 )]
857 #[case(
858 Value::unchecked_single(None),
859 vec![Some(" ")]
860 )]
861 #[case(
862 Value::unchecked_multi([
863 "multi",
864 "value",
865 "attribute"
866 ]),
867 vec![Some("different"), Some("multi"), Some("value"), Some("attribute")]
868 )]
869 fn value_partialeq_vec_option_str_ne_is_ne(#[case] value: Value, #[case] v: Vec<Option<&str>>) {
871 assert_ne!(value, v);
872 }
873
874 #[rstest]
875 #[case(
876 Value::unchecked_single("single value"),
877 vec![Some("single value".to_string())]
878 )]
879 #[case(
880 Value::unchecked_multi(["multiple", "values"]),
881 vec![Some("multiple".to_string()), Some("values".to_string())]
882 )]
883 #[case(
884 Value::unchecked_multi(["multiple", "", "separated", "values"]),
885 vec![Some("multiple".to_string()), None, Some("separated".to_string()), Some("values".to_string())]
886 )]
887 fn value_into_vec_of_option_string(
888 #[case] value: Value,
889 #[case] expected: Vec<Option<String>>,
890 ) {
891 let vec: Vec<Option<String>> = value.into();
892 assert_eq!(vec, expected);
893 }
894
895 proptest! {
896 #[test]
897 fn value_from_str_non_ascii_is_err(v in r"[^[[:ascii:]]]") {
898 assert!(Name::from_str(&v).is_err());
899 }
900
901 #[test]
902 fn value_from_str_ascii_control_is_err(v in r"[[:cntrl:]]") {
903 assert!(Name::from_str(&v).is_err());
904 }
905 }
906}