ichen-openprotocol 0.5.0

iChen Open Protocol access library.
Documentation
#![allow(non_upper_case_globals)]

use bitflags::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::ops::{Add, AddAssign};
use std::str::FromStr;

bitflags! {
    /// General authorizations to access the iChen System via Open Protocol.
    ///
    /// See [this document] for details.
    ///
    /// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/doc/enums.md#filters
    ///
    pub struct Filters: u32 {
        /// No rights.
        const None = 0;
        //
        /// Controller status update messages.
        const Status = 0b_0000_0001;
        /// Cycle data messages.
        const Cycle = 0b_0000_0010;
        /// Mold data messages.
        const Mold = 0b_0000_0100;
        /// Controller action messages.
        const Actions = 0b_0000_1000;
        /// Controller alarm messages.
        const Alarms = 0b_0001_0000;
        /// Controller audit trail of setting changes
        const Audit = 0b_0010_0000;
        /// Administrator rights.
        ///
        /// `All` implies `Status` + `Cycle` + `Mold` + `Actions` + `Alarms` + `Audit`
        const All = 0b_1111_1111;
        //
        /// MIS/MES integration: Job scheduling messages.
        const JobCards = 0b_0001_0000_0000_0000;
        /// MIS/MES integration: User authorization messages.
        const Operators = 0b_0010_0000_0000_0000;
        //
        /// Industrial bus integration: Connect via OPC UA.
        const OPCUA = 0b_0001_0000_0000_0000_0000_0000_0000_0000;
    }
}

static ALL: &str = "Status | Cycle | Mold | Actions | Alarms | Audit | All";

impl Filters {
    /// Is a particular set of filters set?
    ///
    /// # Examples
    ///
    /// ~~~
    /// # use ichen_openprotocol::*;
    /// let f = Filters::Status + Filters::Audit + Filters::JobCards;
    /// assert!(f.has(Filters::Status));
    /// assert!(f.has(Filters::JobCards));
    /// assert!(!f.has(Filters::All));
    /// assert!(!f.has(Filters::OPCUA));
    /// assert!(!f.has(Filters::Mold));
    /// ~~~
    pub fn has(self, other: Self) -> bool {
        self.contains(other)
    }
}

impl FromStr for Filters {
    type Err = String;

    /// Parse a comma-delimited `String` into a `Filters` values.
    ///
    /// **`Filters::from_str` never fails.**
    /// Unmatched tokens will simply be discarded.
    /// If nothing matches, `Filters::None` will be returned.
    ///
    /// # Examples
    ///
    /// ~~~
    /// # use std::str::FromStr;
    /// # use ichen_openprotocol::*;
    /// let f = Filters::from_str("Hello, World, Cycle, Mold,Operators|Foo+BarXYZYXYZ=123").unwrap();
    /// assert_eq!(Filters::Cycle + Filters::Mold, f);
    ///
    /// let f = Filters::from_str("All, OPCUA").unwrap();
    /// assert_eq!(Filters::All + Filters::OPCUA, f);
    /// assert!(f.has(Filters::All));
    /// assert!(f.has(Filters::OPCUA));
    /// assert!(!f.has(Filters::Operators));
    /// assert!(!f.has(Filters::JobCards));
    /// assert!(f.has(Filters::Cycle));
    /// assert!(f.has(Filters::Status));
    /// assert!(f.has(Filters::Mold));
    /// assert!(f.has(Filters::Audit));
    /// assert!(f.has(Filters::Alarms));
    /// ~~~
    fn from_str(text: &str) -> Result<Self, Self::Err> {
        let text = text.trim();

        Ok(if text == "None" || text.is_empty() {
            Filters::None
        } else {
            text.split(',')
                .map(|t| match t.trim() {
                    "Status" => Filters::Status,
                    "Cycle" => Filters::Cycle,
                    "Mold" => Filters::Mold,
                    "Actions" => Filters::Actions,
                    "Alarms" => Filters::Alarms,
                    "Audit" => Filters::Audit,
                    "All" => Filters::All,
                    "JobCards" => Filters::JobCards,
                    "Operators" => Filters::Operators,
                    "OPCUA" => Filters::OPCUA,
                    _ => Filters::None,
                })
                .fold(Filters::None, |f, x| f | x)
        })
    }
}

impl<T: AsRef<str>> From<T> for Filters {
    /// Call `Filters::from_str` to parse a filters value from a comma-delimited string.
    fn from(s: T) -> Self {
        // `Filters::from_str` does not fail.
        Self::from_str(s.as_ref()).unwrap()
    }
}

impl From<Filters> for String {
    /// Convert filters value into a comma-delimited list.
    fn from(f: Filters) -> Self {
        f.to_string()
    }
}

impl Add for Filters {
    type Output = Self;

    /// Turn on a particular filter.
    ///
    /// # Example
    ///
    /// ~~~
    /// # use ichen_openprotocol::*;
    /// let mut f = Filters::Cycle + Filters::OPCUA;
    /// f = f + Filters::All;
    /// assert_eq!(Filters::All + Filters::OPCUA, f);
    /// ~~~
    #[allow(clippy::suspicious_arithmetic_impl)]
    fn add(self, rhs: Self) -> Self::Output {
        self | rhs
    }
}

impl AddAssign for Filters {
    /// Turn on a particular filter.
    ///
    /// # Example
    ///
    /// ~~~
    /// # use ichen_openprotocol::*;
    /// let mut f = Filters::Cycle + Filters::OPCUA;
    /// f += Filters::All;
    /// assert_eq!(Filters::All + Filters::OPCUA, f);
    /// ~~~
    fn add_assign(&mut self, other: Self) {
        *self |= other;
    }
}

/// Serialize `Filters` as comma-separated list.
///
/// # Examples
///
/// ~~~
/// # use ichen_openprotocol::*;
/// let f = Filters::All + Filters::Cycle + Filters::OPCUA;
/// assert_eq!("All, OPCUA", f.to_string());
/// ~~~
impl Display for Filters {
    /// Display filters value as comma-delimited list.
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        let text = format!("{:?}", self);
        let mut text = text.trim();

        // Remove redundant flags when All is present
        if text.starts_with(ALL) {
            text = text[ALL.len() - 3..].trim();
        }

        if text.is_empty() {
            write!(f, "None")
        } else {
            write!(f, "{}", text.replace(" | ", ", "))
        }
    }
}

impl Serialize for Filters {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for Filters {
    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let s = Deserialize::deserialize(d).map_err(serde::de::Error::custom)?;
        Filters::from_str(s).map_err(serde::de::Error::custom)
    }
}