use serde::{Deserialize, Serialize};
use crate::client::Sendly;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Deserialize)]
pub struct NumberCountry {
pub code: String,
pub name: String,
#[serde(default, alias = "numberTypes")]
pub number_types: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NumberCountriesResponse {
#[serde(default)]
pub countries: Vec<NumberCountry>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AvailableNumber {
#[serde(alias = "phoneNumber")]
pub phone_number: String,
pub country: String,
#[serde(alias = "numberType")]
pub number_type: String,
#[serde(alias = "monthlyCost")]
pub monthly_cost: String,
pub currency: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AvailableNumbersResponse {
#[serde(default)]
pub numbers: Vec<AvailableNumber>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OwnedNumber {
pub id: String,
#[serde(alias = "phoneNumber")]
pub phone_number: String,
pub status: String,
#[serde(default)]
pub source: Option<String>,
#[serde(default, alias = "countryCode")]
pub country_code: Option<String>,
#[serde(default, alias = "phoneNumberType")]
pub phone_number_type: Option<String>,
#[serde(default, alias = "monthlyCostCents")]
pub monthly_cost_cents: i64,
#[serde(default, alias = "isDefault")]
pub is_default: Option<bool>,
#[serde(default, alias = "requirementsSubmittedAt")]
pub requirements_submitted_at: Option<String>,
#[serde(default, alias = "pendingCancellation")]
pub pending_cancellation: bool,
#[serde(default, alias = "scheduledReleaseAt")]
pub scheduled_release_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OwnedNumbersResponse {
#[serde(default)]
pub numbers: Vec<OwnedNumber>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NumberBuyAction {
pub url: String,
pub code: String,
#[serde(default, alias = "actionCode")]
pub action_code: Option<String>,
#[serde(default, alias = "expiresAt")]
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BuyNumberResponse {
pub status: String,
#[serde(default)]
pub number: Option<OwnedNumber>,
#[serde(default)]
pub requirements: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub action: Option<NumberBuyAction>,
}
#[derive(Debug, Clone, Default)]
pub struct ListAvailableNumbersOptions {
pub country: String,
pub r#type: String,
pub contains: Option<String>,
}
impl ListAvailableNumbersOptions {
pub fn new(country: impl Into<String>, r#type: impl Into<String>) -> Self {
Self {
country: country.into(),
r#type: r#type.into(),
contains: None,
}
}
pub fn contains(mut self, contains: impl Into<String>) -> Self {
self.contains = Some(contains.into());
self
}
pub(crate) fn to_query_params(&self) -> Vec<(String, String)> {
let mut params = vec![
("country".to_string(), self.country.clone()),
("type".to_string(), self.r#type.clone()),
];
if let Some(ref contains) = self.contains {
params.push(("contains".to_string(), contains.clone()));
}
params
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuyNumberRequest {
pub phone_number: String,
pub country_code: String,
pub phone_number_type: String,
pub monthly_cost: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub action_code: Option<String>,
}
impl BuyNumberRequest {
pub fn new(
phone_number: impl Into<String>,
country_code: impl Into<String>,
phone_number_type: impl Into<String>,
monthly_cost: impl Into<String>,
) -> Self {
Self {
phone_number: phone_number.into(),
country_code: country_code.into(),
phone_number_type: phone_number_type.into(),
monthly_cost: monthly_cost.into(),
action_code: None,
}
}
pub fn action_code(mut self, action_code: impl Into<String>) -> Self {
self.action_code = Some(action_code.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateNumberRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub is_default: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pending_cancellation: Option<bool>,
}
impl UpdateNumberRequest {
pub fn new() -> Self {
Self::default()
}
pub fn make_default(mut self) -> Self {
self.is_default = Some(true);
self
}
pub fn keep(mut self) -> Self {
self.pending_cancellation = Some(false);
self
}
fn has_mutation(&self) -> bool {
self.is_default.is_some() || self.pending_cancellation.is_some()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ReleaseNumberResponse {
#[serde(default)]
pub success: bool,
#[serde(default)]
pub scheduled: Option<bool>,
#[serde(default, alias = "scheduledReleaseAt")]
pub scheduled_release_at: Option<String>,
}
pub struct NumbersResource<'a> {
client: &'a Sendly,
}
impl<'a> NumbersResource<'a> {
pub(crate) fn new(client: &'a Sendly) -> Self {
Self { client }
}
pub async fn list_countries(&self) -> Result<NumberCountriesResponse> {
let response = self.client.get("/numbers/countries", &[]).await?;
Ok(response.json().await?)
}
pub async fn list_available(
&self,
options: ListAvailableNumbersOptions,
) -> Result<AvailableNumbersResponse> {
if options.country.is_empty() {
return Err(Error::Validation {
message: "list_available requires a country".to_string(),
});
}
if options.r#type.is_empty() {
return Err(Error::Validation {
message: "list_available requires a type".to_string(),
});
}
let params = options.to_query_params();
let response = self.client.get("/numbers/available", ¶ms).await?;
Ok(response.json().await?)
}
pub async fn list(&self) -> Result<OwnedNumbersResponse> {
let response = self.client.get("/numbers", &[]).await?;
Ok(response.json().await?)
}
pub async fn get(&self, id: &str) -> Result<OwnedNumber> {
if id.is_empty() {
return Err(Error::Validation {
message: "Number ID is required".to_string(),
});
}
let encoded_id = urlencoding::encode(id);
let path = format!("/numbers/{}", encoded_id);
let response = self.client.get(&path, &[]).await?;
Ok(response.json().await?)
}
pub async fn update(
&self,
id: &str,
request: UpdateNumberRequest,
) -> Result<OwnedNumber> {
if id.is_empty() {
return Err(Error::Validation {
message: "Number ID is required".to_string(),
});
}
if !request.has_mutation() {
return Err(Error::Validation {
message:
"Provide at least one of make_default() or keep() (isDefault / pendingCancellation)"
.to_string(),
});
}
let encoded_id = urlencoding::encode(id);
let path = format!("/numbers/{}", encoded_id);
let response = self.client.patch(&path, &request).await?;
Ok(response.json().await?)
}
pub async fn release(&self, id: &str) -> Result<ReleaseNumberResponse> {
if id.is_empty() {
return Err(Error::Validation {
message: "Number ID is required".to_string(),
});
}
let encoded_id = urlencoding::encode(id);
let path = format!("/numbers/{}", encoded_id);
let response = self.client.delete(&path).await?;
Ok(response.json().await?)
}
pub async fn buy(&self, request: BuyNumberRequest) -> Result<BuyNumberResponse> {
let response = self.client.post("/numbers/buy", &request).await?;
Ok(response.json().await?)
}
}