|
5 | 5 | //! `Cargo.toml` file.
|
6 | 6 |
|
7 | 7 | use std::cmp::Reverse;
|
| 8 | +use std::str::FromStr; |
8 | 9 |
|
9 | 10 | use crate::controllers::frontend_prelude::*;
|
10 | 11 | use crate::controllers::helpers::pagination::PaginationOptions;
|
@@ -45,7 +46,7 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult {
|
45 | 46 | .map(|((top_versions, krate), recent_downloads)| {
|
46 | 47 | Ok(EncodableCrate::from_minimal(
|
47 | 48 | krate,
|
48 |
| - &top_versions, |
| 49 | + Some(&top_versions), |
49 | 50 | None,
|
50 | 51 | false,
|
51 | 52 | recent_downloads,
|
@@ -111,71 +112,183 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult {
|
111 | 112 | /// Handles the `GET /crates/:crate_id` route.
|
112 | 113 | pub fn show(req: &mut dyn RequestExt) -> EndpointResult {
|
113 | 114 | let name = &req.params()["crate_id"];
|
| 115 | + let include = req |
| 116 | + .query() |
| 117 | + .get("include") |
| 118 | + .map(|mode| ShowIncludeMode::from_str(mode)) |
| 119 | + .transpose()? |
| 120 | + .unwrap_or_default(); |
| 121 | + |
114 | 122 | let conn = req.db_read_only()?;
|
115 | 123 | let krate: Crate = Crate::by_name(name).first(&*conn)?;
|
116 | 124 |
|
117 |
| - let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate |
118 |
| - .all_versions() |
119 |
| - .left_outer_join(users::table) |
120 |
| - .select((versions::all_columns, users::all_columns.nullable())) |
121 |
| - .load(&*conn)?; |
122 |
| - |
123 |
| - versions_and_publishers |
124 |
| - .sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok())); |
125 |
| - |
126 |
| - let versions = versions_and_publishers |
127 |
| - .iter() |
128 |
| - .map(|(v, _)| v) |
129 |
| - .cloned() |
130 |
| - .collect::<Vec<_>>(); |
131 |
| - let versions_publishers_and_audit_actions = versions_and_publishers |
132 |
| - .into_iter() |
133 |
| - .zip(VersionOwnerAction::for_versions(&conn, &versions)?.into_iter()) |
134 |
| - .map(|((v, pb), aas)| (v, pb, aas)) |
135 |
| - .collect::<Vec<_>>(); |
| 125 | + let versions_publishers_and_audit_actions = if include.versions { |
| 126 | + let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate |
| 127 | + .all_versions() |
| 128 | + .left_outer_join(users::table) |
| 129 | + .select((versions::all_columns, users::all_columns.nullable())) |
| 130 | + .load(&*conn)?; |
| 131 | + versions_and_publishers |
| 132 | + .sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok())); |
| 133 | + |
| 134 | + let versions = versions_and_publishers |
| 135 | + .iter() |
| 136 | + .map(|(v, _)| v) |
| 137 | + .cloned() |
| 138 | + .collect::<Vec<_>>(); |
| 139 | + Some( |
| 140 | + versions_and_publishers |
| 141 | + .into_iter() |
| 142 | + .zip(VersionOwnerAction::for_versions(&conn, &versions)?.into_iter()) |
| 143 | + .map(|((v, pb), aas)| (v, pb, aas)) |
| 144 | + .collect::<Vec<_>>(), |
| 145 | + ) |
| 146 | + } else { |
| 147 | + None |
| 148 | + }; |
136 | 149 | let ids = versions_publishers_and_audit_actions
|
137 |
| - .iter() |
138 |
| - .map(|v| v.0.id) |
139 |
| - .collect(); |
140 |
| - |
141 |
| - let kws = CrateKeyword::belonging_to(&krate) |
142 |
| - .inner_join(keywords::table) |
143 |
| - .select(keywords::all_columns) |
144 |
| - .load(&*conn)?; |
145 |
| - let cats = CrateCategory::belonging_to(&krate) |
146 |
| - .inner_join(categories::table) |
147 |
| - .select(categories::all_columns) |
148 |
| - .load(&*conn)?; |
149 |
| - let recent_downloads = RecentCrateDownloads::belonging_to(&krate) |
150 |
| - .select(recent_crate_downloads::downloads) |
151 |
| - .get_result(&*conn) |
152 |
| - .optional()?; |
| 150 | + .as_ref() |
| 151 | + .map(|vps| vps.iter().map(|v| v.0.id).collect()); |
| 152 | + |
| 153 | + let kws = if include.keywords { |
| 154 | + Some( |
| 155 | + CrateKeyword::belonging_to(&krate) |
| 156 | + .inner_join(keywords::table) |
| 157 | + .select(keywords::all_columns) |
| 158 | + .load(&*conn)?, |
| 159 | + ) |
| 160 | + } else { |
| 161 | + None |
| 162 | + }; |
| 163 | + let cats = if include.categories { |
| 164 | + Some( |
| 165 | + CrateCategory::belonging_to(&krate) |
| 166 | + .inner_join(categories::table) |
| 167 | + .select(categories::all_columns) |
| 168 | + .load(&*conn)?, |
| 169 | + ) |
| 170 | + } else { |
| 171 | + None |
| 172 | + }; |
| 173 | + let recent_downloads = if include.downloads { |
| 174 | + RecentCrateDownloads::belonging_to(&krate) |
| 175 | + .select(recent_crate_downloads::downloads) |
| 176 | + .get_result(&*conn) |
| 177 | + .optional()? |
| 178 | + } else { |
| 179 | + None |
| 180 | + }; |
153 | 181 |
|
154 |
| - let badges = badges::table |
155 |
| - .filter(badges::crate_id.eq(krate.id)) |
156 |
| - .load(&*conn)?; |
157 |
| - let top_versions = krate.top_versions(&conn)?; |
| 182 | + let badges = if include.badges { |
| 183 | + Some( |
| 184 | + badges::table |
| 185 | + .filter(badges::crate_id.eq(krate.id)) |
| 186 | + .load(&*conn)?, |
| 187 | + ) |
| 188 | + } else { |
| 189 | + None |
| 190 | + }; |
| 191 | + let top_versions = if include.versions { |
| 192 | + Some(krate.top_versions(&conn)?) |
| 193 | + } else { |
| 194 | + None |
| 195 | + }; |
158 | 196 |
|
159 |
| - Ok(req.json(&json!({ |
160 |
| - "crate": EncodableCrate::from( |
161 |
| - krate.clone(), |
162 |
| - &top_versions, |
163 |
| - Some(ids), |
164 |
| - Some(&kws), |
165 |
| - Some(&cats), |
166 |
| - Some(badges), |
167 |
| - false, |
168 |
| - recent_downloads, |
169 |
| - ), |
170 |
| - "versions": versions_publishers_and_audit_actions |
171 |
| - .into_iter() |
| 197 | + let encodable_crate = EncodableCrate::from( |
| 198 | + krate.clone(), |
| 199 | + top_versions.as_ref(), |
| 200 | + ids, |
| 201 | + kws.as_deref(), |
| 202 | + cats.as_deref(), |
| 203 | + badges, |
| 204 | + false, |
| 205 | + recent_downloads, |
| 206 | + ); |
| 207 | + let encodable_versions = versions_publishers_and_audit_actions.map(|vpa| { |
| 208 | + vpa.into_iter() |
172 | 209 | .map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas))
|
173 |
| - .collect::<Vec<_>>(), |
174 |
| - "keywords": kws.into_iter().map(Keyword::into).collect::<Vec<EncodableKeyword>>(), |
175 |
| - "categories": cats.into_iter().map(Category::into).collect::<Vec<EncodableCategory>>(), |
| 210 | + .collect::<Vec<_>>() |
| 211 | + }); |
| 212 | + let encodable_keywords = kws.map(|kws| { |
| 213 | + kws.into_iter() |
| 214 | + .map(Keyword::into) |
| 215 | + .collect::<Vec<EncodableKeyword>>() |
| 216 | + }); |
| 217 | + let encodable_cats = cats.map(|cats| { |
| 218 | + cats.into_iter() |
| 219 | + .map(Category::into) |
| 220 | + .collect::<Vec<EncodableCategory>>() |
| 221 | + }); |
| 222 | + Ok(req.json(&json!({ |
| 223 | + "crate": encodable_crate, |
| 224 | + "versions": encodable_versions, |
| 225 | + "keywords": encodable_keywords, |
| 226 | + "categories": encodable_cats, |
176 | 227 | })))
|
177 | 228 | }
|
178 | 229 |
|
| 230 | +#[derive(Debug)] |
| 231 | +struct ShowIncludeMode { |
| 232 | + versions: bool, |
| 233 | + keywords: bool, |
| 234 | + categories: bool, |
| 235 | + badges: bool, |
| 236 | + downloads: bool, |
| 237 | +} |
| 238 | + |
| 239 | +impl Default for ShowIncludeMode { |
| 240 | + fn default() -> Self { |
| 241 | + // Send everything for legacy clients that expect the full response |
| 242 | + Self { |
| 243 | + versions: true, |
| 244 | + keywords: true, |
| 245 | + categories: true, |
| 246 | + badges: true, |
| 247 | + downloads: true, |
| 248 | + } |
| 249 | + } |
| 250 | +} |
| 251 | + |
| 252 | +impl ShowIncludeMode { |
| 253 | + const INVALID_COMPONENT: &'static str = |
| 254 | + "invalid component for ?include= (expected 'versions', 'keywords', 'categories', 'badges', 'downloads', or 'full')"; |
| 255 | +} |
| 256 | + |
| 257 | +impl FromStr for ShowIncludeMode { |
| 258 | + type Err = Box<dyn AppError>; |
| 259 | + |
| 260 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 261 | + let mut mode = Self { |
| 262 | + versions: false, |
| 263 | + keywords: false, |
| 264 | + categories: false, |
| 265 | + badges: false, |
| 266 | + downloads: false, |
| 267 | + }; |
| 268 | + for component in s.split(',') { |
| 269 | + match component { |
| 270 | + "" => {} |
| 271 | + "full" => { |
| 272 | + mode = Self { |
| 273 | + versions: true, |
| 274 | + keywords: true, |
| 275 | + categories: true, |
| 276 | + badges: true, |
| 277 | + downloads: true, |
| 278 | + } |
| 279 | + } |
| 280 | + "versions" => mode.versions = true, |
| 281 | + "keywords" => mode.keywords = true, |
| 282 | + "categories" => mode.categories = true, |
| 283 | + "badges" => mode.badges = true, |
| 284 | + "downloads" => mode.downloads = true, |
| 285 | + _ => return Err(bad_request(Self::INVALID_COMPONENT)), |
| 286 | + } |
| 287 | + } |
| 288 | + Ok(mode) |
| 289 | + } |
| 290 | +} |
| 291 | + |
179 | 292 | /// Handles the `GET /crates/:crate_id/:version/readme` route.
|
180 | 293 | pub fn readme(req: &mut dyn RequestExt) -> EndpointResult {
|
181 | 294 | let crate_name = &req.params()["crate_id"];
|
|
0 commit comments