use crate::error::{Error, ErrorResponse, StatusCode};
use crate::pagination::KBResponse;
use crate::v1::query::{AssessmentsParameters, ContributorsParameters, TopicsParameters};
use reqwest::{header, ClientBuilder, RequestBuilder};
use std::fmt::Display;
pub mod error;
pub mod pagination;
pub mod v1;
const BASE_URL: &str = "https://api.attackerkb.com";
#[derive(Debug, Clone)]
pub struct AttackKBApi {
base_path: String,
version: ApiVersion,
client: reqwest::Client,
}
#[derive(Debug, Clone)]
pub enum ApiVersion {
V1,
}
impl Default for ApiVersion {
fn default() -> Self {
Self::V1
}
}
impl Display for ApiVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ApiVersion::V1 => String::from("v1"),
}
)
}
}
impl AttackKBApi {
pub fn new(api_token: Option<impl Into<String>>) -> Result<Self, Error> {
let mut headers = reqwest::header::HeaderMap::new();
if let Some(api_token) = api_token {
let mut auth_value =
reqwest::header::HeaderValue::from_str(&format!("basic {}", api_token.into()))
.map_err(|source| Error::InvalidApiToken { source })?;
auth_value.set_sensitive(true);
headers.insert(
header::ACCEPT,
header::HeaderValue::from_static("application/json"),
);
headers.insert(header::AUTHORIZATION, auth_value);
}
let api_client = ClientBuilder::new()
.default_headers(headers)
.build()
.map_err(|source| Error::BuildingClient { source })?;
Ok(AttackKBApi {
base_path: BASE_URL.to_owned(),
version: ApiVersion::default(),
client: api_client,
})
}
}
impl AttackKBApi {
async fn request(&self, request: RequestBuilder) -> Result<KBResponse, Error> {
let request = request.build()?;
let resp = self
.client
.execute(request)
.await
.map_err(|source| Error::RequestFailed { source })?;
if !resp.status().is_success() {
return Err(Error::Api {
error: ErrorResponse {
message: "not success request".to_string(),
status: StatusCode::NonZeroU16(resp.status().as_u16()),
},
});
};
let result = resp
.json()
.await
.map_err(|source| Error::ResponseIo { source })?;
if let KBResponse::Error(err) = &result {
return Err(Error::Api { error: err.clone() });
}
Ok(result)
}
}
impl AttackKBApi {
pub async fn topics(&self, query: &TopicsParameters) -> Result<KBResponse, Error> {
let u = format!("{}/{}/{}", self.base_path, self.version, "topics");
self.request(self.client.get(u).query(&query)).await
}
pub async fn topic(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
let u = format!(
"{}/{}/{}/{}",
self.base_path,
self.version,
"topics",
id.into()
);
self.request(self.client.get(u)).await
}
pub async fn assessments(&self, query: &AssessmentsParameters) -> Result<KBResponse, Error> {
let u = format!("{}/{}/{}", self.base_path, self.version, "assessments");
self.request(self.client.get(u).query(&query)).await
}
pub async fn assessment(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
let u = format!(
"{}/{}/{}/{}",
self.base_path,
self.version,
"assessments",
id.into()
);
self.request(self.client.get(u)).await
}
pub async fn contributors(&self, query: &ContributorsParameters) -> Result<KBResponse, Error> {
let u = format!("{}/{}/{}", self.base_path, self.version, "contributors");
self.request(self.client.get(u).query(&query)).await
}
pub async fn contributor(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
let u = format!(
"{}/{}/{}/{}",
self.base_path,
self.version,
"contributors",
id.into()
);
self.request(self.client.get(u)).await
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}