1use serde::{Deserialize, Serialize};
2use utoipa::ToSchema;
3
4use crate::crate_data::is_outdated;
5use crate::index_metadata::IndexDep;
6
7#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
9pub struct CrateDependents {
10 pub latest_version: Option<String>,
12 pub dependents: Vec<CrateDependent>,
14 pub restricted: u64,
17}
18
19#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
21pub struct CrateDependent {
22 pub name: String,
23 pub version: String,
25 pub requirements: Vec<DependentRequirement>,
28 pub outdated: bool,
30}
31
32#[derive(
33 Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
34)]
35pub struct DependentRequirement {
36 pub version_req: String,
38 pub kind: String,
40 pub outdated: bool,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct LocalDependency {
48 pub name: String,
50 pub version_req: String,
51 pub kind: String,
53}
54
55impl LocalDependency {
56 pub fn from_index_dep(dep: &IndexDep) -> Option<Self> {
58 if dep.registry.is_some() {
59 return None;
60 }
61 Some(LocalDependency {
62 name: dep.package.as_ref().unwrap_or(&dep.name).to_lowercase(),
63 version_req: dep.req.clone(),
64 kind: dep
65 .kind
66 .as_ref()
67 .map_or_else(|| "normal".to_string(), ToString::to_string),
68 })
69 }
70}
71
72impl CrateDependent {
73 pub fn new(
77 name: String,
78 version: String,
79 requirements: impl IntoIterator<Item = (String, String)>,
80 latest_version: Option<&str>,
81 ) -> Self {
82 let mut requirements: Vec<DependentRequirement> = requirements
83 .into_iter()
84 .map(|(version_req, kind)| DependentRequirement {
85 outdated: latest_version.is_some_and(|latest| is_outdated(&version_req, latest)),
86 version_req,
87 kind,
88 })
89 .collect();
90 requirements.sort();
92 requirements.dedup();
93 CrateDependent {
94 name,
95 version,
96 outdated: requirements.iter().any(|r| r.outdated),
97 requirements,
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::index_metadata::DependencyKind;
106
107 fn dep(name: &str, req: &str, kind: Option<DependencyKind>) -> IndexDep {
108 IndexDep {
109 name: name.to_string(),
110 req: req.to_string(),
111 features: vec![],
112 optional: false,
113 default_features: true,
114 target: None,
115 kind,
116 registry: None,
117 package: None,
118 }
119 }
120
121 fn dependent(requirements: &[(&str, &str)], latest: Option<&str>) -> CrateDependent {
122 CrateDependent::new(
123 "bar".to_string(),
124 "1.0.0".to_string(),
125 requirements
126 .iter()
127 .map(|(req, kind)| ((*req).to_string(), (*kind).to_string())),
128 latest,
129 )
130 }
131
132 #[test]
133 fn dependent_on_old_major_is_outdated() {
134 let dependent = dependent(&[("^1", "normal")], Some("2.0.0"));
135 assert_eq!("bar", dependent.name);
136 assert_eq!("1.0.0", dependent.version);
137 assert!(dependent.outdated);
138 assert_eq!(
139 vec![DependentRequirement {
140 version_req: "^1".to_string(),
141 kind: "normal".to_string(),
142 outdated: true,
143 }],
144 dependent.requirements
145 );
146 }
147
148 #[test]
149 fn dependent_on_latest_is_not_outdated() {
150 let dependent = dependent(&[("^2", "normal")], Some("2.0.0"));
151 assert!(!dependent.outdated);
152 }
153
154 #[test]
155 fn dependent_is_outdated_if_any_requirement_is() {
156 let dependent = dependent(
157 &[("^2", "normal"), ("^1", "dev"), ("^1", "dev")],
158 Some("2.0.0"),
159 );
160 assert!(dependent.outdated);
161 assert_eq!(2, dependent.requirements.len());
162 assert_eq!("dev", dependent.requirements[0].kind);
163 assert!(!dependent.requirements[1].outdated);
164 }
165
166 #[test]
167 fn without_latest_version_nothing_is_outdated() {
168 assert!(!dependent(&[("^1", "normal")], None).outdated);
169 }
170
171 #[test]
172 fn local_dependency_uses_normalized_package_name() {
173 let mut renamed = dep("foo_renamed", "^2", Some(DependencyKind::Dev));
174 renamed.package = Some("Foo".to_string());
175 assert_eq!(
176 Some(LocalDependency {
177 name: "foo".to_string(),
178 version_req: "^2".to_string(),
179 kind: "dev".to_string(),
180 }),
181 LocalDependency::from_index_dep(&renamed)
182 );
183 }
184
185 #[test]
186 fn local_dependency_without_kind_is_normal() {
187 let local = LocalDependency::from_index_dep(&dep("foo", "^1", None)).unwrap();
188 assert_eq!("normal", local.kind);
189 }
190
191 #[test]
192 fn dependency_from_other_registry_is_not_local() {
193 let mut cratesio = dep("foo", "^1", None);
194 cratesio.registry = Some("https://github.com/rust-lang/crates.io-index".to_string());
195 assert_eq!(None, LocalDependency::from_index_dep(&cratesio));
196 }
197}