pub mod api;
use std::error::Error as StdErr;
use std::sync::Arc;
pub use api::{ApiError, Client, RecordConversionError, TencentError};
use crate::{
CreateRecord, CreateRecordError, CreateZone, CreateZoneError, DeleteRecord, DeleteRecordError,
DeleteZone, DeleteZoneError, HttpClientConfig, Provider, Record, RecordData,
RetrieveRecordError, RetrieveZoneError, Zone,
};
const SUPPORTED_RECORD_TYPES: &[&str] = &["A", "AAAA", "CNAME", "MX", "NS", "TXT", "SRV"];
#[derive(Clone)]
pub struct TencentProvider {
api_client: Arc<Client>,
}
pub struct TencentZone {
api_client: Arc<Client>,
repr: api::DomainListItem,
}
impl TencentZone {
pub fn domain(&self) -> &str {
&self.repr.name
}
}
impl TencentProvider {
pub fn new(secret_id: &str, secret_key: &str) -> Result<Self, Box<dyn StdErr + Send + Sync>> {
let api_client = Client::new(secret_id, secret_key)?;
Ok(Self {
api_client: Arc::new(api_client),
})
}
pub fn with_config(
secret_id: &str,
secret_key: &str,
config: HttpClientConfig,
) -> Result<Self, Box<dyn StdErr + Send + Sync>> {
let api_client = Client::with_config(secret_id, secret_key, config)?;
Ok(Self {
api_client: Arc::new(api_client),
})
}
}
impl Provider for TencentProvider {
type Zone = TencentZone;
type CustomRetrieveError = TencentError;
async fn get_zone(
&self,
zone_id: &str,
) -> Result<Self::Zone, RetrieveZoneError<Self::CustomRetrieveError>> {
let response = if zone_id.chars().all(|c| c.is_ascii_digit()) {
let domain_id: u64 = zone_id.parse().map_err(|_| RetrieveZoneError::NotFound)?;
self.api_client.describe_domain_by_id(domain_id).await
} else {
self.api_client.describe_domain(zone_id).await
};
let domain_info = response.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure" | "AuthFailure.SecretIdNotFound" | "AuthFailure.SignatureFailure" => {
RetrieveZoneError::Unauthorized
}
"InvalidParameterValue.DomainNotExists" | "ResourceNotFound.NoDataOfDomain" => {
RetrieveZoneError::NotFound
}
_ => RetrieveZoneError::Custom(err),
},
_ => RetrieveZoneError::Custom(err),
})?;
Ok(TencentZone {
api_client: self.api_client.clone(),
repr: api::DomainListItem {
domain_id: domain_info.domain_info.domain_id,
name: domain_info.domain_info.domain,
status: domain_info.domain_info.status,
ttl: domain_info.domain_info.ttl,
record_count: domain_info.domain_info.record_count,
},
})
}
async fn list_zones(
&self,
) -> Result<Vec<Self::Zone>, RetrieveZoneError<Self::CustomRetrieveError>> {
let mut zones = Vec::new();
let mut offset: u32 = 0;
const LIMIT: u32 = 500;
loop {
let response = self
.api_client
.describe_domain_list(Some(offset), Some(LIMIT))
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => RetrieveZoneError::Unauthorized,
_ => RetrieveZoneError::Custom(err),
},
_ => RetrieveZoneError::Custom(err),
})?;
let domains = response.domain_list.unwrap_or_default();
let domain_count = domains.len();
zones.extend(domains.into_iter().map(|domain| TencentZone {
api_client: self.api_client.clone(),
repr: domain,
}));
if domain_count < LIMIT as usize {
break;
}
offset += LIMIT;
}
Ok(zones)
}
}
impl CreateZone for TencentProvider {
type CustomCreateError = TencentError;
async fn create_zone(
&self,
domain: &str,
) -> Result<Self::Zone, CreateZoneError<Self::CustomCreateError>> {
let response = self
.api_client
.create_domain(domain)
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => CreateZoneError::Unauthorized,
"InvalidParameter.DomainInvalid" => CreateZoneError::InvalidDomainName,
_ => CreateZoneError::Custom(err),
},
_ => CreateZoneError::Custom(err),
})?;
let domain_info = self
.api_client
.describe_domain_by_id(response.domain_info.id)
.await
.map_err(CreateZoneError::Custom)?;
Ok(TencentZone {
api_client: self.api_client.clone(),
repr: api::DomainListItem {
domain_id: domain_info.domain_info.domain_id,
name: domain_info.domain_info.domain,
status: domain_info.domain_info.status,
ttl: domain_info.domain_info.ttl,
record_count: domain_info.domain_info.record_count,
},
})
}
}
impl DeleteZone for TencentProvider {
type CustomDeleteError = TencentError;
async fn delete_zone(
&self,
zone_id: &str,
) -> Result<(), DeleteZoneError<Self::CustomDeleteError>> {
self.api_client
.delete_domain(zone_id)
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => DeleteZoneError::Unauthorized,
"InvalidParameterValue.DomainNotExists" | "ResourceNotFound.NoDataOfDomain" => {
DeleteZoneError::NotFound
}
_ => DeleteZoneError::Custom(err),
},
_ => DeleteZoneError::Custom(err),
})?;
Ok(())
}
}
impl Zone for TencentZone {
type CustomRetrieveError = TencentError;
fn id(&self) -> &str {
&self.repr.name
}
fn domain(&self) -> &str {
&self.repr.name
}
async fn list_records(
&self,
) -> Result<Vec<Record>, RetrieveRecordError<Self::CustomRetrieveError>> {
let mut records = Vec::new();
let mut offset: u32 = 0;
const LIMIT: u32 = 500;
loop {
let response = self
.api_client
.describe_record_list(&self.repr.name, Some(offset), Some(LIMIT))
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => RetrieveRecordError::Unauthorized,
_ => RetrieveRecordError::Custom(err),
},
_ => RetrieveRecordError::Custom(err),
})?;
let record_list = response.record_list.unwrap_or_default();
let record_count = record_list.len();
for record in record_list {
if let Ok(r) = Record::try_from(record) {
records.push(r);
}
}
if record_count < LIMIT as usize {
break;
}
offset += LIMIT;
}
Ok(records)
}
async fn get_record(
&self,
record_id: &str,
) -> Result<Record, RetrieveRecordError<Self::CustomRetrieveError>> {
let record_id_num: u64 = record_id
.parse()
.map_err(|_| RetrieveRecordError::NotFound)?;
let response = self
.api_client
.describe_record(&self.repr.name, record_id_num)
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => RetrieveRecordError::Unauthorized,
"InvalidParameter.RecordIdInvalid" | "ResourceNotFound.NoDataOfRecord" => {
RetrieveRecordError::NotFound
}
_ => RetrieveRecordError::Custom(err),
},
_ => RetrieveRecordError::Custom(err),
})?;
Record::try_from(response.record_info).map_err(|_| RetrieveRecordError::NotFound)
}
}
impl CreateRecord for TencentZone {
type CustomCreateError = TencentError;
async fn create_record(
&self,
host: &str,
data: &RecordData,
ttl: u64,
) -> Result<Record, CreateRecordError<Self::CustomCreateError>> {
let typ = data.get_type();
if !SUPPORTED_RECORD_TYPES.contains(&typ) {
return Err(CreateRecordError::UnsupportedType);
}
let mx = match data {
RecordData::MX { priority, .. } => Some(*priority),
_ => None,
};
let value = data.get_api_value();
let response = self
.api_client
.create_record(
&self.repr.name,
host,
typ,
"默认", &value,
mx,
Some(ttl),
)
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => CreateRecordError::Unauthorized,
"InvalidParameter.RecordTypeInvalid" => CreateRecordError::UnsupportedType,
"InvalidParameter.SubDomainInvalid"
| "InvalidParameter.RecordValueInvalid"
| "InvalidParameter.MXInvalid" => CreateRecordError::InvalidRecord,
_ => CreateRecordError::Custom(err),
},
_ => CreateRecordError::Custom(err),
})?;
Ok(Record {
id: response.record_id.to_string(),
host: host.to_string(),
data: data.clone(),
ttl,
})
}
}
impl DeleteRecord for TencentZone {
type CustomDeleteError = TencentError;
async fn delete_record(
&self,
record_id: &str,
) -> Result<(), DeleteRecordError<Self::CustomDeleteError>> {
let record_id_num: u64 = record_id.parse().map_err(|_| DeleteRecordError::NotFound)?;
self.api_client
.delete_record(&self.repr.name, record_id_num)
.await
.map_err(|err| match &err {
TencentError::Api(api_err) => match api_err.code.as_str() {
"AuthFailure"
| "AuthFailure.SecretIdNotFound"
| "AuthFailure.SignatureFailure" => DeleteRecordError::Unauthorized,
"InvalidParameter.RecordIdInvalid" | "ResourceNotFound.NoDataOfRecord" => {
DeleteRecordError::NotFound
}
_ => DeleteRecordError::Custom(err),
},
_ => DeleteRecordError::Custom(err),
})?;
Ok(())
}
}