use crate::bug_model::{Bug, Response};
use crate::errors::BugzillaQueryError;
pub struct BzInstance {
pub host: String,
pub auth: Auth,
pub pagination: Pagination,
pub included_fields: Vec<String>,
client: reqwest::Client,
}
pub enum Auth {
Anonymous,
ApiKey(String),
Basic { user: String, password: String },
}
impl Default for Auth {
fn default() -> Self {
Self::Anonymous
}
}
pub enum Pagination {
Default,
Limit(u32),
Unlimited,
}
impl Default for Pagination {
fn default() -> Self {
Self::Default
}
}
impl Pagination {
fn url_fragment(&self) -> String {
match self {
Pagination::Default => String::new(),
Pagination::Limit(n) => format!("&limit={n}"),
Pagination::Unlimited => "&limit=0".to_string(),
}
}
}
enum Method<'a> {
Ids(&'a [&'a str]),
Search(&'a str),
}
impl<'a> Method<'a> {
fn url_fragment(&self) -> String {
match self {
Self::Ids(ids) => format!("id={}", ids.join(",")),
Self::Search(query) => (*query).to_string(),
}
}
}
impl BzInstance {
pub fn at(host: String) -> Result<Self, BugzillaQueryError> {
let client = reqwest::Client::new();
Ok(BzInstance {
host,
client,
included_fields: vec!["_default".to_string()],
auth: Auth::default(),
pagination: Pagination::default(),
})
}
#[must_use]
pub fn authenticate(mut self, auth: Auth) -> Self {
self.auth = auth;
self
}
#[must_use]
pub fn paginate(mut self, pagination: Pagination) -> Self {
self.pagination = pagination;
self
}
#[must_use]
pub fn include_fields(mut self, fields: Vec<String>) -> Self {
self.included_fields = fields;
self
}
#[must_use]
fn fields_as_query(&self) -> String {
if self.included_fields.is_empty() {
String::new()
} else {
format!("&include_fields={}", self.included_fields.join(","))
}
}
#[must_use]
fn path(&self, method: &Method) -> String {
format!(
"{}/rest/bug?{}{}{}",
&self.host,
method.url_fragment(),
self.fields_as_query(),
self.pagination.url_fragment()
)
}
async fn authenticated_get(&self, url: &str) -> Result<reqwest::Response, reqwest::Error> {
let request_builder = self.client.get(url);
let authenticated = match &self.auth {
Auth::Anonymous => request_builder,
Auth::ApiKey(key) => request_builder.header("Authorization", &format!("Bearer {key}")),
Auth::Basic { user, password } => request_builder.basic_auth(user, Some(password)),
};
authenticated.send().await
}
pub async fn bugs(&self, ids: &[&str]) -> Result<Vec<Bug>, BugzillaQueryError> {
if ids.is_empty() {
return Ok(Vec::new());
}
let url = self.path(&Method::Ids(ids));
let response = self
.authenticated_get(&url)
.await?
.json::<Response>()
.await?;
log::debug!("{:#?}", response);
if response.bugs.is_empty() {
Err(BugzillaQueryError::NoBugs)
} else {
Ok(response.bugs)
}
}
pub async fn bug(&self, id: &str) -> Result<Bug, BugzillaQueryError> {
let bugs = self.bugs(&[id]).await?;
bugs.into_iter().next().ok_or(BugzillaQueryError::NoBugs)
}
pub async fn search(&self, query: &str) -> Result<Vec<Bug>, BugzillaQueryError> {
let url = self.path(&Method::Search(query));
let response = self
.authenticated_get(&url)
.await?
.json::<Response>()
.await?;
log::debug!("{:#?}", response);
if response.bugs.is_empty() {
Err(BugzillaQueryError::NoBugs)
} else {
Ok(response.bugs)
}
}
}