pdns-cli 0.0.3

Rust client library and CLI for the PowerDNS Authoritative Server API
Documentation
//! impl the server related PDNS API functions
//! source: <https://doc.powerdns.com/authoritative/http-api/server.html>

use super::error::PdnsAPIError;
use super::result::Result;

use serde::Deserialize;
use std::fmt;

// src: https://github.com/zhiburt/tabled#set-column-order
use tabled::Tabled;

/// The server information returned by pdns API.
/// Note, crucially, this does not contain any information about the client (and thus the pdns webserver) used to retrieve this information.
/// As a result, users may want to ensure that servers being compared, are retrieved from the same client/pdns webserver.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Tabled)]
pub struct Server {
    /// server id
    #[tabled(order = 0)]
    pub id: String,

    /// daemon type
    #[tabled(order = 1)]
    pub daemon_type: String,

    /// server version
    #[tabled(order = 2)]
    pub version: String,

    #[serde(rename = "type")] // type is a keyword in rust and thus unusable as field name
    /// server type
    #[tabled(skip)]
    pub server_type: String,

    /// server url
    #[tabled(skip)]
    pub url: String,

    /// server's config url
    #[tabled(skip)]
    pub config_url: String,

    /// server's zone url
    #[tabled(skip)]
    pub zones_url: String,

    /// server's automprimaries url
    #[tabled(skip)]
    pub autoprimaries: String,
}

impl fmt::Display for Server {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "pdns server with id={}, type={}, daemon_type={}",
            self.id, self.server_type, self.daemon_type
        )
    }
}

impl super::Client {
    /// list the servers
    ///
    /// # Errors
    /// - [`super::Error::Url`] if the api endpoint url could not be formed
    /// - [`super::Error::Http`] if errors in making request
    /// - [`super::Error::Http`] if errors in decoding response JSON on a successful request
    /// - [`super::Error::APIError`] if api returned an error (not 200 OK status)
    pub fn list_servers(&self) -> Result<Vec<Server>> {
        let url = self.base_url.join("servers")?;

        let resp = self.client.get(url).send()?;
        let status = resp.status();

        // status code denotes error
        if status != http::StatusCode::OK {
            // here since resp.json it not returned directly, the compiler
            // cant figure out the type to deserialize it into and thus we must specify it
            // the return type does, at least, signal to the compiler what type into should convert to
            return Err(resp.json::<PdnsAPIError>()?.into());
        }

        Ok(resp.json()?) // fn return type specifies the type to deserialize into
    }

    /// lists information on a specific server
    ///
    /// # Errors
    /// - [`super::Error::Url`] if the api endpoint url could not be formed
    /// - [`super::Error::Http`] if errors in making request
    /// - [`super::Error::Http`] if errors in decoding response JSON on a successful request
    /// - [`super::Error::APIError`] if api returned an error (not 200 OK status)
    pub fn list_server(&self, server_id: &str) -> Result<Server> {
        let url = self.base_url.join(&format!("servers/{server_id}"))?;

        let resp = self.client.get(url).send()?;
        let status = resp.status();

        // status code denotes error
        if status != http::StatusCode::OK {
            // here since resp.json it not returned directly, the compiler
            // cant figure out the type to deserialize it into and thus we must specify it
            // the return type does, at least, signal to the compiler what type into should convert to
            return Err(resp.json::<PdnsAPIError>()?.into());
        }

        Ok(resp.json()?) // fn return type specifies the type to deserialize into
    }
}