use sqlx::error::BoxDynError;
use sqlx::{Error, PgPool, Postgres};
pub type BugId = i32;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub enum BugKind {
RFP,
ITP,
}
impl std::str::FromStr for BugKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"RFP" => Ok(BugKind::RFP),
"ITP" => Ok(BugKind::ITP),
_ => Err(format!("Unknown bug kind: {}", s)),
}
}
}
impl std::fmt::Display for BugKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
BugKind::RFP => write!(f, "RFP"),
BugKind::ITP => write!(f, "ITP"),
}
}
}
impl sqlx::Type<Postgres> for BugKind {
fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<Postgres>>::type_info()
}
}
impl sqlx::Decode<'_, Postgres> for BugKind {
fn decode(value: <Postgres as sqlx::Database>::ValueRef<'_>) -> Result<Self, BoxDynError> {
let s = <String as sqlx::Decode<Postgres>>::decode(value)?;
s.parse().map_err(Into::into)
}
}
pub struct DebBugs {
pool: PgPool,
}
impl DebBugs {
pub fn new(pool: PgPool) -> Self {
DebBugs { pool }
}
pub async fn default() -> Result<Self, Error> {
Ok(DebBugs {
pool: crate::udd::connect_udd_mirror().await?,
})
}
pub async fn check_bug(&self, package: &str, bugid: BugId) -> Result<bool, Error> {
let actual_package: Option<String> =
sqlx::query_scalar("select package from bugs where id = $1")
.bind(bugid)
.fetch_optional(&self.pool)
.await?;
Ok(actual_package.as_deref() == Some(package))
}
pub async fn find_archived_wnpp_bugs(
&self,
source_name: &str,
) -> Result<Vec<(BugId, BugKind)>, Error> {
sqlx::query_as::<_, (BugId, BugKind)>(
"select id, substring(title, 1, 3) from archived_bugs where package = 'wnpp' and (
title like 'ITP: ' || $1 || ' -- %' OR
title like 'RFP: ' || $1 || ' -- %')",
)
.bind(source_name)
.fetch_all(&self.pool)
.await
}
pub async fn find_wnpp_bugs(&self, source_name: &str) -> Result<Vec<(BugId, BugKind)>, Error> {
sqlx::query_as::<_, (BugId, BugKind)>(
"select id, type from wnpp where source = $1 and type in ('ITP', 'RFP')",
)
.bind(source_name)
.fetch_all(&self.pool)
.await
}
}
pub async fn find_wnpp_bugs_harder(names: &[&str]) -> Result<Vec<(BugId, BugKind)>, Error> {
for name in names {
let debbugs = DebBugs::default().await?;
let mut wnpp_bugs = debbugs.find_wnpp_bugs(name).await?;
if wnpp_bugs.is_empty() {
wnpp_bugs = debbugs.find_archived_wnpp_bugs(name).await?;
if !wnpp_bugs.is_empty() {
log::warn!("Found archived ITP/RFP bugs for {}: {:?}", name, wnpp_bugs);
} else {
log::warn!("No relevant WNPP bugs found for {}", name);
}
}
if !wnpp_bugs.is_empty() {
log::info!("Found WNPP bugs for {}: {:?}", name, wnpp_bugs);
return Ok(wnpp_bugs);
}
}
Ok(vec![])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bug_id_is_i32() {
let _: BugId = 123456i32;
assert_eq!(std::mem::size_of::<BugId>(), 4);
}
#[test]
fn test_bug_kind_parsing() {
assert_eq!("RFP".parse::<BugKind>().unwrap(), BugKind::RFP);
assert_eq!("ITP".parse::<BugKind>().unwrap(), BugKind::ITP);
assert!("INVALID".parse::<BugKind>().is_err());
}
#[test]
fn test_bug_kind_display() {
assert_eq!(BugKind::RFP.to_string(), "RFP");
assert_eq!(BugKind::ITP.to_string(), "ITP");
}
}