use serde::de::DeserializeOwned;
use crate::domains::Domains;
use crate::error::{Error, Result};
use crate::response;
use crate::users::Users;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Environment {
#[default]
Production,
Sandbox,
}
impl Environment {
fn endpoint(self) -> &'static str {
match self {
Environment::Production => "https://api.namecheap.com/xml.response",
Environment::Sandbox => "https://api.sandbox.namecheap.com/xml.response",
}
}
}
#[derive(Debug, Clone)]
pub struct Client {
http: reqwest::Client,
api_user: String,
api_key: String,
user_name: String,
client_ip: String,
environment: Environment,
}
impl Client {
#[must_use]
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
pub fn new(
api_user: impl Into<String>,
api_key: impl Into<String>,
client_ip: impl Into<String>,
) -> Result<Self> {
Self::builder()
.api_user(api_user)
.api_key(api_key)
.client_ip(client_ip)
.build()
}
#[must_use]
pub fn domains(&self) -> Domains<'_> {
Domains::new(self)
}
#[must_use]
pub fn users(&self) -> Users<'_> {
Users::new(self)
}
#[must_use]
pub fn environment(&self) -> Environment {
self.environment
}
pub(crate) async fn send<T>(&self, command: &str, params: Vec<(String, String)>) -> Result<T>
where
T: DeserializeOwned,
{
let mut query: Vec<(String, String)> = Vec::with_capacity(params.len() + 5);
query.push(("ApiUser".to_owned(), self.api_user.clone()));
query.push(("ApiKey".to_owned(), self.api_key.clone()));
query.push(("UserName".to_owned(), self.user_name.clone()));
query.push(("ClientIp".to_owned(), self.client_ip.clone()));
query.push(("Command".to_owned(), command.to_owned()));
query.extend(params);
let response = self
.http
.get(self.environment.endpoint())
.query(&query)
.send()
.await?;
let status = response.status();
let bytes = response.bytes().await?;
let body = String::from_utf8_lossy(&bytes);
response::parse(status, &body)
}
}
#[derive(Debug, Default, Clone)]
pub struct ClientBuilder {
api_user: Option<String>,
api_key: Option<String>,
user_name: Option<String>,
client_ip: Option<String>,
environment: Environment,
http: Option<reqwest::Client>,
}
impl ClientBuilder {
#[must_use]
pub fn api_user(mut self, value: impl Into<String>) -> Self {
self.api_user = Some(value.into());
self
}
#[must_use]
pub fn api_key(mut self, value: impl Into<String>) -> Self {
self.api_key = Some(value.into());
self
}
#[must_use]
pub fn user_name(mut self, value: impl Into<String>) -> Self {
self.user_name = Some(value.into());
self
}
#[must_use]
pub fn client_ip(mut self, value: impl Into<String>) -> Self {
self.client_ip = Some(value.into());
self
}
#[must_use]
pub fn environment(mut self, environment: Environment) -> Self {
self.environment = environment;
self
}
#[must_use]
pub fn http_client(mut self, client: reqwest::Client) -> Self {
self.http = Some(client);
self
}
pub fn build(self) -> Result<Client> {
let api_user = self
.api_user
.ok_or_else(|| Error::Configuration("`api_user` is required".to_owned()))?;
let api_key = self
.api_key
.ok_or_else(|| Error::Configuration("`api_key` is required".to_owned()))?;
let client_ip = self
.client_ip
.ok_or_else(|| Error::Configuration("`client_ip` is required".to_owned()))?;
let user_name = self.user_name.unwrap_or_else(|| api_user.clone());
let http = match self.http {
Some(client) => client,
None => reqwest::Client::builder()
.user_agent(concat!("namecheap-client/", env!("CARGO_PKG_VERSION")))
.build()
.map_err(|error| Error::Configuration(error.to_string()))?,
};
Ok(Client {
http,
api_user,
api_key,
user_name,
client_ip,
environment: self.environment,
})
}
}