1use std::{
2 borrow::Cow,
3 fmt,
4 ops::{Deref, Index},
5};
6
7#[cfg(feature = "serde")]
8use serde::Serialize;
9
10use super::Attribute;
11use crate::spec::{AttributeError, Raw, Specification};
12
13#[derive(Debug, Clone)]
181#[cfg_attr(feature = "serde", derive(Serialize), serde(bound = ""))]
182#[allow(clippy::len_without_is_empty)]
183pub struct Object<'a, Spec: Specification = Raw> {
184 attributes: Vec<Attribute<'a, Spec>>,
185 #[cfg_attr(feature = "serde", serde(skip))]
187 source: Option<Cow<'a, str>>,
188}
189
190impl<'a, Spec: Specification> Object<'a, Spec> {
191 pub fn validate<TargetSpec: Specification>(&self) -> Result<(), ObjectValidationError> {
207 let mut errors = Vec::new();
208 for (index, attribute) in self.attributes.iter().enumerate() {
209 if let Err(error) = attribute.validate::<TargetSpec>() {
210 errors.push((index, error));
211 }
212 }
213
214 if errors.is_empty() {
215 Ok(())
216 } else {
217 Err(ObjectValidationError::new(errors))
218 }
219 }
220
221 pub fn into_spec<TargetSpec: Specification>(
237 self,
238 ) -> Result<Object<'a, TargetSpec>, AttributeError> {
239 let Object { attributes, source } = self;
240
241 let mut converted = Vec::with_capacity(attributes.len());
242 for attribute in attributes {
243 converted.push(attribute.into_spec::<TargetSpec>()?);
244 }
245
246 Ok(Object {
247 attributes: converted,
248 source,
249 })
250 }
251
252 #[must_use]
271 pub fn new(attributes: Vec<Attribute<'static, Spec>>) -> Object<'static, Spec> {
272 Object {
273 attributes,
274 source: None,
275 }
276 }
277
278 pub(crate) fn new_parsed(
280 source: &'a str,
281 attributes: Vec<Attribute<'a, Spec>>,
282 ) -> Object<'a, Spec> {
283 Object {
284 attributes,
285 source: Some(Cow::Borrowed(source)),
286 }
287 }
288
289 #[must_use]
291 pub fn len(&self) -> usize {
292 self.attributes.len()
293 }
294
295 #[must_use]
297 pub fn get(&self, name: &str) -> Vec<&str> {
298 self.attributes
299 .iter()
300 .filter(|a| a.name == name)
301 .flat_map(|a| a.value.with_content())
302 .collect()
303 }
304
305 #[cfg(feature = "json")]
306 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
307 #[allow(clippy::missing_panics_doc)]
308 #[must_use]
309 pub fn json(&self) -> serde_json::Value {
311 serde_json::to_value(self).unwrap()
312 }
313
314 #[cfg(test)]
316 pub(crate) fn source(&self) -> Option<&str> {
317 self.source.as_deref()
318 }
319
320 pub fn into_owned(self) -> Object<'static, Spec> {
322 Object {
323 attributes: self
324 .attributes
325 .into_iter()
326 .map(Attribute::into_owned)
327 .collect(),
328 source: self.source.map(|s| Cow::Owned(s.into_owned())),
329 }
330 }
331}
332
333impl<'a, Spec: Specification> Index<usize> for Object<'a, Spec> {
334 type Output = Attribute<'a, Spec>;
335
336 fn index(&self, index: usize) -> &Self::Output {
337 &self.attributes[index]
338 }
339}
340
341impl<'a, Spec: Specification> Deref for Object<'a, Spec> {
342 type Target = Vec<Attribute<'a, Spec>>;
343
344 fn deref(&self) -> &Self::Target {
345 &self.attributes
346 }
347}
348
349impl<'a, Spec: Specification> IntoIterator for Object<'a, Spec> {
350 type Item = Attribute<'a, Spec>;
351 type IntoIter = std::vec::IntoIter<Self::Item>;
352
353 fn into_iter(self) -> Self::IntoIter {
354 self.attributes.into_iter()
355 }
356}
357
358impl PartialEq for Object<'_> {
359 fn eq(&self, other: &Self) -> bool {
362 self.attributes == other.attributes
363 }
364}
365
366impl fmt::Display for Object<'_> {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 if let Some(source) = &self.source {
370 write!(f, "{source}")
371 } else {
372 for attribute in &self.attributes {
373 write!(f, "{attribute}")?;
374 }
375 writeln!(f)
376 }
377 }
378}
379
380#[macro_export]
419macro_rules! object {
420 (
421 $(
422 $name:literal: $($value:literal),+
423 );+ $(;)?
424 ) => {
425 $crate::Object::new(vec![
426 $(
427 {
428 let name = $crate::Name::new($name);
429 let value: $crate::Value = vec![$($value),+].into();
430 $crate::Attribute::new(name, value)
431 },
432 )*
433 ])
434 };
435}
436
437#[derive(Debug, thiserror::Error)]
439#[error("{num} attribute(s) failed validation", num = .errors.len())]
440pub struct ObjectValidationError {
441 errors: Vec<(usize, AttributeError)>,
443}
444
445impl ObjectValidationError {
446 fn new(errors: Vec<(usize, AttributeError)>) -> Self {
447 Self { errors }
448 }
449
450 #[must_use]
452 pub fn len(&self) -> usize {
453 self.errors.len()
454 }
455
456 #[must_use]
458 pub fn is_empty(&self) -> bool {
459 self.errors.is_empty()
460 }
461
462 pub fn iter_errors(&self) -> impl Iterator<Item = &AttributeError> {
464 self.errors.iter().map(|(_, error)| error)
465 }
466
467 pub fn iter_indexed(&self) -> impl Iterator<Item = (usize, &AttributeError)> {
469 self.errors.iter().map(|(index, error)| (*index, error))
470 }
471
472 #[must_use]
474 pub fn into_errors(self) -> Vec<(usize, AttributeError)> {
475 self.errors
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use rstest::*;
482 #[cfg(feature = "json")]
483 use serde_json::json;
484
485 use super::*;
486 use crate::{
487 spec::{InvalidNameError, Rfc2622},
488 Name,
489 };
490
491 #[rstest]
492 #[case(
493 Object::new(vec![
494 Attribute::unchecked_single("role", "ACME Company"),
495 Attribute::unchecked_single("address", "Packet Street 6"),
496 Attribute::unchecked_single("address", "128 Series of Tubes"),
497 Attribute::unchecked_single("address", "Internet"),
498 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
499 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
500 Attribute::unchecked_single("source", "RIPE"),
501 ]),
502 Object::new_parsed(
503 concat!(
504 "role: ACME Company\n",
505 "address: Packet Street 6\n",
506 "address: 128 Series of Tubes\n",
507 "address: Internet\n",
508 "email: rpsl-rs@github.com\n",
509 "nic-hdl: RPSL1-RIPE\n",
510 "source: RIPE\n",
511 "\n"
512 ),
513 vec![
514 Attribute::unchecked_single("role", "ACME Company"),
515 Attribute::unchecked_single("address", "Packet Street 6"),
516 Attribute::unchecked_single("address", "128 Series of Tubes"),
517 Attribute::unchecked_single("address", "Internet"),
518 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
519 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
520 Attribute::unchecked_single("source", "RIPE"),
521 ]
522 ),
523 concat!(
524 "role: ACME Company\n",
525 "address: Packet Street 6\n",
526 "address: 128 Series of Tubes\n",
527 "address: Internet\n",
528 "email: rpsl-rs@github.com\n",
529 "nic-hdl: RPSL1-RIPE\n",
530 "source: RIPE\n",
531 "\n"
532 )
533 )]
534 #[case(
535 Object::new(vec![
536 Attribute::unchecked_single("role", "ACME Company"),
537 Attribute::unchecked_multi(
538 "address",
539 ["Packet Street 6", "128 Series of Tubes", "Internet"]
540 ),
541 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
542 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
543 Attribute::unchecked_single("source", "RIPE"),
544 ]),
545 Object::new_parsed(
546 concat!(
547 "role: ACME Company\n",
548 "address: Packet Street 6\n",
549 " 128 Series of Tubes\n",
550 " Internet\n",
551 "email: rpsl-rs@github.com\n",
552 "nic-hdl: RPSL1-RIPE\n",
553 "source: RIPE\n",
554 "\n"
555 ),
556 vec![
557 Attribute::unchecked_single("role", "ACME Company"),
558 Attribute::unchecked_multi(
559 "address",
560 ["Packet Street 6", "128 Series of Tubes", "Internet"]
561 ),
562 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
563 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
564 Attribute::unchecked_single("source", "RIPE"),
565 ]
566 ),
567 concat!(
568 "role: ACME Company\n",
569 "address: Packet Street 6\n",
570 " 128 Series of Tubes\n",
571 " Internet\n",
572 "email: rpsl-rs@github.com\n",
573 "nic-hdl: RPSL1-RIPE\n",
574 "source: RIPE\n",
575 "\n"
576 )
577 )]
578 fn object_display(
579 #[case] owned: Object<'static>,
580 #[case] borrowed: Object,
581 #[case] expected: &str,
582 ) {
583 assert_eq!(owned.to_string(), expected);
584 assert_eq!(borrowed.to_string(), expected);
585 }
586
587 #[rstest]
588 #[case(
589 Object::new(vec![
590 Attribute::unchecked_single("role", "ACME Company"),
591 ]),
592 1
593 )]
594 #[case(
595 Object::new(vec![
596 Attribute::unchecked_single("role", "ACME Company"),
597 Attribute::unchecked_single("address", "Packet Street 6"),
598 Attribute::unchecked_single("address", "128 Series of Tubes"),
599 Attribute::unchecked_single("address", "Internet"),
600 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
601 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
602 Attribute::unchecked_single("source", "RIPE"),
603 ]),
604 7
605 )]
606 fn object_len(#[case] object: Object, #[case] expected: usize) {
607 assert_eq!(object.len(), expected);
608 }
609
610 #[rstest]
611 #[case(
612 Object::new(vec![
613 Attribute::unchecked_single("role", "ACME Company"),
614 Attribute::unchecked_single("address", "Packet Street 6"),
615 Attribute::unchecked_single("address", "128 Series of Tubes"),
616 Attribute::unchecked_single("address", "Internet"),
617 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
618 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
619 Attribute::unchecked_single("source", "RIPE"),
620 ]),
621 2,
622 Attribute::unchecked_single("address", "128 Series of Tubes"),
623 )]
624 fn object_index(#[case] object: Object, #[case] index: usize, #[case] expected: Attribute) {
625 assert_eq!(object[index], expected);
626 }
627
628 #[rstest]
629 #[case(
630 vec![
631 Attribute::unchecked_single("role", "ACME Company"),
632 Attribute::unchecked_single("address", "Packet Street 6"),
633 Attribute::unchecked_single("address", "128 Series of Tubes"),
634 Attribute::unchecked_single("address", "Internet"),
635 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
636 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
637 Attribute::unchecked_single("source", "RIPE"),
638 ],
639 )]
640 fn object_deref(#[case] attributes: Vec<Attribute<'static>>) {
641 let object = Object::new(attributes.clone());
642 assert_eq!(*object, attributes);
643 }
644
645 #[rstest]
646 #[case(
647 vec![
648 Attribute::unchecked_single("role", "ACME Company"),
649 Attribute::unchecked_single("address", "Packet Street 6"),
650 Attribute::unchecked_single("address", "128 Series of Tubes"),
651 Attribute::unchecked_single("address", "Internet"),
652 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
653 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
654 Attribute::unchecked_single("source", "RIPE"),
655 ],
656 )]
657 fn object_into_iter(#[case] attributes: Vec<Attribute<'static>>) {
658 let object = Object::new(attributes.clone());
659
660 let attr_iter = attributes.into_iter();
661 let obj_iter = object.into_iter();
662
663 for (a, b) in attr_iter.zip(obj_iter) {
664 assert_eq!(a, b);
665 }
666 }
667
668 #[rstest]
669 #[case(
670 Object::new(vec![
671 Attribute::unchecked_single("role", "ACME Company"),
672 Attribute::unchecked_single("address", "Packet Street 6"),
673 Attribute::unchecked_single("address", "128 Series of Tubes"),
674 Attribute::unchecked_single("address", "Internet"),
675 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
676 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
677 Attribute::unchecked_single("source", "RIPE"),
678 ]),
679 json!({
680 "attributes": [
681 { "name": "role", "values": ["ACME Company"] },
682 { "name": "address", "values": ["Packet Street 6"] },
683 { "name": "address", "values": ["128 Series of Tubes"] },
684 { "name": "address", "values": ["Internet"] },
685 { "name": "email", "values": ["rpsl-rs@github.com"] },
686 { "name": "nic-hdl", "values": ["RPSL1-RIPE"] },
687 { "name": "source", "values": ["RIPE"] }
688 ]
689 })
690 )]
691 #[case(
692 Object::new(vec![
693 Attribute::unchecked_single("role", "ACME Company"),
694 Attribute::unchecked_multi(
695 "address",
696 ["Packet Street 6", "", "128 Series of Tubes", "Internet"]
697 ),
698 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
699 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
700 Attribute::unchecked_single("source", "RIPE"),
701 ]),
702 json!({
703 "attributes": [
704 { "name": "role", "values": ["ACME Company"] },
705 {
706 "name": "address",
707 "values": ["Packet Street 6", null, "128 Series of Tubes", "Internet"] },
708 { "name": "email", "values": ["rpsl-rs@github.com"] },
709 { "name": "nic-hdl", "values": ["RPSL1-RIPE"] },
710 { "name": "source", "values": ["RIPE"] }
711 ]
712 })
713 )]
714 #[cfg(feature = "json")]
715 fn object_json_repr(#[case] object: Object, #[case] expected: serde_json::Value) {
716 let json = object.json();
717 assert_eq!(json, expected);
718 }
719
720 #[rstest]
721 #[case(
722 Object::new_parsed(
723 concat!(
724 "role: ACME Company\n",
725 "address: Packet Street 6\n",
726 "address: 128 Series of Tubes\n",
727 "address: Internet\n",
728 "email: rpsl-rs@github.com\n",
729 "nic-hdl: RPSL1-RIPE\n",
730 "source: RIPE\n",
731 "\n", ),
733 vec![
734 Attribute::unchecked_single("role", "ACME Company"),
735 Attribute::unchecked_single("address", "Packet Street 6"),
736 Attribute::unchecked_single("address", "128 Series of Tubes"),
737 Attribute::unchecked_single("address", "Internet"),
738 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
739 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
740 Attribute::unchecked_single("source", "RIPE"),
741 ],
742 ),
743 concat!(
744 "role: ACME Company\n",
745 "address: Packet Street 6\n",
746 "address: 128 Series of Tubes\n",
747 "address: Internet\n",
748 "email: rpsl-rs@github.com\n",
749 "nic-hdl: RPSL1-RIPE\n",
750 "source: RIPE\n",
751 "\n" )
753 )]
754 #[case(
755 Object::new_parsed(
756 concat!(
757 "role: ACME Company\n",
758 "address: Packet Street 6\n",
759 "address: 128 Series of Tubes\n",
760 "address: Internet\n",
761 "email: rpsl-rs@github.com\n",
762 "nic-hdl: RPSL1-RIPE\n",
763 "source: RIPE\n",
764 ),
766 vec![
767 Attribute::unchecked_single("role", "ACME Company"),
768 Attribute::unchecked_single("address", "Packet Street 6"),
769 Attribute::unchecked_single("address", "128 Series of Tubes"),
770 Attribute::unchecked_single("address", "Internet"),
771 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
772 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
773 Attribute::unchecked_single("source", "RIPE"),
774 ],
775 ),
776 concat!(
777 "role: ACME Company\n",
778 "address: Packet Street 6\n",
779 "address: 128 Series of Tubes\n",
780 "address: Internet\n",
781 "email: rpsl-rs@github.com\n",
782 "nic-hdl: RPSL1-RIPE\n",
783 "source: RIPE\n",
784 )
786 )]
787 #[case(
788 Object::new_parsed(
789 concat!(
790 "role: ACME Company\n",
791 "address: Packet Street 6\n",
792 " 128 Series of Tubes\n",
794 " Internet\n",
795 "email: rpsl-rs@github.com\n",
796 "nic-hdl: RPSL1-RIPE\n",
797 "source: RIPE\n",
798 "\n"
799 ),
800 vec![
801 Attribute::unchecked_single("role", "ACME Company"),
802 Attribute::unchecked_multi(
803 "address",
804 ["Packet Street 6", "128 Series of Tubes", "Internet"]
805 ),
806 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
807 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
808 Attribute::unchecked_single("source", "RIPE"),
809 ],
810 ),
811 concat!(
812 "role: ACME Company\n",
813 "address: Packet Street 6\n",
814 " 128 Series of Tubes\n",
816 " Internet\n",
817 "email: rpsl-rs@github.com\n",
818 "nic-hdl: RPSL1-RIPE\n",
819 "source: RIPE\n",
820 "\n"
821 )
822 )]
823 #[case(
824 Object::new_parsed(
825 concat!(
826 "role: ACME Company\n",
827 "address: Packet Street 6\n",
828 "+ 128 Series of Tubes\n",
830 "+ Internet\n",
831 "email: rpsl-rs@github.com\n",
832 "nic-hdl: RPSL1-RIPE\n",
833 "source: RIPE\n",
834 "\n"
835 ),
836 vec![
837 Attribute::unchecked_single("role", "ACME Company"),
838 Attribute::unchecked_multi(
839 "address",
840 ["Packet Street 6", "128 Series of Tubes", "Internet"]
841 ),
842 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
843 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
844 Attribute::unchecked_single("source", "RIPE"),
845 ],
846 ),
847 concat!(
848 "role: ACME Company\n",
849 "address: Packet Street 6\n",
850 "+ 128 Series of Tubes\n",
852 "+ Internet\n",
853 "email: rpsl-rs@github.com\n",
854 "nic-hdl: RPSL1-RIPE\n",
855 "source: RIPE\n",
856 "\n"
857 )
858 )]
859 fn borrowed_objects_display_like_source(#[case] object: Object, #[case] expected: &str) {
861 assert_eq!(object.to_string(), expected);
862 }
863
864 #[rstest]
865 #[case(
866 object! {
867 "role": "ACME Company";
868 "address": "Packet Street 6", "128 Series of Tubes", "Internet";
869 "email": "rpsl-rs@github.com";
870 "nic-hdl": "RPSL1-RIPE";
871 "source": "RIPE";
872 },
873 Object::new(vec![
874 Attribute::unchecked_single("role", "ACME Company"),
875 Attribute::unchecked_multi(
876 "address",
877 ["Packet Street 6", "128 Series of Tubes", "Internet"],
878 ),
879 Attribute::unchecked_single("email", "rpsl-rs@github.com"),
880 Attribute::unchecked_single("nic-hdl", "RPSL1-RIPE"),
881 Attribute::unchecked_single("source", "RIPE"),
882 ])
883 )]
884 fn object_from_macro(#[case] from_macro: Object, #[case] expected: Object) {
885 assert_eq!(from_macro, expected);
886 }
887
888 #[rstest]
889 #[allow(clippy::used_underscore_binding)]
890 #[case(
891 Object::new(vec![
892 Attribute::unchecked_single("role", "ACME Company"),
893 Attribute::unchecked_single("address", "Packet Street 6"),
894 ]),
895 Rfc2622
896 )]
897 fn object_validate_conformant_object_validates<TargetSpec: Specification>(
898 #[case] object: Object,
899 #[case] _target: TargetSpec,
900 ) {
901 object.validate::<TargetSpec>().unwrap();
902 }
903
904 #[rstest]
905 #[case(
906 Object::new(vec![
907 Attribute::unchecked_single("role", "ACME Company"),
908 Attribute::unchecked_single("a", "Packet Street 6"),
909 Attribute::unchecked_single("1mail", "rpsl-rs@github.com"),
910 ]),
911 Rfc2622,
912 vec![
913 (
914 1,
915 AttributeError::from(
916 InvalidNameError::new(&Name::new("a"), "must be at least two characters long")
917 ),
918 ),
919 (
920 2,
921 AttributeError::from(
922 InvalidNameError::new(&Name::new("1mail"), "must start with an ASCII alphabetic character")
923 ),
924 )
925 ]
926 )]
927 #[allow(clippy::used_underscore_binding)]
928 fn object_validate_invalid_object_returns_expected_errors<
929 TargetSpec: Specification + PartialEq,
930 >(
931 #[case] object: Object,
932 #[case] _target: TargetSpec,
933 #[case] expected: Vec<(usize, AttributeError)>,
934 ) {
935 let errors = object.validate::<TargetSpec>().unwrap_err().into_errors();
936 assert_eq!(errors, expected);
937 }
938
939 #[test]
940 fn object_into_spec_preserves_source() {
941 let source = concat!(
942 "role: ACME Company\n",
943 "source: RIPE\n",
944 "\n"
945 );
946 let object = Object::new_parsed(
947 source,
948 vec![
949 Attribute::unchecked_single("role", "ACME Company"),
950 Attribute::unchecked_single("source", "RIPE"),
951 ],
952 );
953
954 let converted = object.into_spec::<Rfc2622>().unwrap();
955 assert_eq!(converted.source(), Some(source));
956 }
957
958 #[rstest]
959 #[case(
960 Object::new(vec![
961 Attribute::unchecked_single("role", "ACME Company"),
962 Attribute::unchecked_single("a", "Packet Street 6"),
963 Attribute::unchecked_single("mail", "rpsl-rs@github.com"),
964 ]),
965 Rfc2622,
966 AttributeError::from(
967 InvalidNameError::new(&Name::new("a"), "must be at least two characters long")
968 ),
969 )]
970 #[allow(clippy::used_underscore_binding)]
971 fn object_into_spec_returns_first_validation_error<TargetSpec: Specification + PartialEq>(
972 #[case] object: Object,
973 #[case] _target: TargetSpec,
974 #[case] expected: AttributeError,
975 ) {
976 let error = object.into_spec::<TargetSpec>().unwrap_err();
977 assert_eq!(error, expected);
978 }
979
980 #[rstest]
981 #[case(
982 Object::new(vec![
983 Attribute::unchecked_single("aut-num", "AS42"),
984 Attribute::unchecked_single(
985 "remarks",
986 "All imported prefixes will be tagged with geographic communities and",
987 ),
988 Attribute::unchecked_single(
989 "remarks",
990 "the type of peering relationship according to the table below, using the default",
991 ),
992 Attribute::unchecked_single(
993 "remarks",
994 "announce rule (x=0).",
995 ),
996 Attribute::unchecked_single("remarks", None),
997 Attribute::unchecked_single(
998 "remarks",
999 "The following communities can be used by peers and customers",
1000 ),
1001 Attribute::unchecked_multi(
1002 "remarks",
1003 [
1004 "x = 0 - Announce (default rule)",
1005 "x = 1 - Prepend x1",
1006 "x = 2 - Prepend x2",
1007 "x = 3 - Prepend x3",
1008 "x = 9 - Do not announce",
1009 ],
1010 ),
1011 ]),
1012 vec![
1013 ("aut-num", vec!["AS42"]),
1014 (
1015 "remarks",
1016 vec![
1017 "All imported prefixes will be tagged with geographic communities and",
1018 "the type of peering relationship according to the table below, using the default",
1019 "announce rule (x=0).",
1020 "The following communities can be used by peers and customers",
1021 "x = 0 - Announce (default rule)",
1022 "x = 1 - Prepend x1",
1023 "x = 2 - Prepend x2",
1024 "x = 3 - Prepend x3",
1025 "x = 9 - Do not announce",
1026 ]
1027 )
1028 ]
1029 )]
1030 fn get_values_by_name(#[case] object: Object, #[case] name_expected: Vec<(&str, Vec<&str>)>) {
1031 for (name, expected) in name_expected {
1032 assert_eq!(object.get(name), expected);
1033 }
1034 }
1035
1036 #[rstest]
1037 #[case(
1038 Object::new(
1039 vec![
1040 Attribute::unchecked_single("role", "ACME Company"),
1041 Attribute::unchecked_single("address", "Packet Street 6"),
1042 Attribute::unchecked_single("address", "128 Series of Tubes"),
1043 Attribute::unchecked_single("address", "Internet"),
1044 ]),
1045 Object::new(
1046 vec![
1047 Attribute::unchecked_single("role", "ACME Company"),
1048 Attribute::unchecked_single("address", "Packet Street 6"),
1049 Attribute::unchecked_single("address", "128 Series of Tubes"),
1050 Attribute::unchecked_single("address", "Internet"),
1051 ]),
1052 )]
1053 #[case(
1054 Object::new_parsed(
1055 concat!(
1056 "role: ACME Company\n",
1057 "address: Packet Street 6\n",
1058 "address: 128 Series of Tubes\n",
1059 "address: Internet\n",
1060 "\n"
1061 ),
1062 vec![
1063 Attribute::unchecked_single("role", "ACME Company"),
1064 Attribute::unchecked_single("address", "Packet Street 6"),
1065 Attribute::unchecked_single("address", "128 Series of Tubes"),
1066 Attribute::unchecked_single("address", "Internet"),
1067 ],
1068 ),
1069 Object::new(
1070 vec![
1071 Attribute::unchecked_single("role", "ACME Company"),
1072 Attribute::unchecked_single("address", "Packet Street 6"),
1073 Attribute::unchecked_single("address", "128 Series of Tubes"),
1074 Attribute::unchecked_single("address", "Internet"),
1075 ],
1076 ),
1077 )]
1078 fn eq_objects_are_eq(#[case] object_1: Object, #[case] object_2: Object) {
1080 assert_eq!(object_1, object_2);
1081 }
1082
1083 #[rstest]
1084 #[case(
1085 Object::new(
1086 vec![
1087 Attribute::unchecked_single("role", "Umbrella Corporation"),
1088 Attribute::unchecked_single("address", "Paraguas Street"),
1089 Attribute::unchecked_single("address", "Raccoon City"),
1090 Attribute::unchecked_single("address", "Colorado"),
1091 ]),
1092 Object::new(
1093 vec![
1094 Attribute::unchecked_single("role", "ACME Company"),
1095 Attribute::unchecked_single("address", "Packet Street 6"),
1096 Attribute::unchecked_single("address", "128 Series of Tubes"),
1097 Attribute::unchecked_single("address", "Internet"),
1098 ]),
1099 )]
1100 fn ne_objects_are_ne(#[case] object_1: Object, #[case] object_2: Object) {
1102 assert_ne!(object_1, object_2);
1103 }
1104}