protwrap 0.4.3

Thin protocol wrapper for network applications.
Documentation
#![allow(dead_code)]

use std::collections::HashMap;

use crate::err::Error;


pub struct ParsedSpec<'a> {
  pub(crate) addr: &'a str,

  // todo: rename to "fields'
  pub(crate) args: HashMap<&'a str, &'a str>
}

impl ParsedSpec<'_> {
  pub(crate) fn parse(spec: &str) -> Result<ParsedSpec<'_>, Error> {
    if spec.starts_with('{') && spec.ends_with('}') {
      // {addr,a=b,c=d} → addr,a=b,c=d
      let s = &spec[1..spec.len() - 1];

      // addr,a=b,c=d → addr a=b c=d
      let mut fields = s.split(',');

      // Get address
      let Some(addr) = fields.next() else {
        return Err(Error::BadProtSpec(
          "Missing address field in spec".into()
        ));
      };

      let fields = fields.filter_map(|x| x.split_once('='));
      let args: HashMap<&str, &str> = fields.collect::<HashMap<_, _>>();

      Ok(ParsedSpec { addr, args })
    } else {
      Ok(ParsedSpec {
        addr: spec,
        args: HashMap::new()
      })
    }
  }
}

impl ParsedSpec<'_> {
  #[cfg(feature = "tls")]
  pub(crate) fn is_tls(&self) -> Result<bool, Error> {
    let is_tls = self.get_bool("tls")?;
    if is_tls {
      return Ok(true);
    }
    if self.args.contains_key("cafile") {
      return Ok(true);
    }
    Ok(false)
  }

  #[cfg(unix)]
  pub(crate) fn is_uds(&self) -> bool {
    self.addr.find('/').is_some()
  }

  pub(crate) fn get_bool(&self, key: &str) -> Result<bool, Error> {
    self.args.get(key).map_or(Ok(false), |val| match *val {
      "yes" | "y" | "true" | "t" | "on" | "1" => Ok(true),
      "no" | "n" | "false" | "f" | "off" | "0" => Ok(false),
      _ => {
        let msg = format!("Invalid boolean value '{val}' for key='{key}'");
        Err(Error::BadProtSpec(msg))
      }
    })
  }
}


#[cfg(test)]
mod tests {
  use super::ParsedSpec;

  #[test]
  fn tcp() {
    let ps = ParsedSpec::parse("127.0.0.1:1234").unwrap();

    #[cfg(feature = "tls")]
    assert!(!ps.is_tls().unwrap());

    #[cfg(unix)]
    assert!(!ps.is_uds());

    assert_eq!(ps.addr, "127.0.0.1:1234");
  }

  #[cfg(unix)]
  #[test]
  fn uds() {
    let ps = ParsedSpec::parse("/tmp/foo.sock").unwrap();
    assert!(ps.is_uds());
    assert_eq!(ps.addr, "/tmp/foo.sock");
  }

  #[cfg(feature = "tls")]
  #[test]
  fn tls() {
    let ps = ParsedSpec::parse("{127.0.0.1:2345,tls=true}").unwrap();
    assert!(ps.is_tls().unwrap());
    assert_eq!(ps.addr, "127.0.0.1:2345");
  }

  #[cfg(feature = "tls")]
  #[test]
  fn tls_cafile() {
    let ps = ParsedSpec::parse("{127.0.0.1:2345,tls=true,cafile=/tmp/ca.pem}")
      .unwrap();
    let cafile = ps.args.get("cafile").unwrap();
    assert_eq!(*cafile, "/tmp/ca.pem");
  }
}

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