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}