use crate::{Error, Package, Raur, SearchBy, AUR_RPC_URL};
use async_trait::async_trait;
use reqwest::Client;
use serde_derive::Deserialize;
#[derive(Deserialize)]
struct Response {
#[serde(rename = "type")]
response_type: String,
error: Option<String>,
results: Vec<Package>,
}
#[derive(Clone, Debug)]
pub struct Handle {
client: Client,
url: String,
}
#[async_trait]
impl Raur for Handle {
type Err = Error;
async fn raw_info<S: AsRef<str> + Send + Sync>(
&self,
pkg_names: &[S],
) -> Result<Vec<Package>, Error> {
let mut params = pkg_names
.iter()
.map(|name| ("arg[]", name.as_ref()))
.collect::<Vec<_>>();
params.extend([("v", "5"), ("type", "info")]);
self.request(¶ms).await
}
async fn search_by<S: AsRef<str> + Send + Sync>(
&self,
query: S,
strategy: SearchBy,
) -> Result<Vec<Package>, Error> {
self.request(&[
("v", "5"),
("type", "search"),
("by", &strategy.to_string()),
("arg", query.as_ref()),
])
.await
}
}
#[cfg(feature = "rusttls-ring")]
fn build_rustls_client() -> reqwest::Client {
use std::sync::Arc;
use rustls::crypto::ring;
let provider = ring::default_provider();
let config = rustls::ClientConfig::builder_with_provider(Arc::new(provider))
.with_safe_default_protocol_versions()
.expect("failed to negotiate protocol versions")
.with_root_certificates(rustls::RootCertStore::empty())
.with_no_client_auth();
reqwest::Client::builder()
.use_preconfigured_tls(config)
.build()
.expect("failed to build rustls client")
}
impl Default for Handle {
#[allow(unreachable_code)]
fn default() -> Self {
#[cfg(any(feature = "rusttls-tls", feature = "native-tls"))]
return Handle {
client: reqwest::Client::new(),
url: AUR_RPC_URL.to_string(),
};
#[cfg(feature = "rusttls-ring")]
return Handle {
client: build_rustls_client(),
url: AUR_RPC_URL.to_string(),
};
#[cfg(not(any(feature = "native-tls", feature = "rusttls-tls", feature = "rusttls-ring")))]
panic!("Either native-tls or rusttls-tls or rusttls-ring feature must be enabled");
}
}
impl Handle {
pub fn new() -> Self {
Handle {
client: Client::new(),
url: AUR_RPC_URL.to_string(),
}
}
pub fn new_with_client(client: Client) -> Self {
Handle {
client,
url: AUR_RPC_URL.to_string(),
}
}
pub fn new_with_url<S: Into<String>>(url: S) -> Self {
Handle {
client: Client::new(),
url: url.into(),
}
}
pub fn new_with_settings<S: Into<String>>(client: Client, url: S) -> Self {
Handle {
client,
url: url.into(),
}
}
pub fn url(&self) -> &str {
&self.url
}
pub fn client(&self) -> &Client {
&self.client
}
async fn request(&self, params: &[(&str, &str)]) -> Result<Vec<Package>, Error> {
let response = self.client.post(&self.url).form(params).send().await?;
response.error_for_status_ref()?;
let response: Response = response.json().await?;
if response.response_type == "error" {
Err(Error::Aur(
response
.error
.unwrap_or_else(|| "No error message provided".to_string()),
))
} else {
Ok(response.results)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_search() {
let handle = Handle::default();
let query = handle.search("zzzzzzz").await.unwrap();
assert_eq!(0, query.len());
let query = handle.search("spotify").await.unwrap();
assert!(!query.is_empty());
}
#[tokio::test]
async fn test_info() {
let handle = Handle::default();
let query = handle.info(&["pacman-git"]).await.unwrap();
assert_eq!(query[0].name, "pacman-git");
let query = handle.info(&["screens", "screens-git"]).await;
assert!(query.is_ok());
let query = query.unwrap();
assert_eq!(2, query.len());
}
}