port-plumber 0.3.1

Utility bind ports with initialization commands
Documentation
use std::collections::{BTreeMap, HashMap};
use std::convert::Infallible;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
use crate::utils::serde::string_or_struct;

#[derive(Deserialize)]
pub struct PortPlumberConfig {
    pub socket: Option<PathBuf>,
    pub plumbing: BTreeMap<String, PlumbingItemConfig>,
}

#[derive(Deserialize)]
#[serde(tag = "mode")]
pub enum PlumbingItemConfig {
    Addr(SocketConf<AddrPlumbingConfig>),
    Name(SocketConf<NamePlumbingConfig>),
}

#[derive(Deserialize, Clone)]
pub struct SocketConf<T> {
    pub sockets: BTreeMap<String, T>
}

#[derive(Deserialize)]
pub struct AddrPlumbingConfig {
    pub source: u16,
    pub target: SocketAddr,
    pub resource: Option<ResourceConfig>,
}

#[derive(Deserialize, Clone)]
pub struct NamePlumbingConfig {
    pub source: u16,
    pub target: u16,
    pub resource: ResourceConfig,
}

#[derive(Deserialize, Debug, Clone)]
pub struct ResourceConfig {
    #[serde(deserialize_with = "string_or_struct")]
    pub setup: CommandConfig,
    #[serde(default)]
    pub warmup_millis: u64,
    #[serde(default)]
    pub healthcheck_cmd: Option<HealthcheckCmdConfig>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct HealthcheckCmdConfig {
    pub command: String,
    #[serde(default)]
    pub args: Vec<String>,
    pub timeout_millis: u64,
}

#[derive(Deserialize, Debug, Clone)]
pub struct CommandConfig {
    pub command: String,
    #[serde(default)]
    pub args: Vec<String>,
    #[serde(default = "std::env::temp_dir")]
    pub workingdir: PathBuf,
}

impl CommandConfig {
    pub fn render_template<T: Serialize>(&self, data: &T) -> anyhow::Result<Self> {
        let h = Handlebars::new();
        Ok(Self {
            command: h.render_template(&self.command, data)?,
            args: self.args.iter()
                .map(|arg| h.render_template(arg, data))
                .collect::<Result<Vec<_>, _>>()?,
            workingdir: self.workingdir.to_str()
                .map(|workdir| h.render_template(workdir, data))
                .transpose()?
                .map(PathBuf::from)
                .unwrap_or_else(std::env::temp_dir),
        })
    }
}

impl FromStr for CommandConfig {
    type Err = Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            command: String::from(s),
            args: Vec::new(),
            workingdir: std::env::temp_dir(),
        })
    }
}