videosdk-server-sdk 0.1.0

Rust server SDK for the VideoSDK v2 REST APIs
Documentation
//! Provisioned phone numbers, and the gateways attached to them.

use std::sync::Arc;

use futures_util::Stream;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::client::{CallOptions, Client};
use crate::common::MessageResponse;
use crate::error::Result;
use crate::pagination::{auto_page, paginate, ListParams, Page, PageFetcher};
use crate::query::QueryBuilder;
use crate::resources::escape;
use crate::resources::sip::trunks::SipTrunk;
use crate::resources::sip::{SipAuth, SipMediaEncryption, SipRegion, SipTransport};

const PATH: &str = "/v2/sip/phone-numbers";

/// A provisioned phone number.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PhoneNumberInfo {
    /// The number's id.
    pub phone_number_id: String,
    /// The number in E.164 form.
    pub e164: String,
    /// The number's country code.
    pub country_code: Option<String>,
    /// The upstream provider.
    pub provider: Option<String>,
    /// Where the number came from.
    pub origin: Option<String>,
    /// The number's status.
    pub status: Option<String>,
    /// The number's display name.
    pub name: Option<String>,
    /// When the number was provisioned.
    pub created_at: Option<String>,
    /// When the number was last updated.
    pub updated_at: Option<String>,
    /// Any fields the server returned that this SDK does not model yet.
    #[serde(flatten)]
    pub extra: Map<String, Value>,
}

/// A phone number with the gateways attached to it.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PhoneNumberWithGateways {
    /// The provisioned number.
    pub phone_number: PhoneNumberInfo,
    /// The inbound gateway, if one is attached.
    pub inbound: Option<SipTrunk>,
    /// The outbound gateway, if one is attached.
    pub outbound: Option<SipTrunk>,
}

/// Configures a phone number's inbound gateway.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PhoneNumberInboundConfig {
    /// The SIP region. Required.
    pub sip_region: SipRegion,
}

/// Configures a phone number's outbound gateway.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PhoneNumberOutboundConfig {
    /// The SIP host. Required.
    pub address: String,
    /// The SIP region. Required.
    pub sip_region: SipRegion,
    /// The SIP credentials.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auth: Option<SipAuth>,
    /// The SIP transport. Defaults to TLS.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transport: Option<SipTransport>,
}

/// The parameters for [`PhoneNumbersResource::create`].
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePhoneNumbersParams {
    /// The display name. Required.
    pub name: String,
    /// The E.164 numbers to provision. Required, non-empty.
    pub phone_numbers: Vec<String>,
    /// Shares one gateway pair across every number, rather than a pair per
    /// number. Defaults to `true`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_shared: Option<bool>,
    /// Whether to encrypt media.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_encryption: Option<SipMediaEncryption>,
    /// The inbound gateway configuration. Required.
    pub inbound: PhoneNumberInboundConfig,
    /// The outbound gateway configuration. Required.
    pub outbound: PhoneNumberOutboundConfig,
}

/// The parameters for [`PhoneNumbersResource::update_gateway`].
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePhoneNumberGatewayParams {
    /// The gateway's display name.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Whether to encrypt media.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_encryption: Option<SipMediaEncryption>,
    /// The SIP region.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sip_region: Option<SipRegion>,
    /// The SIP host. Outbound gateways only.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    /// The SIP credentials. Outbound gateways only.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auth: Option<SipAuth>,
    /// The SIP transport. Outbound gateways only.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transport: Option<SipTransport>,
}

/// The query parameters for [`PhoneNumbersResource::list`].
#[derive(Debug, Clone, Default)]
pub struct PhoneNumberListParams {
    /// The 1-based page number.
    pub page: Option<u32>,
    /// Items per page.
    pub per_page: Option<u32>,
    /// An opaque cursor from a previous page.
    pub cursor: Option<String>,
    /// Matches on the number's name or E.164 form.
    pub search: Option<String>,
}

impl PhoneNumberListParams {
    fn pagination(&self) -> ListParams {
        ListParams {
            page: self.page,
            per_page: self.per_page,
            cursor: self.cursor.clone(),
        }
    }
}

/// Provisioned phone numbers. Reached via
/// [`SipResource::phone_numbers`](crate::SipResource::phone_numbers).
#[derive(Debug, Clone, Copy)]
pub struct PhoneNumbersResource<'a> {
    client: &'a Client,
}

impl<'a> PhoneNumbersResource<'a> {
    pub(crate) fn new(client: &'a Client) -> Self {
        Self { client }
    }

    /// Provisions numbers and attaches inbound and outbound gateways.
    pub async fn create(&self, params: CreatePhoneNumbersParams) -> Result<MessageResponse> {
        self.client
            .json(Method::POST, PATH, CallOptions::json(&params)?)
            .await
    }

    /// Lists provisioned numbers with their gateways, one page at a time.
    pub async fn list(
        &self,
        params: PhoneNumberListParams,
    ) -> Result<Page<PhoneNumberWithGateways>> {
        paginate(self.fetcher(&params), &params.pagination(), "data", None).await
    }

    /// Lists provisioned numbers, transparently fetching every page.
    pub fn list_stream(
        &self,
        params: PhoneNumberListParams,
    ) -> impl Stream<Item = Result<PhoneNumberWithGateways>> + Send {
        auto_page(self.fetcher(&params), params.pagination(), "data", None)
    }

    /// Fetches a single number, by its E.164 form, with its gateways.
    pub async fn get(&self, number: &str) -> Result<PhoneNumberWithGateways> {
        let path = format!("{PATH}/{}", escape(number));
        self.client
            .json(Method::GET, &path, CallOptions::new())
            .await
    }

    /// Detaches a number from its gateways.
    pub async fn detach(&self, phone_number: &str) -> Result<MessageResponse> {
        let body = serde_json::json!({ "phoneNumber": phone_number });
        let path = format!("{PATH}/detach");
        self.client
            .json(Method::POST, &path, CallOptions::json(&body)?)
            .await
    }

    /// Updates a phone number's gateway.
    pub async fn update_gateway(
        &self,
        gateway_id: &str,
        params: UpdatePhoneNumberGatewayParams,
    ) -> Result<MessageResponse> {
        let path = format!("{PATH}/gateways/{}", escape(gateway_id));
        self.client
            .json(Method::PATCH, &path, CallOptions::json(&params)?)
            .await
    }

    /// Releases a phone number. This has provider and billing side effects.
    pub async fn release(&self, phone_number_id: &str) -> Result<MessageResponse> {
        let path = format!("{PATH}/{}/release", escape(phone_number_id));
        self.client
            .json(Method::POST, &path, CallOptions::new())
            .await
    }

    fn fetcher(&self, params: &PhoneNumberListParams) -> PageFetcher {
        let client = self.client.clone();
        let search = params.search.clone();
        Arc::new(move |page, per_page| {
            let client = client.clone();
            let search = search.clone();
            Box::pin(async move {
                let query = QueryBuilder::new()
                    .opt("page", page)
                    .opt("perPage", per_page)
                    .opt_str("search", search.as_deref())
                    .into_pairs();
                client
                    .json::<Value>(Method::GET, PATH, CallOptions::new().query(query))
                    .await
            })
        })
    }
}