|
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;
|
@@ -111,83 +112,145 @@ 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.full { |
| 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.full { |
| 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.full { |
| 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.full { |
| 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.full { |
| 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.full { |
| 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 |
| - Some(&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 |
|
179 |
| -/// Handles the `GET /crates/:crate_id/crate` route. |
180 |
| -/// |
181 |
| -/// A minimal version of [`show`] that only covers the crate itself, without versions or catalog information |
182 |
| -/// (such as keywords and categories). |
183 |
| -pub fn show_minimal(req: &mut dyn RequestExt) -> EndpointResult { |
184 |
| - let name = &req.params()["crate_id"]; |
185 |
| - let conn = req.db_read_only()?; |
186 |
| - let krate: Crate = Crate::by_name(name).first(&*conn)?; |
| 230 | +#[derive(Debug)] |
| 231 | +struct ShowIncludeMode { |
| 232 | + full: bool, |
| 233 | +} |
187 | 234 |
|
188 |
| - Ok(req.json(&EncodableCrate::from_minimal( |
189 |
| - krate, None, None, false, None, |
190 |
| - ))) |
| 235 | +impl Default for ShowIncludeMode { |
| 236 | + fn default() -> Self { |
| 237 | + // Send everything for legacy clients that expect the full response |
| 238 | + Self { full: true } |
| 239 | + } |
| 240 | +} |
| 241 | + |
| 242 | +impl FromStr for ShowIncludeMode { |
| 243 | + type Err = Box<dyn AppError>; |
| 244 | + |
| 245 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 246 | + match s { |
| 247 | + "" => Ok(ShowIncludeMode { full: false }), |
| 248 | + "full" => Ok(ShowIncludeMode { full: true }), |
| 249 | + _ => Err(bad_request( |
| 250 | + "invalid value for ?mode= (expected '' or 'full')", |
| 251 | + )), |
| 252 | + } |
| 253 | + } |
191 | 254 | }
|
192 | 255 |
|
193 | 256 | /// Handles the `GET /crates/:crate_id/:version/readme` route.
|
|
0 commit comments