-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathvalidation.rs
More file actions
121 lines (107 loc) · 3.5 KB
/
validation.rs
File metadata and controls
121 lines (107 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use crate::{is_default, Package, VersionInfo};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt::Display};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub(crate) struct RawVersionInfo {
pub packages: Vec<Package>,
#[serde(default)]
#[serde(skip_serializing_if = "is_default")]
pub format: u32,
}
pub enum ValidationError {
MultipleRoots,
CyclicDependency,
}
impl Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationError::MultipleRoots => {
write!(f, "Multiple root packages specified in the input JSON")
}
ValidationError::CyclicDependency => {
write!(f, "The input JSON specifies a cyclic dependency graph")
}
}
}
}
impl TryFrom<RawVersionInfo> for VersionInfo {
type Error = ValidationError;
fn try_from(v: RawVersionInfo) -> Result<Self, Self::Error> {
if has_multiple_root_packages(&v) {
Err(ValidationError::MultipleRoots)
} else if has_cylic_dependencies(&v) {
Err(ValidationError::CyclicDependency)
} else {
Ok(VersionInfo {
packages: v.packages,
format: v.format,
})
}
}
}
fn has_multiple_root_packages(v: &RawVersionInfo) -> bool {
let mut seen_a_root = false;
for package in &v.packages {
if package.root {
if seen_a_root {
return true;
} else {
seen_a_root = true;
}
}
}
false
}
fn has_cylic_dependencies(v: &RawVersionInfo) -> bool {
// I've reviewed the `topological_sort` crate and it appears to be high-quality,
// so I'm not concerned about having it exposed to untrusted input.
// It's better than my hand-rolled version would have been.
// populate the topological sorting map
let mut ts = topological_sort::TopologicalSort::<usize>::new();
for (index, package) in v.packages.iter().enumerate() {
for dep in &package.dependencies {
ts.add_dependency(*dep, index);
}
}
// drain all elements that are not part of a cycle
while ts.pop().is_some() {}
// if the set isn't empty, the graph has cycles
!ts.is_empty()
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use crate::*;
fn dummy_package(pkg_counter: u32, root: bool, deps: Vec<usize>) -> Package {
Package {
name: format!("test_{pkg_counter}"),
version: semver::Version::from_str("0.0.0").unwrap(),
source: Source::Local,
kind: DependencyKind::Build,
dependencies: deps,
root: root,
}
}
// these tests are very basic because `topological_sort` crate is already tested extensively
#[test]
fn cyclic_dependencies() {
let pkg0 = dummy_package(0, true, vec![1]);
let pkg1 = dummy_package(1, false, vec![0]);
let raw = RawVersionInfo {
packages: vec![pkg0, pkg1],
format: 0,
};
assert!(VersionInfo::try_from(raw).is_err());
}
#[test]
fn no_cyclic_dependencies() {
let pkg0 = dummy_package(0, true, vec![1]);
let pkg1 = dummy_package(1, false, vec![]);
let raw = RawVersionInfo {
packages: vec![pkg0, pkg1],
format: 0,
};
assert!(VersionInfo::try_from(raw).is_ok());
}
}