rpsl/
attribute.rs

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/// An attribute of an [`Object`](crate::Object).
9///
10/// # Example
11/// ```
12/// # use rpsl::{parse_object, Attribute};
13/// let object = parse_object("
14/// name:           ACME Company
15///
16/// ")?;
17/// let attribute = Attribute::new("name".parse()?, "ACME Company".parse()?);
18/// assert_eq!(object[0], attribute);
19/// # Ok::<(), Box<dyn std::error::Error>>(())
20/// ```
21#[derive(Debug, PartialEq, Eq, Clone)]
22#[cfg_attr(feature = "serde", derive(Serialize))]
23pub struct Attribute<'a> {
24    /// The name of the attribute.
25    pub name: Name<'a>,
26    /// The value of the attribute.
27    #[cfg_attr(feature = "serde", serde(rename = "values"))]
28    pub value: Value<'a>,
29}
30
31impl<'a> Attribute<'a> {
32    /// Create a new attribute.
33    #[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/// The name of an [`Attribute`].
89#[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    /// Create a new `Name` from a string slice.
117    ///
118    /// A valid name may consist of ASCII letters, digits and the characters "-", "_",
119    /// while beginning with a letter and ending with a letter or a digit.
120    ///
121    /// # Errors
122    /// Returns an error if the name is empty or invalid.
123    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/// The value of an [`Attribute`].
150/// Since only some values contain multiple lines and single line values do not require
151/// additional heap allocation, an Enum is used to represent both variants.
152#[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    /// A single line value.
160    ///
161    /// # Example
162    /// ```
163    /// # use rpsl::{parse_object, Value};
164    /// let object = parse_object("
165    /// name:           ACME Company
166    ///
167    /// ")?;
168    /// let value: Value = "ACME Company".parse()?;
169    /// assert_eq!(object[0].value, value);
170    /// # Ok::<(), Box<dyn std::error::Error>>(())
171    /// ```
172    SingleLine(Option<Cow<'a, str>>),
173    /// A value spanning over multiple lines.
174    ///
175    /// # Example
176    /// ```
177    /// # use rpsl::{parse_object, Value};
178    /// let object = parse_object("
179    /// remarks:        Packet Street 6
180    ///                 128 Series of Tubes
181    ///                 Internet
182    ///
183    /// ")?;
184    /// let value: Value = vec!["Packet Street 6", "128 Series of Tubes", "Internet"].try_into()?;
185    /// assert_eq!(object[0].value, value);
186    /// # Ok::<(), Box<dyn std::error::Error>>(())
187    /// ```
188    MultiLine(Vec<Option<Cow<'a, str>>>),
189}
190
191impl<'a> Value<'a> {
192    /// Create a single line value without checking that characters conform to any specification
193    /// while still coercing empty values to `None`.
194    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    /// Create a multi line value without checking that characters conform to any specification
202    /// while still coercing empty values to `None`.
203    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    /// Even though RFC 2622 requires values to be ASCII, in practice some WHOIS databases
223    /// (e.g. RIPE) do not enforce this so, to be useful in the real world, we don't either.
224    #[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    /// The number of lines contained.
236    ///
237    /// # Examples
238    ///
239    /// A value with a single line.
240    /// ```
241    /// # use rpsl::Value;
242    /// let value: Value = "ACME Company".parse()?;
243    /// assert_eq!(value.lines(), 1);
244    /// # Ok::<(), Box<dyn std::error::Error>>(())
245    /// ```
246    ///
247    /// A value with multiple lines.
248    /// ```
249    /// # use rpsl::Value;
250    /// let value: Value = vec!["Packet Street 6", "128 Series of Tubes", "Internet"].try_into()?;
251    /// assert_eq!(value.lines(), 3);
252    /// # Ok::<(), Box<dyn std::error::Error>>(())
253    /// ```
254    #[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    /// The lines that contain content and are non empty.
275    ///
276    /// # Example
277    /// ```
278    /// # use rpsl::parse_object;
279    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
280    /// let remarks = parse_object("
281    /// remarks:        I have lots
282    ///                 
283    ///                 to say.
284    ///
285    /// ")?;
286    /// assert_eq!(remarks[0].value.with_content(), vec!["I have lots", "to say."]);
287    /// # Ok(())
288    /// # }
289    /// ```
290    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    /// Create a new single line value from a string slice.
308    ///
309    /// A valid value may consist of any ASCII character, excluding control characters.
310    ///
311    /// # Errors
312    /// Returns an error if the value contains invalid characters.
313    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    /// Create a new value from a vector of string slices, representing the values lines.
325    ///
326    /// # Errors
327    /// Returns an error if a value contains invalid characters.
328    ///
329    /// # Example
330    /// ```
331    /// # use rpsl::Value;
332    /// let value: Value = vec!["Packet Street 6", "128 Series of Tubes", "Internet"].try_into()?;
333    /// assert_eq!(value.lines(), 3);
334    /// # Ok::<(), Box<dyn std::error::Error>>(())
335    /// ```
336    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
424/// Coerce an empty value to `None`.
425fn 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/// Checks if the given char is part of the extended ASCII set.
437#[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    /// Creating unchecked values from empty strings results in None values.
669    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    /// Unchecked multi line attributes cannot be created with only a single value.
676    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    /// A value and &str evaluate as equal if the contents match.
755    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    /// A value and &str do not evaluate as equal if the contents differ.
770    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    /// A value and a Vec<&str> evaluate as equal if the contents match.
800    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    /// A value and a Vec<&str> do not evaluate as equal if the contents differ.
822    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    /// A value and a Vec<Option<&str>> evaluate as equal if the contents match.
844    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    /// A value and a Vec<Option<&str>> do not evaluate as equal if the contents differ.
870    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}