ichen_openprotocol/
filters.rs

1#![allow(non_upper_case_globals)]
2
3use bitflags::*;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::fmt::{Display, Formatter};
6use std::ops::{Add, AddAssign};
7use std::str::FromStr;
8
9bitflags! {
10    /// General authorizations to access the iChen System via Open Protocol.
11    ///
12    /// See [this document] for details.
13    ///
14    /// [this document]: https://github.com/chenhsong/OpenProtocol/blob/master/doc/enums.md#filters
15    ///
16    pub struct Filters: u32 {
17        /// No rights.
18        const None = 0;
19        //
20        /// Controller status update messages.
21        const Status = 0b_0000_0001;
22        /// Cycle data messages.
23        const Cycle = 0b_0000_0010;
24        /// Mold data messages.
25        const Mold = 0b_0000_0100;
26        /// Controller action messages.
27        const Actions = 0b_0000_1000;
28        /// Controller alarm messages.
29        const Alarms = 0b_0001_0000;
30        /// Controller audit trail of setting changes
31        const Audit = 0b_0010_0000;
32        /// Administrator rights.
33        ///
34        /// `All` implies `Status` + `Cycle` + `Mold` + `Actions` + `Alarms` + `Audit`
35        const All = 0b_1111_1111;
36        //
37        /// MIS/MES integration: Job scheduling messages.
38        const JobCards = 0b_0001_0000_0000_0000;
39        /// MIS/MES integration: User authorization messages.
40        const Operators = 0b_0010_0000_0000_0000;
41        //
42        /// Industrial bus integration: Connect via OPC UA.
43        const OPCUA = 0b_0001_0000_0000_0000_0000_0000_0000_0000;
44    }
45}
46
47static ALL: &str = "Status | Cycle | Mold | Actions | Alarms | Audit | All";
48
49impl Filters {
50    /// Is a particular set of filters set?
51    ///
52    /// # Examples
53    ///
54    /// ~~~
55    /// # use ichen_openprotocol::*;
56    /// let f = Filters::Status + Filters::Audit + Filters::JobCards;
57    /// assert!(f.has(Filters::Status));
58    /// assert!(f.has(Filters::JobCards));
59    /// assert!(!f.has(Filters::All));
60    /// assert!(!f.has(Filters::OPCUA));
61    /// assert!(!f.has(Filters::Mold));
62    /// ~~~
63    pub fn has(self, other: Self) -> bool {
64        self.contains(other)
65    }
66}
67
68impl FromStr for Filters {
69    type Err = String;
70
71    /// Parse a comma-delimited `String` into a `Filters` values.
72    ///
73    /// **`Filters::from_str` never fails.**
74    /// Unmatched tokens will simply be discarded.
75    /// If nothing matches, `Filters::None` will be returned.
76    ///
77    /// # Examples
78    ///
79    /// ~~~
80    /// # use std::str::FromStr;
81    /// # use ichen_openprotocol::*;
82    /// let f = Filters::from_str("Hello, World, Cycle, Mold,Operators|Foo+BarXYZYXYZ=123").unwrap();
83    /// assert_eq!(Filters::Cycle + Filters::Mold, f);
84    ///
85    /// let f = Filters::from_str("All, OPCUA").unwrap();
86    /// assert_eq!(Filters::All + Filters::OPCUA, f);
87    /// assert!(f.has(Filters::All));
88    /// assert!(f.has(Filters::OPCUA));
89    /// assert!(!f.has(Filters::Operators));
90    /// assert!(!f.has(Filters::JobCards));
91    /// assert!(f.has(Filters::Cycle));
92    /// assert!(f.has(Filters::Status));
93    /// assert!(f.has(Filters::Mold));
94    /// assert!(f.has(Filters::Audit));
95    /// assert!(f.has(Filters::Alarms));
96    /// ~~~
97    fn from_str(text: &str) -> Result<Self, Self::Err> {
98        let text = text.trim();
99
100        Ok(if text == "None" || text.is_empty() {
101            Filters::None
102        } else {
103            text.split(',')
104                .map(|t| match t.trim() {
105                    "Status" => Filters::Status,
106                    "Cycle" => Filters::Cycle,
107                    "Mold" => Filters::Mold,
108                    "Actions" => Filters::Actions,
109                    "Alarms" => Filters::Alarms,
110                    "Audit" => Filters::Audit,
111                    "All" => Filters::All,
112                    "JobCards" => Filters::JobCards,
113                    "Operators" => Filters::Operators,
114                    "OPCUA" => Filters::OPCUA,
115                    _ => Filters::None,
116                })
117                .fold(Filters::None, |f, x| f | x)
118        })
119    }
120}
121
122impl<T: AsRef<str>> From<T> for Filters {
123    /// Call `Filters::from_str` to parse a filters value from a comma-delimited string.
124    fn from(s: T) -> Self {
125        // `Filters::from_str` does not fail.
126        Self::from_str(s.as_ref()).unwrap()
127    }
128}
129
130impl From<Filters> for String {
131    /// Convert filters value into a comma-delimited list.
132    fn from(f: Filters) -> Self {
133        f.to_string()
134    }
135}
136
137impl Add for Filters {
138    type Output = Self;
139
140    /// Turn on a particular filter.
141    ///
142    /// # Example
143    ///
144    /// ~~~
145    /// # use ichen_openprotocol::*;
146    /// let mut f = Filters::Cycle + Filters::OPCUA;
147    /// f = f + Filters::All;
148    /// assert_eq!(Filters::All + Filters::OPCUA, f);
149    /// ~~~
150    #[allow(clippy::suspicious_arithmetic_impl)]
151    fn add(self, rhs: Self) -> Self::Output {
152        self | rhs
153    }
154}
155
156impl AddAssign for Filters {
157    /// Turn on a particular filter.
158    ///
159    /// # Example
160    ///
161    /// ~~~
162    /// # use ichen_openprotocol::*;
163    /// let mut f = Filters::Cycle + Filters::OPCUA;
164    /// f += Filters::All;
165    /// assert_eq!(Filters::All + Filters::OPCUA, f);
166    /// ~~~
167    fn add_assign(&mut self, other: Self) {
168        *self |= other;
169    }
170}
171
172/// Serialize `Filters` as comma-separated list.
173///
174/// # Examples
175///
176/// ~~~
177/// # use ichen_openprotocol::*;
178/// let f = Filters::All + Filters::Cycle + Filters::OPCUA;
179/// assert_eq!("All, OPCUA", f.to_string());
180/// ~~~
181impl Display for Filters {
182    /// Display filters value as comma-delimited list.
183    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
184        let text = format!("{:?}", self);
185        let mut text = text.trim();
186
187        // Remove redundant flags when All is present
188        if text.starts_with(ALL) {
189            text = text[ALL.len() - 3..].trim();
190        }
191
192        if text.is_empty() {
193            write!(f, "None")
194        } else {
195            write!(f, "{}", text.replace(" | ", ", "))
196        }
197    }
198}
199
200impl Serialize for Filters {
201    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
202        s.serialize_str(&self.to_string())
203    }
204}
205
206impl<'de> Deserialize<'de> for Filters {
207    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
208        let s = Deserialize::deserialize(d).map_err(serde::de::Error::custom)?;
209        Filters::from_str(s).map_err(serde::de::Error::custom)
210    }
211}