1use std::iter::Peekable;
23
24use crate::relations::SyntaxKind::*;
25use crate::relations::{lex, BuildProfile, SyntaxKind, VersionConstraint};
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct Relation {
30 pub name: String,
32 pub archqual: Option<String>,
34 pub architectures: Option<Vec<String>>,
36 pub version: Option<(VersionConstraint, debversion::Version)>,
38 pub profiles: Vec<Vec<BuildProfile>>,
40}
41
42impl Default for Relation {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Relation {
49 pub fn new() -> Self {
51 Self {
52 name: String::new(),
53 archqual: None,
54 architectures: None,
55 version: None,
56 profiles: Vec::new(),
57 }
58 }
59
60 pub fn build(name: &str) -> RelationBuilder {
62 RelationBuilder::new(name)
63 }
64
65 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup) -> bool {
81 let actual = package_version.lookup_version(self.name.as_str());
82 if let Some((vc, version)) = &self.version {
83 if let Some(actual) = actual {
84 match vc {
85 VersionConstraint::GreaterThanEqual => actual.as_ref() >= version,
86 VersionConstraint::LessThanEqual => actual.as_ref() <= version,
87 VersionConstraint::Equal => actual.as_ref() == version,
88 VersionConstraint::GreaterThan => actual.as_ref() > version,
89 VersionConstraint::LessThan => actual.as_ref() < version,
90 }
91 } else {
92 false
93 }
94 } else {
95 actual.is_some()
96 }
97 }
98}
99
100pub struct RelationBuilder {
102 name: String,
103
104 archqual: Option<String>,
105 architectures: Option<Vec<String>>,
106 version: Option<(VersionConstraint, debversion::Version)>,
107 profiles: Vec<Vec<BuildProfile>>,
108}
109
110impl RelationBuilder {
111 pub fn new(name: &str) -> Self {
113 Self {
114 name: name.to_string(),
115 archqual: None,
116 architectures: None,
117 version: None,
118 profiles: Vec::new(),
119 }
120 }
121
122 pub fn archqual(mut self, archqual: &str) -> Self {
124 self.archqual = Some(archqual.to_string());
125 self
126 }
127
128 pub fn architectures(mut self, architectures: Vec<&str>) -> Self {
130 self.architectures = Some(architectures.into_iter().map(|s| s.to_string()).collect());
131 self
132 }
133
134 pub fn version(mut self, constraint: VersionConstraint, version: &str) -> Self {
136 self.version = Some((constraint, version.parse().unwrap()));
137 self
138 }
139
140 pub fn profile(mut self, profile: Vec<BuildProfile>) -> Self {
142 self.profiles.push(profile);
143 self
144 }
145
146 pub fn build(self) -> Relation {
148 Relation {
149 name: self.name,
150 archqual: self.archqual,
151 architectures: self.architectures,
152 version: self.version,
153 profiles: self.profiles,
154 }
155 }
156}
157
158impl std::fmt::Display for Relation {
159 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
160 write!(f, "{}", self.name)?;
161 if let Some(archqual) = &self.archqual {
162 write!(f, ":{}", archqual)?;
163 }
164 if let Some((constraint, version)) = &self.version {
165 write!(f, " ({} {})", constraint, version)?;
166 }
167 if let Some(archs) = &self.architectures {
168 write!(f, " [{}]", archs.join(" "))?;
169 }
170 for profile in &self.profiles {
171 write!(f, " <")?;
172 for (i, profile) in profile.iter().enumerate() {
173 if i > 0 {
174 write!(f, ", ")?;
175 }
176 write!(f, "{}", profile)?;
177 }
178 write!(f, ">")?;
179 }
180 Ok(())
181 }
182}
183
184#[cfg(feature = "serde")]
185impl<'de> serde::Deserialize<'de> for Relation {
186 fn deserialize<D>(deserializer: D) -> Result<Relation, D::Error>
187 where
188 D: serde::Deserializer<'de>,
189 {
190 let s = String::deserialize(deserializer)?;
191 s.parse().map_err(serde::de::Error::custom)
192 }
193}
194
195#[cfg(feature = "serde")]
196impl serde::Serialize for Relation {
197 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: serde::Serializer,
200 {
201 self.to_string().serialize(serializer)
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Hash)]
207pub struct Relations(pub Vec<Vec<Relation>>);
208
209impl std::ops::Index<usize> for Relations {
210 type Output = Vec<Relation>;
211
212 fn index(&self, index: usize) -> &Self::Output {
213 &self.0[index]
214 }
215}
216
217impl std::ops::IndexMut<usize> for Relations {
218 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
219 &mut self.0[index]
220 }
221}
222
223impl FromIterator<Vec<Relation>> for Relations {
224 fn from_iter<I: IntoIterator<Item = Vec<Relation>>>(iter: I) -> Self {
225 Self(iter.into_iter().collect())
226 }
227}
228
229impl Default for Relations {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl Relations {
236 pub fn new() -> Self {
238 Self(Vec::new())
239 }
240
241 pub fn remove(&mut self, index: usize) {
243 self.0.remove(index);
244 }
245
246 pub fn iter(&self) -> impl Iterator<Item = Vec<&Relation>> {
248 self.0.iter().map(|entry| entry.iter().collect())
249 }
250
251 pub fn len(&self) -> usize {
253 self.0.len()
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.0.is_empty()
259 }
260
261 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
263 self.0
264 .iter()
265 .all(|e| e.iter().any(|r| r.satisfied_by(package_version)))
266 }
267}
268
269impl FromIterator<Relation> for Relations {
270 fn from_iter<I: IntoIterator<Item = Relation>>(iter: I) -> Self {
271 Self(iter.into_iter().map(|r| vec![r]).collect())
272 }
273}
274
275impl std::fmt::Display for Relations {
276 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
277 for (i, entry) in self.0.iter().enumerate() {
278 if i > 0 {
279 f.write_str(", ")?;
280 }
281 for (j, relation) in entry.iter().enumerate() {
282 if j > 0 {
283 f.write_str(" | ")?;
284 }
285 write!(f, "{}", relation)?;
286 }
287 }
288 Ok(())
289 }
290}
291
292impl std::str::FromStr for Relation {
293 type Err = String;
294
295 fn from_str(s: &str) -> Result<Self, Self::Err> {
296 let tokens = lex(s);
297 let mut tokens = tokens.into_iter().peekable();
298
299 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
300 while let Some((WHITESPACE, _)) = tokens.peek() {
301 tokens.next();
302 }
303 }
304
305 fn read_substvar(
308 tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>,
309 ) -> Result<String, String> {
310 let mut text = String::from("$");
311 match tokens.next() {
312 Some((L_CURLY, s)) => text.push_str(&s),
313 _ => return Err("Expected '{' after '$' in substvar".to_string()),
314 }
315 loop {
316 match tokens.next() {
317 Some((R_CURLY, s)) => {
318 text.push_str(&s);
319 return Ok(text);
320 }
321 Some((_, s)) => text.push_str(&s),
322 None => return Err("Unterminated substvar".to_string()),
323 }
324 }
325 }
326
327 let name = match tokens.next() {
330 Some((IDENT, name)) => name,
331 Some((DOLLAR, _)) => read_substvar(&mut tokens)?,
332 _ => return Err("Expected package name".to_string()),
333 };
334
335 eat_whitespace(&mut tokens);
336
337 let archqual = if let Some((COLON, _)) = tokens.peek() {
338 tokens.next();
339 match tokens.next() {
340 Some((IDENT, s)) => Some(s),
341 _ => return Err("Expected architecture qualifier".to_string()),
342 }
343 } else {
344 None
345 };
346 eat_whitespace(&mut tokens);
347
348 let version = if let Some((L_PARENS, _)) = tokens.peek() {
349 tokens.next();
350 eat_whitespace(&mut tokens);
351 let mut constraint = String::new();
352 while let Some((kind, t)) = tokens.peek() {
353 match kind {
354 EQUAL | L_ANGLE | R_ANGLE => {
355 constraint.push_str(t);
356 tokens.next();
357 }
358 _ => break,
359 }
360 }
361 let constraint = constraint.parse()?;
362 eat_whitespace(&mut tokens);
363 let mut version_string = String::new();
365 while let Some((kind, s)) = tokens.peek() {
366 match kind {
367 R_PARENS => break,
368 IDENT | COLON => version_string.push_str(s),
369 n => return Err(format!("Unexpected token: {:?}", n)),
370 }
371 tokens.next();
372 }
373 let version = version_string
374 .parse()
375 .map_err(|e: debversion::ParseError| e.to_string())?;
376 eat_whitespace(&mut tokens);
377 if let Some((R_PARENS, _)) = tokens.next() {
378 } else {
379 return Err(format!("Expected ')', found {:?}", tokens.next()));
380 }
381 Some((constraint, version))
382 } else {
383 None
384 };
385
386 eat_whitespace(&mut tokens);
387
388 let architectures = if let Some((L_BRACKET, _)) = tokens.peek() {
389 tokens.next();
390 let mut archs = Vec::new();
391 loop {
392 match tokens.next() {
393 Some((IDENT, s)) => archs.push(s),
394 Some((WHITESPACE, _)) => {}
395 Some((R_BRACKET, _)) => break,
396 _ => return Err("Expected architecture name".to_string()),
397 }
398 }
399 Some(archs)
400 } else {
401 None
402 };
403
404 eat_whitespace(&mut tokens);
405
406 let mut profiles = Vec::new();
407 while let Some((L_ANGLE, _)) = tokens.peek() {
408 tokens.next();
409 loop {
410 let mut profile = Vec::new();
411 loop {
412 match tokens.next() {
413 Some((NOT, _)) => {
414 let profile_name = match tokens.next() {
415 Some((IDENT, s)) => s,
416 _ => return Err("Expected profile name".to_string()),
417 };
418 profile.push(BuildProfile::Disabled(profile_name));
419 }
420 Some((IDENT, s)) => profile.push(BuildProfile::Enabled(s)),
421 Some((WHITESPACE, _)) => {}
422 _ => return Err("Expected profile name".to_string()),
423 }
424 if let Some((COMMA, _)) = tokens.peek() {
425 tokens.next();
426 } else {
427 break;
428 }
429 }
430 profiles.push(profile);
431 if let Some((R_ANGLE, _)) = tokens.next() {
432 eat_whitespace(&mut tokens);
433 break;
434 }
435 }
436 }
437
438 eat_whitespace(&mut tokens);
439
440 if let Some((kind, _)) = tokens.next() {
441 return Err(format!("Unexpected token: {:?}", kind));
442 }
443
444 Ok(Relation {
445 name,
446 archqual,
447 architectures,
448 version,
449 profiles,
450 })
451 }
452}
453
454impl std::str::FromStr for Relations {
455 type Err = String;
456
457 fn from_str(s: &str) -> Result<Self, Self::Err> {
458 let mut relations = Vec::new();
459 if s.is_empty() {
460 return Ok(Relations(relations));
461 }
462 for entry in s.split(',') {
463 let entry = entry.trim();
464 if entry.is_empty() {
465 continue;
467 }
468 let entry_relations = entry.split('|').map(|relation| {
469 let relation = relation.trim();
470 if relation.is_empty() {
471 return Err("Empty relation".to_string());
472 }
473 relation.parse()
474 });
475 relations.push(entry_relations.collect::<Result<Vec<_>, _>>()?);
476 }
477 Ok(Relations(relations))
478 }
479}
480
481#[cfg(feature = "serde")]
482impl<'de> serde::Deserialize<'de> for Relations {
483 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
484 where
485 D: serde::Deserializer<'de>,
486 {
487 let s = String::deserialize(deserializer)?;
488 s.parse().map_err(serde::de::Error::custom)
489 }
490}
491
492#[cfg(feature = "serde")]
493impl serde::Serialize for Relations {
494 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
495 where
496 S: serde::Serializer,
497 {
498 self.to_string().serialize(serializer)
499 }
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Hash)]
504pub struct SourceRelation {
505 pub name: String,
507 pub version: Option<debversion::Version>,
509}
510
511impl std::fmt::Display for SourceRelation {
512 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
513 write!(f, "{name}", name = self.name)?;
514 if let Some(ref version) = self.version {
515 write!(f, " (= {version})")?;
516 }
517 Ok(())
518 }
519}
520
521impl std::str::FromStr for SourceRelation {
522 type Err = String;
523
524 fn from_str(s: &str) -> Result<Self, Self::Err> {
525 let tokens = lex(s);
526
527 let mut tokens = tokens.into_iter().peekable();
528
529 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
530 while let Some((WHITESPACE, _)) = tokens.peek() {
531 tokens.next();
532 }
533 }
534
535 let name = match tokens.next() {
536 Some((IDENT, name)) => name,
537
538 _ => return Err("Expected package name".to_string()),
539 };
540
541 eat_whitespace(&mut tokens);
542
543 match tokens.next() {
544 None => {
545 return Ok(Self {
546 name,
547 version: None,
548 });
549 }
550 Some((L_PARENS, _)) => {}
551 _ => return Err("Unexpected token after package name".to_string()),
552 };
553
554 let mut version_str = "".to_string();
555 for (token, value) in tokens.by_ref() {
556 if token != R_PARENS {
557 version_str.push_str(&value);
558 } else {
559 break;
560 }
561 }
562
563 let version: debversion::Version = version_str
564 .parse()
565 .map_err(|err| format!("Failed to parse version: {err}"))?;
566
567 eat_whitespace(&mut tokens);
568
569 if tokens.next().is_some() {
570 return Err("Unexpected tokens after version".to_string());
571 }
572
573 Ok(Self {
574 name,
575 version: Some(version),
576 })
577 }
578}
579
580#[cfg(feature = "serde")]
581impl<'de> serde::Deserialize<'de> for SourceRelation {
582 fn deserialize<D>(deserializer: D) -> Result<SourceRelation, D::Error>
583 where
584 D: serde::Deserializer<'de>,
585 {
586 let s = String::deserialize(deserializer)?;
587
588 s.parse().map_err(serde::de::Error::custom)
589 }
590}
591
592#[cfg(feature = "serde")]
593impl serde::Serialize for SourceRelation {
594 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
595 where
596 S: serde::Serializer,
597 {
598 self.to_string().serialize(serializer)
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605
606 #[test]
607 fn test_parse() {
608 let input = "python3-dulwich";
609 let parsed: Relations = input.parse().unwrap();
610 assert_eq!(parsed.to_string(), input);
611 assert_eq!(parsed.len(), 1);
612 let entry = &parsed[0];
613 assert_eq!(entry.len(), 1);
614 let relation = &entry[0];
615 assert_eq!(relation.to_string(), "python3-dulwich");
616 assert_eq!(relation.version, None);
617
618 let input = "python3-dulwich (>= 0.20.21)";
619 let parsed: Relations = input.parse().unwrap();
620 assert_eq!(parsed.to_string(), input);
621 assert_eq!(parsed.len(), 1);
622 let entry = &parsed[0];
623 assert_eq!(entry.len(), 1);
624 let relation = &entry[0];
625 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
626 assert_eq!(
627 relation.version,
628 Some((
629 VersionConstraint::GreaterThanEqual,
630 "0.20.21".parse().unwrap()
631 ))
632 );
633 }
634
635 #[test]
636 fn test_substvar() {
637 let input = "${misc:Depends}";
640 let parsed: Relations = input.parse().unwrap();
641 assert_eq!(parsed.to_string(), input);
642 assert_eq!(parsed.len(), 1);
643 let relation = &parsed[0][0];
644 assert_eq!(relation.name, "${misc:Depends}");
645 assert_eq!(relation.version, None);
646
647 let input = "${misc:Depends}, python3-dulwich (>= 0.20)";
649 let parsed: Relations = input.parse().unwrap();
650 assert_eq!(parsed.len(), 2);
651 assert_eq!(parsed[0][0].name, "${misc:Depends}");
652 assert_eq!(parsed[1][0].name, "python3-dulwich");
653 }
654
655 #[test]
656 fn test_multiple() {
657 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
658 let parsed: Relations = input.parse().unwrap();
659 assert_eq!(parsed.to_string(), input);
660 assert_eq!(parsed.len(), 2);
661 let entry = &parsed[0];
662 assert_eq!(entry.len(), 1);
663 let relation = &entry[0];
664 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
665 assert_eq!(
666 relation.version,
667 Some((
668 VersionConstraint::GreaterThanEqual,
669 "0.20.21".parse().unwrap()
670 ))
671 );
672 let entry = &parsed[1];
673 assert_eq!(entry.len(), 1);
674 let relation = &entry[0];
675 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
676 assert_eq!(
677 relation.version,
678 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
679 );
680 }
681
682 #[test]
683 fn test_architectures() {
684 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
685 let parsed: Relations = input.parse().unwrap();
686 assert_eq!(parsed.to_string(), input);
687 assert_eq!(parsed.len(), 1);
688 let entry = &parsed[0];
689 assert_eq!(
690 entry[0].to_string(),
691 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
692 );
693 assert_eq!(entry.len(), 1);
694 let relation = &entry[0];
695 assert_eq!(
696 relation.to_string(),
697 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
698 );
699 assert_eq!(relation.version, None);
700 assert_eq!(
701 relation.architectures.as_ref().unwrap(),
702 &vec![
703 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
704 ]
705 .into_iter()
706 .map(|s| s.to_string())
707 .collect::<Vec<_>>()
708 );
709 }
710
711 #[test]
712 fn test_profiles() {
713 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
714 let parsed: Relations = input.parse().unwrap();
715 assert_eq!(parsed.to_string(), input);
716 assert_eq!(parsed.iter().count(), 2);
717 let entry = parsed.iter().next().unwrap();
718 assert_eq!(
719 entry[0].to_string(),
720 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
721 );
722 assert_eq!(entry.len(), 1);
723 let relation = entry[0];
724 assert_eq!(
725 relation.to_string(),
726 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
727 );
728 assert_eq!(
729 relation.version,
730 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
731 );
732 assert_eq!(
733 relation.architectures.as_ref().unwrap(),
734 &["i386", "arm"]
735 .into_iter()
736 .map(|s| s.to_string())
737 .collect::<Vec<_>>()
738 );
739 assert_eq!(
740 relation.profiles,
741 vec![
742 vec![BuildProfile::Disabled("nocheck".to_string())],
743 vec![BuildProfile::Disabled("cross".to_string())]
744 ]
745 );
746 }
747
748 #[cfg(feature = "serde")]
749 #[test]
750 fn test_serde_relations() {
751 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
752 let parsed: Relations = input.parse().unwrap();
753 let serialized = serde_json::to_string(&parsed).unwrap();
754 assert_eq!(
755 serialized,
756 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
757 );
758 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
759 assert_eq!(deserialized, parsed);
760 }
761
762 #[cfg(feature = "serde")]
763 #[test]
764 fn test_serde_relation() {
765 let input = "python3-dulwich (>= 0.20.21)";
766 let parsed: Relation = input.parse().unwrap();
767 let serialized = serde_json::to_string(&parsed).unwrap();
768 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
769 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
770 assert_eq!(deserialized, parsed);
771 }
772
773 #[test]
774 fn test_relations_is_empty() {
775 let input = "python3-dulwich (>= 0.20.21)";
776 let parsed: Relations = input.parse().unwrap();
777 assert!(!parsed.is_empty());
778 let input = "";
779 let parsed: Relations = input.parse().unwrap();
780 assert!(parsed.is_empty());
781 }
782
783 #[test]
784 fn test_relations_len() {
785 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
786 let parsed: Relations = input.parse().unwrap();
787 assert_eq!(parsed.len(), 2);
788 }
789
790 #[test]
791 fn test_relations_remove() {
792 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
793 let mut parsed: Relations = input.parse().unwrap();
794 parsed.remove(1);
795 assert_eq!(parsed.len(), 1);
796 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
797 }
798
799 #[test]
800 fn test_relations_satisfied_by() {
801 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
802 let parsed: Relations = input.parse().unwrap();
803 assert!(
804 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
805 match name {
806 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
807 _ => None,
808 }
809 })
810 );
811 assert!(
812 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
813 match name {
814 "python3-dulwich" => Some("0.21".parse().unwrap()),
815 _ => None,
816 }
817 })
818 );
819 }
820
821 #[test]
822 fn test_relation_satisfied_by() {
823 let input = "python3-dulwich (>= 0.20.21)";
824 let parsed: Relation = input.parse().unwrap();
825 assert!(
826 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
827 match name {
828 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
829 _ => None,
830 }
831 })
832 );
833 assert!(
834 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
835 match name {
836 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
837 _ => None,
838 }
839 })
840 );
841 }
842
843 #[test]
844 fn test_relations_from_iter() {
845 let relation1 = Relation::build("python3-dulwich")
846 .version(VersionConstraint::GreaterThanEqual, "0.19.0")
847 .build();
848 let relation2 = Relation::build("python3-requests").build();
849
850 let relations: Relations = vec![relation1, relation2].into_iter().collect();
851
852 assert_eq!(
853 relations.to_string(),
854 "python3-dulwich (>= 0.19.0), python3-requests"
855 );
856 }
857
858 #[test]
859 fn test_source_relation_without_version() {
860 let input = "some-package";
861 let parsed: SourceRelation = input.parse().unwrap();
862 assert_eq!(parsed.name, input);
863 assert!(parsed.version.is_none());
864 }
865
866 #[test]
867 fn test_source_relation_with_valid_version() {
868 let input = "some-package (1.2.3+dfsg1-5~bpo13+1)";
869 let parsed: SourceRelation = input.parse().unwrap();
870 assert_eq!(parsed.name, "some-package");
871
872 let expected_version = debversion::Version {
873 epoch: None,
874 upstream_version: "1.2.3+dfsg1".to_string(),
875 debian_revision: Some("5~bpo13+1".to_string()),
876 };
877 assert_eq!(parsed.version, Some(expected_version));
878 }
879
880 #[test]
881 fn test_source_relation_with_invalid_version() {
882 let input = "some-package (1.2_@!.3+dfsg1-5~bpo13+1)";
883
884 let attempted_parse: Result<SourceRelation, String> = input.parse();
885 assert!(attempted_parse.is_err())
886 }
887}