|
| 1 | +use anyhow::Result; |
| 2 | +use chrono::{DateTime, Utc}; |
| 3 | +use futures_util::TryStreamExt as _; |
| 4 | +use serde_json::Value; |
| 5 | +use docs_rs_types::{BuildStatus, CrateId, ReleaseId, Version}; |
| 6 | + |
| 7 | +#[derive(Debug, Clone, Eq, PartialEq)] |
| 8 | +pub struct Release { |
| 9 | + pub id: ReleaseId, |
| 10 | + pub version: Version, |
| 11 | + #[allow(clippy::doc_overindented_list_items)] |
| 12 | + /// Aggregated build status of the release. |
| 13 | + /// * no builds -> build In progress |
| 14 | + /// * any build is successful -> Success |
| 15 | + /// -> even with failed or in-progress builds we have docs to show |
| 16 | + /// * any build is failed -> Failure |
| 17 | + /// -> we can only have Failure or InProgress here, so the Failure is the |
| 18 | + /// important part on this aggregation level. |
| 19 | + /// * the rest is all builds are in-progress -> InProgress |
| 20 | + /// -> if we have any builds, and the previous conditions don't match, we end |
| 21 | + /// up here, but we still check. |
| 22 | + /// |
| 23 | + /// calculated in a database view : `release_build_status` |
| 24 | + pub build_status: BuildStatus, |
| 25 | + pub yanked: Option<bool>, |
| 26 | + pub is_library: Option<bool>, |
| 27 | + pub rustdoc_status: Option<bool>, |
| 28 | + pub target_name: Option<String>, |
| 29 | + pub default_target: Option<String>, |
| 30 | + pub doc_targets: Option<Vec<String>>, |
| 31 | + pub release_time: Option<DateTime<Utc>>, |
| 32 | +} |
| 33 | + |
| 34 | +pub fn parse_doc_targets(targets: Value) -> Vec<String> { |
| 35 | + let mut targets: Vec<String> = serde_json::from_value(targets).unwrap_or_default(); |
| 36 | + targets.sort_unstable(); |
| 37 | + targets |
| 38 | +} |
| 39 | + |
| 40 | +/// Return all releases for a crate, sorted in descending order by semver |
| 41 | +pub async fn releases_for_crate( |
| 42 | + conn: &mut sqlx::PgConnection, |
| 43 | + crate_id: CrateId, |
| 44 | +) -> Result<Vec<Release>, anyhow::Error> { |
| 45 | + let mut releases: Vec<Release> = sqlx::query!( |
| 46 | + r#"SELECT |
| 47 | + releases.id as "id: ReleaseId", |
| 48 | + releases.version as "version: Version", |
| 49 | + release_build_status.build_status as "build_status!: BuildStatus", |
| 50 | + releases.yanked, |
| 51 | + releases.is_library, |
| 52 | + releases.rustdoc_status, |
| 53 | + releases.release_time, |
| 54 | + releases.target_name, |
| 55 | + releases.default_target, |
| 56 | + releases.doc_targets |
| 57 | + FROM releases |
| 58 | + INNER JOIN release_build_status ON releases.id = release_build_status.rid |
| 59 | + WHERE |
| 60 | + releases.crate_id = $1"#, |
| 61 | + crate_id.0, |
| 62 | + ) |
| 63 | + .fetch(&mut *conn) |
| 64 | + .try_filter_map(|row| async move { |
| 65 | + Ok(Some(Release { |
| 66 | + id: row.id, |
| 67 | + version: row.version, |
| 68 | + build_status: row.build_status, |
| 69 | + yanked: row.yanked, |
| 70 | + is_library: row.is_library, |
| 71 | + rustdoc_status: row.rustdoc_status, |
| 72 | + target_name: row.target_name, |
| 73 | + default_target: row.default_target, |
| 74 | + doc_targets: row.doc_targets.map(parse_doc_targets), |
| 75 | + release_time: row.release_time, |
| 76 | + })) |
| 77 | + }) |
| 78 | + .try_collect() |
| 79 | + .await?; |
| 80 | + |
| 81 | + releases.sort_by(|a, b| b.version.cmp(&a.version)); |
| 82 | + Ok(releases) |
| 83 | +} |
| 84 | + |
| 85 | +pub fn latest_release(releases: &[Release]) -> Option<&Release> { |
| 86 | + if let Some(release) = releases.iter().find(|release| { |
| 87 | + release.version.pre.is_empty() |
| 88 | + && release.yanked == Some(false) |
| 89 | + && release.build_status != BuildStatus::InProgress |
| 90 | + }) { |
| 91 | + Some(release) |
| 92 | + } else { |
| 93 | + releases |
| 94 | + .iter() |
| 95 | + .find(|release| release.build_status != BuildStatus::InProgress) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +pub async fn update_latest_version_id( |
| 100 | + conn: &mut sqlx::PgConnection, |
| 101 | + crate_id: CrateId, |
| 102 | +) -> Result<()> { |
| 103 | + let releases = releases_for_crate(conn, crate_id).await?; |
| 104 | + |
| 105 | + sqlx::query!( |
| 106 | + "UPDATE crates |
| 107 | + SET latest_version_id = $2 |
| 108 | + WHERE id = $1", |
| 109 | + crate_id.0, |
| 110 | + latest_release(&releases).map(|release| release.id.0), |
| 111 | + ) |
| 112 | + .execute(&mut *conn) |
| 113 | + .await?; |
| 114 | + |
| 115 | + Ok(()) |
| 116 | +} |
0 commit comments