ddmw-client 0.3.2

Utility functions for DDMW client application/proxy integrations
Documentation
//! Methods used to establish connections to DDMW Core servers' client
//! interfaces.

use futures::sink::SinkExt;

use tokio::io::{AsyncRead, AsyncWrite};

use tokio_stream::StreamExt;

use tokio_util::codec::Framed;

use blather::{codec, Telegram};

use crate::auth::Auth;

use crate::err::Error;

pub use protwrap::tokio::Stream;
pub use protwrap::ProtAddr;


pub type Frm = Framed<protwrap::tokio::Stream, blather::Codec>;


/// Connect to one of the DDMW core's client interfaces and optionally attempt
/// to authenticate.
///
/// If `ProtAddr::Tcp()` is passed into the `pa` argument an TCP/IP connection
/// will be attempted.  If `ProtAddr::Uds()` (currently only available on
/// unix-like platforms) is used a unix local domain socket connection will be
/// attempted.
///
/// If `auth` has `Some` value, an authentication will be attempted after
/// successful connection.  If the authentication fails the entire connection
/// will fail.  To be able to keep the connection up in case the authentication
/// fails, pass `None` to the `auth` argument and manually authenticate in the
/// application.
///
/// # Examples
/// ```no_run
/// use ddmw_client::conn;
/// async fn test() {
///   let pa = protwrap::ProtAddr::Tcp("127.0.0.1:8777".to_string());
///   let conn = conn::connect(&pa, None).await.expect("Unable to connect");
/// }
/// ```
pub async fn connect(
  pa: &ProtAddr,
  auth: Option<&Auth>
) -> Result<Framed<protwrap::tokio::Stream, blather::Codec>, Error> {
  let strm = protwrap::tokio::connect(pa).await?;

  let mut framed = Framed::new(strm, blather::Codec::new());

  if let Some(auth) = auth {
    auth.authenticate(&mut framed).await?;
  }

  Ok(framed)
}


/// Send a telegram then wait for and return the server's reply.
/// If the server returns a `Fail`, it will be returned as
/// `Err(Error::ServerError)`.
pub async fn sendrecv<T>(
  conn: &mut Framed<T, blather::Codec>,
  tg: &Telegram
) -> Result<blather::Params, Error>
where
  T: AsyncRead + AsyncWrite + Unpin
{
  conn.send(tg).await?;
  expect_okfail(conn).await
}


/// Waits for a message and ensures that it's Ok or Fail.
/// Converts Fail state to an Error::ServerError.
/// Returns a Params buffer containig the Ok parameters on success.
pub async fn expect_okfail<T>(
  conn: &mut Framed<T, blather::Codec>
) -> Result<blather::Params, Error>
where
  T: AsyncRead + Unpin
{
  if let Some(o) = conn.next().await {
    let o = o?;
    match o {
      codec::Input::Telegram(tg) => {
        if let Some(topic) = tg.get_topic() {
          if topic == "Ok" {
            return Ok(tg.into_params());
          } else if topic == "Fail" {
            return Err(Error::ServerError(tg.into_params()));
          }
        }
      }
      _ => {
        println!("unexpected reply");
      }
    }
    return Err(Error::BadState("Unexpected reply from server.".to_string()));
  }

  Err(Error::Disconnected)
}


#[derive(Debug)]
pub struct WhoAmI {
  pub id: i64,
  pub name: String
}

/// Return the current owner of a connection.
///
/// # Examples
/// ```no_run
/// use ddmw_client::{conn, auth};
/// async fn test() {
///   let pa = protwrap::ProtAddr::Tcp("127.0.0.1:8777".to_string());
///   let auth = auth::Builder::new()
///     .name("elena")
///     .pass("secret")
///     .build().expect("Unable to build Auth buffer");
///   let mut frm = conn::connect(&pa, Some(&auth)).await
///     .expect("Connection failed");
///
///   let w = conn::whoami(&mut frm).await.expect("whoami failed");
///   assert_eq!(&w.name, "elena");
/// }
/// ```
pub async fn whoami<T>(
  conn: &mut Framed<T, blather::Codec>
) -> Result<WhoAmI, Error>
where
  T: AsyncRead + AsyncWrite + Unpin
{
  let tg = Telegram::new_topic("WhoAmI")?;
  let params = sendrecv(conn, &tg).await?;
  let id = params.get_param::<i64>("Id")?;
  let name = params.get_param("Name")?;
  Ok(WhoAmI { id, name })
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :