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 let name = match tokens.next() {
306 Some((IDENT, name)) => name,
307 _ => return Err("Expected package name".to_string()),
308 };
309
310 eat_whitespace(&mut tokens);
311
312 let archqual = if let Some((COLON, _)) = tokens.peek() {
313 tokens.next();
314 match tokens.next() {
315 Some((IDENT, s)) => Some(s),
316 _ => return Err("Expected architecture qualifier".to_string()),
317 }
318 } else {
319 None
320 };
321 eat_whitespace(&mut tokens);
322
323 let version = if let Some((L_PARENS, _)) = tokens.peek() {
324 tokens.next();
325 eat_whitespace(&mut tokens);
326 let mut constraint = String::new();
327 while let Some((kind, t)) = tokens.peek() {
328 match kind {
329 EQUAL | L_ANGLE | R_ANGLE => {
330 constraint.push_str(t);
331 tokens.next();
332 }
333 _ => break,
334 }
335 }
336 let constraint = constraint.parse()?;
337 eat_whitespace(&mut tokens);
338 let mut version_string = String::new();
340 while let Some((kind, s)) = tokens.peek() {
341 match kind {
342 R_PARENS => break,
343 IDENT | COLON => version_string.push_str(s),
344 n => return Err(format!("Unexpected token: {:?}", n)),
345 }
346 tokens.next();
347 }
348 let version = version_string
349 .parse()
350 .map_err(|e: debversion::ParseError| e.to_string())?;
351 eat_whitespace(&mut tokens);
352 if let Some((R_PARENS, _)) = tokens.next() {
353 } else {
354 return Err(format!("Expected ')', found {:?}", tokens.next()));
355 }
356 Some((constraint, version))
357 } else {
358 None
359 };
360
361 eat_whitespace(&mut tokens);
362
363 let architectures = if let Some((L_BRACKET, _)) = tokens.peek() {
364 tokens.next();
365 let mut archs = Vec::new();
366 loop {
367 match tokens.next() {
368 Some((IDENT, s)) => archs.push(s),
369 Some((WHITESPACE, _)) => {}
370 Some((R_BRACKET, _)) => break,
371 _ => return Err("Expected architecture name".to_string()),
372 }
373 }
374 Some(archs)
375 } else {
376 None
377 };
378
379 eat_whitespace(&mut tokens);
380
381 let mut profiles = Vec::new();
382 while let Some((L_ANGLE, _)) = tokens.peek() {
383 tokens.next();
384 loop {
385 let mut profile = Vec::new();
386 loop {
387 match tokens.next() {
388 Some((NOT, _)) => {
389 let profile_name = match tokens.next() {
390 Some((IDENT, s)) => s,
391 _ => return Err("Expected profile name".to_string()),
392 };
393 profile.push(BuildProfile::Disabled(profile_name));
394 }
395 Some((IDENT, s)) => profile.push(BuildProfile::Enabled(s)),
396 Some((WHITESPACE, _)) => {}
397 _ => return Err("Expected profile name".to_string()),
398 }
399 if let Some((COMMA, _)) = tokens.peek() {
400 tokens.next();
401 } else {
402 break;
403 }
404 }
405 profiles.push(profile);
406 if let Some((R_ANGLE, _)) = tokens.next() {
407 eat_whitespace(&mut tokens);
408 break;
409 }
410 }
411 }
412
413 eat_whitespace(&mut tokens);
414
415 if let Some((kind, _)) = tokens.next() {
416 return Err(format!("Unexpected token: {:?}", kind));
417 }
418
419 Ok(Relation {
420 name,
421 archqual,
422 architectures,
423 version,
424 profiles,
425 })
426 }
427}
428
429impl std::str::FromStr for Relations {
430 type Err = String;
431
432 fn from_str(s: &str) -> Result<Self, Self::Err> {
433 let mut relations = Vec::new();
434 if s.is_empty() {
435 return Ok(Relations(relations));
436 }
437 for entry in s.split(',') {
438 let entry = entry.trim();
439 if entry.is_empty() {
440 continue;
442 }
443 let entry_relations = entry.split('|').map(|relation| {
444 let relation = relation.trim();
445 if relation.is_empty() {
446 return Err("Empty relation".to_string());
447 }
448 relation.parse()
449 });
450 relations.push(entry_relations.collect::<Result<Vec<_>, _>>()?);
451 }
452 Ok(Relations(relations))
453 }
454}
455
456#[cfg(feature = "serde")]
457impl<'de> serde::Deserialize<'de> for Relations {
458 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
459 where
460 D: serde::Deserializer<'de>,
461 {
462 let s = String::deserialize(deserializer)?;
463 s.parse().map_err(serde::de::Error::custom)
464 }
465}
466
467#[cfg(feature = "serde")]
468impl serde::Serialize for Relations {
469 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
470 where
471 S: serde::Serializer,
472 {
473 self.to_string().serialize(serializer)
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, Eq, Hash)]
479pub struct SourceRelation {
480 pub name: String,
482 pub version: Option<debversion::Version>,
484}
485
486impl std::fmt::Display for SourceRelation {
487 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
488 write!(f, "{name}", name = self.name)?;
489 if let Some(ref version) = self.version {
490 write!(f, " (= {version})")?;
491 }
492 Ok(())
493 }
494}
495
496impl std::str::FromStr for SourceRelation {
497 type Err = String;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 let tokens = lex(s);
501
502 let mut tokens = tokens.into_iter().peekable();
503
504 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
505 while let Some((WHITESPACE, _)) = tokens.peek() {
506 tokens.next();
507 }
508 }
509
510 let name = match tokens.next() {
511 Some((IDENT, name)) => name,
512
513 _ => return Err("Expected package name".to_string()),
514 };
515
516 eat_whitespace(&mut tokens);
517
518 match tokens.next() {
519 None => {
520 return Ok(Self {
521 name,
522 version: None,
523 });
524 }
525 Some((L_PARENS, _)) => {}
526 _ => return Err("Unexpected token after package name".to_string()),
527 };
528
529 let mut version_str = "".to_string();
530 while let Some((token, value)) = tokens.next() {
531 if token != R_PARENS {
532 version_str.push_str(&value);
533 } else {
534 break;
535 }
536 }
537
538 let version: debversion::Version = version_str
539 .parse()
540 .map_err(|err| format!("Failed to parse version: {err}"))?;
541
542 eat_whitespace(&mut tokens);
543
544 if tokens.next().is_some() {
545 return Err("Unexpected tokens after version".to_string());
546 }
547
548 Ok(Self {
549 name,
550 version: Some(version),
551 })
552 }
553}
554
555#[cfg(feature = "serde")]
556impl<'de> serde::Deserialize<'de> for SourceRelation {
557 fn deserialize<D>(deserializer: D) -> Result<SourceRelation, D::Error>
558 where
559 D: serde::Deserializer<'de>,
560 {
561 let s = String::deserialize(deserializer)?;
562
563 s.parse().map_err(serde::de::Error::custom)
564 }
565}
566
567#[cfg(feature = "serde")]
568impl serde::Serialize for SourceRelation {
569 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570 where
571 S: serde::Serializer,
572 {
573 self.to_string().serialize(serializer)
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[test]
582 fn test_parse() {
583 let input = "python3-dulwich";
584 let parsed: Relations = input.parse().unwrap();
585 assert_eq!(parsed.to_string(), input);
586 assert_eq!(parsed.len(), 1);
587 let entry = &parsed[0];
588 assert_eq!(entry.len(), 1);
589 let relation = &entry[0];
590 assert_eq!(relation.to_string(), "python3-dulwich");
591 assert_eq!(relation.version, None);
592
593 let input = "python3-dulwich (>= 0.20.21)";
594 let parsed: Relations = input.parse().unwrap();
595 assert_eq!(parsed.to_string(), input);
596 assert_eq!(parsed.len(), 1);
597 let entry = &parsed[0];
598 assert_eq!(entry.len(), 1);
599 let relation = &entry[0];
600 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
601 assert_eq!(
602 relation.version,
603 Some((
604 VersionConstraint::GreaterThanEqual,
605 "0.20.21".parse().unwrap()
606 ))
607 );
608 }
609
610 #[test]
611 fn test_multiple() {
612 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
613 let parsed: Relations = input.parse().unwrap();
614 assert_eq!(parsed.to_string(), input);
615 assert_eq!(parsed.len(), 2);
616 let entry = &parsed[0];
617 assert_eq!(entry.len(), 1);
618 let relation = &entry[0];
619 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
620 assert_eq!(
621 relation.version,
622 Some((
623 VersionConstraint::GreaterThanEqual,
624 "0.20.21".parse().unwrap()
625 ))
626 );
627 let entry = &parsed[1];
628 assert_eq!(entry.len(), 1);
629 let relation = &entry[0];
630 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
631 assert_eq!(
632 relation.version,
633 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
634 );
635 }
636
637 #[test]
638 fn test_architectures() {
639 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
640 let parsed: Relations = input.parse().unwrap();
641 assert_eq!(parsed.to_string(), input);
642 assert_eq!(parsed.len(), 1);
643 let entry = &parsed[0];
644 assert_eq!(
645 entry[0].to_string(),
646 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
647 );
648 assert_eq!(entry.len(), 1);
649 let relation = &entry[0];
650 assert_eq!(
651 relation.to_string(),
652 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
653 );
654 assert_eq!(relation.version, None);
655 assert_eq!(
656 relation.architectures.as_ref().unwrap(),
657 &vec![
658 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
659 ]
660 .into_iter()
661 .map(|s| s.to_string())
662 .collect::<Vec<_>>()
663 );
664 }
665
666 #[test]
667 fn test_profiles() {
668 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
669 let parsed: Relations = input.parse().unwrap();
670 assert_eq!(parsed.to_string(), input);
671 assert_eq!(parsed.iter().count(), 2);
672 let entry = parsed.iter().next().unwrap();
673 assert_eq!(
674 entry[0].to_string(),
675 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
676 );
677 assert_eq!(entry.len(), 1);
678 let relation = entry[0];
679 assert_eq!(
680 relation.to_string(),
681 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
682 );
683 assert_eq!(
684 relation.version,
685 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
686 );
687 assert_eq!(
688 relation.architectures.as_ref().unwrap(),
689 &["i386", "arm"]
690 .into_iter()
691 .map(|s| s.to_string())
692 .collect::<Vec<_>>()
693 );
694 assert_eq!(
695 relation.profiles,
696 vec![
697 vec![BuildProfile::Disabled("nocheck".to_string())],
698 vec![BuildProfile::Disabled("cross".to_string())]
699 ]
700 );
701 }
702
703 #[cfg(feature = "serde")]
704 #[test]
705 fn test_serde_relations() {
706 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
707 let parsed: Relations = input.parse().unwrap();
708 let serialized = serde_json::to_string(&parsed).unwrap();
709 assert_eq!(
710 serialized,
711 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
712 );
713 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
714 assert_eq!(deserialized, parsed);
715 }
716
717 #[cfg(feature = "serde")]
718 #[test]
719 fn test_serde_relation() {
720 let input = "python3-dulwich (>= 0.20.21)";
721 let parsed: Relation = input.parse().unwrap();
722 let serialized = serde_json::to_string(&parsed).unwrap();
723 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
724 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
725 assert_eq!(deserialized, parsed);
726 }
727
728 #[test]
729 fn test_relations_is_empty() {
730 let input = "python3-dulwich (>= 0.20.21)";
731 let parsed: Relations = input.parse().unwrap();
732 assert!(!parsed.is_empty());
733 let input = "";
734 let parsed: Relations = input.parse().unwrap();
735 assert!(parsed.is_empty());
736 }
737
738 #[test]
739 fn test_relations_len() {
740 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
741 let parsed: Relations = input.parse().unwrap();
742 assert_eq!(parsed.len(), 2);
743 }
744
745 #[test]
746 fn test_relations_remove() {
747 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
748 let mut parsed: Relations = input.parse().unwrap();
749 parsed.remove(1);
750 assert_eq!(parsed.len(), 1);
751 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
752 }
753
754 #[test]
755 fn test_relations_satisfied_by() {
756 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
757 let parsed: Relations = input.parse().unwrap();
758 assert!(
759 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
760 match name {
761 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
762 _ => None,
763 }
764 })
765 );
766 assert!(
767 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
768 match name {
769 "python3-dulwich" => Some("0.21".parse().unwrap()),
770 _ => None,
771 }
772 })
773 );
774 }
775
776 #[test]
777 fn test_relation_satisfied_by() {
778 let input = "python3-dulwich (>= 0.20.21)";
779 let parsed: Relation = input.parse().unwrap();
780 assert!(
781 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
782 match name {
783 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
784 _ => None,
785 }
786 })
787 );
788 assert!(
789 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
790 match name {
791 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
792 _ => None,
793 }
794 })
795 );
796 }
797
798 #[test]
799 fn test_relations_from_iter() {
800 let relation1 = Relation::build("python3-dulwich")
801 .version(VersionConstraint::GreaterThanEqual, "0.19.0")
802 .build();
803 let relation2 = Relation::build("python3-requests").build();
804
805 let relations: Relations = vec![relation1, relation2].into_iter().collect();
806
807 assert_eq!(
808 relations.to_string(),
809 "python3-dulwich (>= 0.19.0), python3-requests"
810 );
811 }
812
813 #[test]
814 fn test_source_relation_without_version() {
815 let input = "some-package";
816 let parsed: SourceRelation = input.parse().unwrap();
817 assert_eq!(parsed.name, input);
818 assert!(parsed.version.is_none());
819 }
820
821 #[test]
822 fn test_source_relation_with_valid_version() {
823 let input = "some-package (1.2.3+dfsg1-5~bpo13+1)";
824 let parsed: SourceRelation = input.parse().unwrap();
825 assert_eq!(parsed.name, "some-package");
826
827 let expected_version = debversion::Version {
828 epoch: None,
829 upstream_version: "1.2.3+dfsg1".to_string(),
830 debian_revision: Some("5~bpo13+1".to_string()),
831 };
832 assert_eq!(parsed.version, Some(expected_version));
833 }
834
835 #[test]
836 fn test_source_relation_with_invalid_version() {
837 let input = "some-package (1.2_@!.3+dfsg1-5~bpo13+1)";
838
839 let attempted_parse: Result<SourceRelation, String> = input.parse();
840 assert!(attempted_parse.is_err())
841 }
842}