haprox-rs 0.3.2

A HaProxy v1/v2 protocol parser.
Documentation
/*-
 * haprox-rs - a HaProxy protocol parser.
 * 
 * Copyright 2025 (c) Aleksandr Morozov
 * The scram-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

use std::{borrow::Cow, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}};

use crate::{HaProxRes, HapProtoV1, map_error};

/// A trait which provides a conversion of strings into
/// [SocketAddr].
pub trait TryIntoSockAddr
{
    fn try_into_sock(self) -> HaProxRes<SocketAddr>;
}

impl TryIntoSockAddr for String
{
    #[inline]
    fn try_into_sock(self) -> HaProxRes<SocketAddr> 
    {
        return self.as_str().try_into_sock();
    }
}

impl TryIntoSockAddr for &String
{
    fn try_into_sock(self) -> HaProxRes<SocketAddr> 
    {
        return self.as_str().try_into_sock();
    }
}

impl TryIntoSockAddr for &str
{
    fn try_into_sock(self) -> HaProxRes<SocketAddr> 
    {
        return 
            self
                .parse()
                .map_err(|e|
                    map_error!(ArgumentEinval, "cannot parse string: '{}' to SocketAddr, err: '{}", self, e)
                );
    }
}

impl<'cow> TryIntoSockAddr for Cow<'cow, str>
{
    fn try_into_sock(self) -> HaProxRes<SocketAddr> 
    {
        return self.as_ref().try_into_sock();
    }
}

/// A HaProxyV1 composer.
/// 
/// Provides an interface to [HapProtoV1].
/// 
/// A function [Self::from_str] provides an option to avoid unnecesary conversions
/// of `string` literals and variables to [SocketAddr]. A [TryIntoSockAddr] is 
/// implemented to all string types.
/// 
/// ## Example
/// 
/// ```ignore
/// let res = 
///     ProxyHdrV1::from_str(ProtocolV1Inet::Tcp4, "192.168.1.1:333", "127.0.0.1:444")
///         .unwrap();
/// 
/// let msg = res.to_string();
/// ```
#[derive(Clone, Copy, Debug)]
pub struct ProxyHdrV1;

impl ProxyHdrV1
{
    /// Creates new message.
    /// 
    /// The `src` and `dst` must be of the same IP version type.
    /// 
    /// # Returns
    /// 
    /// The following errors are returned:
    /// 
    /// * [crate::error::HaProxErrType::ArgumentEinval] - if `src` or `dst` are not 
    ///     of same type.
    #[inline]
    pub 
    fn new(src: SocketAddr, dst: SocketAddr) -> HaProxRes<HapProtoV1>
    {
        return Self::new_ipaddr(src.ip(), src.port(), dst.ip(), dst.port());
    }

    /// Creates new message.
    /// 
    /// The `src` and `dst` must be of the same IP version type.
    /// 
    /// The function takes the argument of the following formats:
    /// 
    /// * ipv4:port i.e "127.0.0.1:4444"
    /// 
    /// * [ipv6]:port i,e "[0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f]:333"
    /// 
    /// # Returns
    /// 
    /// The following errors are returned:
    /// 
    /// * [crate::error::HaProxErrType::ArgumentEinval] - if `src` or `dst` are not 
    ///     of same type / parsing error.
    pub 
    fn from_str<S: TryIntoSockAddr>(src: S, dst: S) -> HaProxRes<HapProtoV1>
    {
        let src_addr = src.try_into_sock()?;
        let dst_addr = dst.try_into_sock()?;

        return HapProtoV1::from_ip_port(src_addr.ip(), dst_addr.ip(), src_addr.port(), dst_addr.port());
    }

    /// Creates new message.
    /// 
    /// The `src` and `dst` must be of the same IP version type.
    /// 
    /// # Returns
    /// 
    /// The following errors are returned:
    /// 
    /// * [crate::error::HaProxErrType::ArgumentEinval] - if `src` or `dst` are not 
    ///     of same type.
    #[inline]
    pub 
    fn new_ipaddr(src_addr: IpAddr, src_port: u16, dst_addr: IpAddr, dst_port: u16) -> HaProxRes<HapProtoV1>
    {
        return HapProtoV1::from_ip_port( src_addr, dst_addr, src_port, dst_port);
    }

    /// Creates new message for `inet` TCP4.
    /// 
    /// # Returns
    /// 
    /// Should never return error.
    #[inline]
    pub 
    fn new_ip4(src_addr: Ipv4Addr, src_port: u16, dst_addr: Ipv4Addr, dst_port: u16) -> HaProxRes<HapProtoV1>
    {
        return 
            HapProtoV1::from_ip_port(IpAddr::V4(src_addr), IpAddr::V4(dst_addr), src_port, dst_port);
    }

    /// Creates new message for `inet` TCP6.
    /// 
    /// # Returns
    /// 
    /// Should never return error.
    #[inline]
    pub 
    fn new_ip6(src_addr: Ipv6Addr, src_port: u16, dst_addr: Ipv6Addr, dst_port: u16) -> HaProxRes<HapProtoV1>
    {
        return 
            HapProtoV1::from_ip_port(IpAddr::V6(src_addr), IpAddr::V6(dst_addr), src_port, dst_port);
    }

    /// Creates new message for `inet` UNKNOWN.
    #[inline]
    pub 
    fn new_unknown() -> HapProtoV1
    {
        return HapProtoV1::unknown();
    }
}

#[cfg(test)]
mod tests_composer
{
    use crate::{protocol_v1::protocol_composer::ProxyHdrV1};

    #[test]
    fn test_from_string()
    {
        let res = 
            ProxyHdrV1::from_str("192.168.1.1:333", "127.0.0.1:444")
                .unwrap();

        assert_eq!(res.to_string(), "PROXY TCP4 192.168.1.1 127.0.0.1 333 444\r\n");
    }

    #[test]
    fn test0()
    {
        let res = 
            ProxyHdrV1
                ::new("192.168.1.1:333".parse().unwrap(), "127.0.0.1:444".parse().unwrap())
                    .unwrap();

        assert_eq!(res.to_string(), "PROXY TCP4 192.168.1.1 127.0.0.1 333 444\r\n");

        let res = 
            ProxyHdrV1
                ::new("[0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f]:333".parse().unwrap(), "[4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393]:444".parse().unwrap())
                    .unwrap();

        assert_eq!(res.to_string(), "PROXY TCP6 0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 333 444\r\n");
    }

    #[should_panic]
    #[test]
    fn test1_fail()
    {
        let _res = 
            ProxyHdrV1
                ::new("[0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f]:333".parse().unwrap(), "127.0.0.1:444".parse().unwrap())
                    .unwrap();
    }

    #[should_panic]
    #[test]
    fn test2_fail()
    {
        let _res = 
            ProxyHdrV1
                ::new("192.168.1.1:333".parse().unwrap(), "[0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f]:333".parse().unwrap())
                    .unwrap();
    }

}