1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6use crate::index_metadata::IndexDep;
7use crate::publish_metadata::RegistryDep;
8
9pub const CRATESIO_INDEX: &str = "https://github.com/rust-lang/crates.io-index";
11
12#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
13pub struct CrateData {
14 pub name: String,
15 pub owners: Vec<String>,
17 pub max_version: String,
18 pub total_downloads: i64,
19 pub last_updated: String,
20 pub homepage: Option<String>,
22 pub description: Option<String>,
23 pub repository: Option<String>,
24 pub categories: Vec<String>,
25 pub keywords: Vec<String>,
26 pub authors: Vec<String>,
27 pub versions: Vec<CrateVersionData>,
28}
29
30#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
31pub struct CrateVersionData {
32 pub version: String,
33 pub created: String,
35 pub downloads: i64,
36 pub readme: Option<String>,
38 pub license: Option<String>,
39 pub license_file: Option<String>,
40 pub documentation: Option<String>,
41 pub dependencies: Vec<CrateRegistryDep>,
42 pub checksum: String,
43 pub features: BTreeMap<String, Vec<String>>,
44 pub yanked: bool,
45 pub links: Option<String>,
46 pub v: i32,
47 pub size: i64,
49 pub rust_version: Option<String>,
51 pub publisher: Option<String>,
53 pub publisher_token: Option<String>,
55}
56
57#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
58pub struct CrateRegistryDep {
59 pub name: String,
64 pub description: Option<String>,
65 pub version_req: String,
67 pub features: Option<Vec<String>>,
69 pub optional: bool,
71 pub default_features: bool,
73 pub target: Option<String>,
77 pub kind: Option<String>,
80 pub registry: Option<String>,
84 pub explicit_name_in_toml: Option<String>,
88 pub latest_version: Option<String>,
91 pub outdated: bool,
93}
94
95impl From<RegistryDep> for CrateRegistryDep {
96 fn from(dep: RegistryDep) -> Self {
97 CrateRegistryDep {
98 name: dep.name,
99 description: None,
100 version_req: dep.version_req,
101 features: dep.features,
102 optional: dep.optional,
103 default_features: dep.default_features,
104 target: dep.target,
105 kind: dep.kind,
106 registry: dep.registry,
107 explicit_name_in_toml: dep.explicit_name_in_toml,
108 latest_version: None,
109 outdated: false,
110 }
111 }
112}
113
114impl CrateRegistryDep {
115 pub fn from_index(desc: Option<String>, latest_version: Option<String>, dep: IndexDep) -> Self {
116 let outdated = latest_version
117 .as_deref()
118 .is_some_and(|latest| is_outdated(&dep.req, latest));
119 CrateRegistryDep {
120 name: match dep.package {
121 Some(ref package) => package.clone(),
122 None => dep.name.clone(),
123 },
124 description: desc,
125 version_req: dep.req,
126 features: Some(dep.features),
127 optional: dep.optional,
128 default_features: dep.default_features,
129 target: dep.target,
130 kind: dep.kind.map(|k| k.to_string()),
131 registry: dep.registry,
132 explicit_name_in_toml: match dep.package {
133 Some(_) => Some(dep.name),
134 None => None,
135 },
136 latest_version,
137 outdated,
138 }
139 }
140}
141
142pub fn is_outdated(req: &str, latest: &str) -> bool {
146 let (Ok(req), Ok(latest)) = (
147 semver::VersionReq::parse(req),
148 semver::Version::parse(latest),
149 ) else {
150 return false;
151 };
152 if req.matches(&latest) {
153 return false;
154 }
155 let below_lower_bound = req.comparators.iter().any(|c| {
158 let bound = semver::Version::new(c.major, c.minor.unwrap_or(0), c.patch.unwrap_or(0));
159 match c.op {
160 semver::Op::Less | semver::Op::LessEq => false,
161 semver::Op::Greater => latest <= bound,
162 _ => latest < bound,
163 }
164 });
165 !below_lower_bound
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn latest_version_outside_the_requirement_is_outdated() {
174 assert!(is_outdated("^2.0", "3.1.0"));
175 assert!(is_outdated("2.0", "3.1.0"));
176 assert!(is_outdated("~1.2", "1.3.0"));
177 assert!(is_outdated("=1.0.0", "1.0.1"));
178 assert!(is_outdated(">=1, <2", "2.0.0"));
179 assert!(is_outdated("^0.3", "0.4.0"));
180 assert!(is_outdated("<2", "3.0.0"));
181 }
182
183 #[test]
184 fn latest_version_inside_the_requirement_is_not_outdated() {
185 assert!(!is_outdated("^2.0", "2.4.0"));
186 assert!(!is_outdated("*", "9.0.0"));
187 assert!(!is_outdated(">=1", "3.0.0"));
188 }
189
190 #[test]
191 fn requirement_newer_than_the_latest_version_is_not_outdated() {
192 assert!(!is_outdated("^5", "4.0.0"));
193 assert!(!is_outdated(">=2.1", "2.0.0"));
194 assert!(!is_outdated(">2.0.0", "2.0.0"));
195 assert!(!is_outdated("=2.0.0", "1.0.0"));
196 }
197
198 #[test]
199 fn unparsable_versions_are_not_outdated() {
200 assert!(!is_outdated("not a req", "1.0.0"));
201 assert!(!is_outdated("^1", "latest"));
202 }
203}