pub mod api;
use std::error::Error as StdErr;
use std::sync::Arc;
pub use api::{ApiError, Client, CloudflareError, DnsRecordWithZone, RecordConversionError};
use crate::{
CreateRecord, CreateRecordError, DeleteRecord, DeleteRecordError, Provider, Record, RecordData,
RetrieveRecordError, RetrieveZoneError, Zone,
};
#[derive(Clone)]
pub struct CloudflareProvider {
api_client: Arc<Client>,
}
pub struct CloudflareZone {
api_client: Arc<Client>,
repr: api::Zone,
}
impl CloudflareZone {
pub fn domain(&self) -> &str {
&self.repr.name
}
}
impl CloudflareProvider {
pub fn new(api_token: &str) -> Result<Self, Box<dyn StdErr + Send + Sync>> {
let api_client = Client::new(api_token)?;
Ok(Self {
api_client: Arc::new(api_client),
})
}
pub fn with_base_url(
api_token: &str,
base_url: &str,
) -> Result<Self, Box<dyn StdErr + Send + Sync>> {
let api_client = Client::with_base_url(api_token, base_url)?;
Ok(Self {
api_client: Arc::new(api_client),
})
}
}
impl Provider for CloudflareProvider {
type Zone = CloudflareZone;
type CustomRetrieveError = CloudflareError;
async fn get_zone(
&self,
zone_id: &str,
) -> Result<Self::Zone, RetrieveZoneError<Self::CustomRetrieveError>> {
let zone = if zone_id.len() == 32 && zone_id.chars().all(|c| c.is_ascii_hexdigit()) {
self.api_client.get_zone(zone_id).await
} else {
self.api_client.get_zone_by_name(zone_id).await
};
let zone = zone.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
9109 | 7003 | 1003 => RetrieveZoneError::NotFound,
9106 | 10000 => RetrieveZoneError::Unauthorized,
_ => RetrieveZoneError::Custom(err),
},
_ => RetrieveZoneError::Custom(err),
})?;
Ok(CloudflareZone {
api_client: self.api_client.clone(),
repr: zone,
})
}
async fn list_zones(
&self,
) -> Result<Vec<Self::Zone>, RetrieveZoneError<Self::CustomRetrieveError>> {
let zones = self
.api_client
.list_zones()
.await
.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
9106 | 10000 => RetrieveZoneError::Unauthorized,
_ => RetrieveZoneError::Custom(err),
},
_ => RetrieveZoneError::Custom(err),
})?;
Ok(zones
.into_iter()
.map(|zone| CloudflareZone {
api_client: self.api_client.clone(),
repr: zone,
})
.collect())
}
}
impl Zone for CloudflareZone {
type CustomRetrieveError = CloudflareError;
fn id(&self) -> &str {
&self.repr.id
}
fn domain(&self) -> &str {
&self.repr.name
}
async fn list_records(
&self,
) -> Result<Vec<Record>, RetrieveRecordError<Self::CustomRetrieveError>> {
let records =
self.api_client
.list_records(&self.repr.id)
.await
.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
9106 | 10000 => RetrieveRecordError::Unauthorized,
_ => RetrieveRecordError::Custom(err),
},
_ => RetrieveRecordError::Custom(err),
})?;
Ok(records
.into_iter()
.filter_map(|r| {
crate::Record::try_from(api::DnsRecordWithZone::new(&r, &self.repr.name)).ok()
})
.collect())
}
async fn get_record(
&self,
record_id: &str,
) -> Result<Record, RetrieveRecordError<Self::CustomRetrieveError>> {
let record = self
.api_client
.get_record(&self.repr.id, record_id)
.await
.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
81044 => RetrieveRecordError::NotFound,
9106 | 10000 => RetrieveRecordError::Unauthorized,
_ => RetrieveRecordError::Custom(err),
},
_ => RetrieveRecordError::Custom(err),
})?;
crate::Record::try_from(api::DnsRecordWithZone::new(&record, &self.repr.name)).map_err(
|e| {
RetrieveRecordError::Custom(CloudflareError::Api(ApiError {
code: 0,
message: format!("Failed to convert record: {}", e),
}))
},
)
}
}
impl CreateRecord for CloudflareZone {
type CustomCreateError = CloudflareError;
async fn create_record(
&self,
host: &str,
data: &RecordData,
ttl: u64,
) -> Result<Record, CreateRecordError<Self::CustomCreateError>> {
let request = api::CreateRecordRequest::from_record_data(host, data, ttl, &self.repr.name)
.map_err(|_| CreateRecordError::UnsupportedType)?;
let record = self
.api_client
.create_record(&self.repr.id, &request)
.await
.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
9106 | 10000 => CreateRecordError::Unauthorized,
81057 => CreateRecordError::InvalidRecord,
_ => CreateRecordError::Custom(err),
},
_ => CreateRecordError::Custom(err),
})?;
crate::Record::try_from(api::DnsRecordWithZone::new(&record, &self.repr.name)).map_err(
|e| {
CreateRecordError::Custom(CloudflareError::Api(ApiError {
code: 0,
message: format!("Failed to convert record: {}", e),
}))
},
)
}
}
impl DeleteRecord for CloudflareZone {
type CustomDeleteError = CloudflareError;
async fn delete_record(
&self,
record_id: &str,
) -> Result<(), DeleteRecordError<Self::CustomDeleteError>> {
self.api_client
.delete_record(&self.repr.id, record_id)
.await
.map_err(|err| match &err {
CloudflareError::Api(api_err) => match api_err.code {
81044 => DeleteRecordError::NotFound,
9106 | 10000 => DeleteRecordError::Unauthorized,
_ => DeleteRecordError::Custom(err),
},
_ => DeleteRecordError::Custom(err),
})?;
Ok(())
}
}