Skip to main content

opcua/types/
operand.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 Adam Lock
4
5use std::convert::TryFrom;
6
7use crate::types::{
8    attribute::AttributeId,
9    node_ids::ObjectId,
10    service_types::{
11        AttributeOperand, ContentFilter, ContentFilterElement, ElementOperand, FilterOperator,
12        LiteralOperand, SimpleAttributeOperand,
13    },
14    status_code::StatusCode,
15    DecodingOptions, ExtensionObject, NodeId, QualifiedName, UAString, Variant,
16};
17
18#[derive(PartialEq)]
19pub enum OperandType {
20    ElementOperand,
21    LiteralOperand,
22    AttributeOperand,
23    SimpleAttributeOperand,
24}
25
26pub enum Operand {
27    ElementOperand(ElementOperand),
28    LiteralOperand(LiteralOperand),
29    AttributeOperand(AttributeOperand),
30    SimpleAttributeOperand(SimpleAttributeOperand),
31}
32
33impl From<i8> for LiteralOperand {
34    fn from(v: i8) -> Self {
35        Self::from(Variant::from(v))
36    }
37}
38
39impl From<u8> for LiteralOperand {
40    fn from(v: u8) -> Self {
41        Self::from(Variant::from(v))
42    }
43}
44
45impl From<i16> for LiteralOperand {
46    fn from(v: i16) -> Self {
47        Self::from(Variant::from(v))
48    }
49}
50
51impl From<u16> for LiteralOperand {
52    fn from(v: u16) -> Self {
53        Self::from(Variant::from(v))
54    }
55}
56
57impl From<i32> for LiteralOperand {
58    fn from(v: i32) -> Self {
59        Self::from(Variant::from(v))
60    }
61}
62
63impl From<u32> for LiteralOperand {
64    fn from(v: u32) -> Self {
65        Self::from(Variant::from(v))
66    }
67}
68
69impl From<f32> for LiteralOperand {
70    fn from(v: f32) -> Self {
71        Self::from(Variant::from(v))
72    }
73}
74
75impl From<f64> for LiteralOperand {
76    fn from(v: f64) -> Self {
77        Self::from(Variant::from(v))
78    }
79}
80
81impl From<bool> for LiteralOperand {
82    fn from(v: bool) -> Self {
83        Self::from(Variant::from(v))
84    }
85}
86
87impl From<&str> for LiteralOperand {
88    fn from(v: &str) -> Self {
89        Self::from(Variant::from(v))
90    }
91}
92
93impl From<()> for LiteralOperand {
94    fn from(_v: ()) -> Self {
95        Self::from(Variant::from(()))
96    }
97}
98
99impl From<Variant> for LiteralOperand {
100    fn from(v: Variant) -> Self {
101        LiteralOperand { value: v }
102    }
103}
104
105impl TryFrom<&ExtensionObject> for Operand {
106    type Error = StatusCode;
107
108    fn try_from(v: &ExtensionObject) -> Result<Self, Self::Error> {
109        let object_id = v
110            .object_id()
111            .map_err(|_| StatusCode::BadFilterOperandInvalid)?;
112        let decoding_options = DecodingOptions::minimal();
113        let operand = match object_id {
114            ObjectId::ElementOperand_Encoding_DefaultBinary => {
115                Operand::ElementOperand(v.decode_inner::<ElementOperand>(&decoding_options)?)
116            }
117            ObjectId::LiteralOperand_Encoding_DefaultBinary => {
118                Operand::LiteralOperand(v.decode_inner::<LiteralOperand>(&decoding_options)?)
119            }
120            ObjectId::AttributeOperand_Encoding_DefaultBinary => {
121                Operand::AttributeOperand(v.decode_inner::<AttributeOperand>(&decoding_options)?)
122            }
123            ObjectId::SimpleAttributeOperand_Encoding_DefaultBinary => {
124                Operand::SimpleAttributeOperand(
125                    v.decode_inner::<SimpleAttributeOperand>(&decoding_options)?,
126                )
127            }
128            _ => {
129                return Err(StatusCode::BadFilterOperandInvalid);
130            }
131        };
132        Ok(operand)
133    }
134}
135
136impl From<&Operand> for ExtensionObject {
137    fn from(v: &Operand) -> Self {
138        match v {
139            Operand::ElementOperand(ref op) => {
140                ExtensionObject::from_encodable(ObjectId::ElementOperand_Encoding_DefaultBinary, op)
141            }
142            Operand::LiteralOperand(ref op) => {
143                ExtensionObject::from_encodable(ObjectId::LiteralOperand_Encoding_DefaultBinary, op)
144            }
145            Operand::AttributeOperand(ref op) => ExtensionObject::from_encodable(
146                ObjectId::AttributeOperand_Encoding_DefaultBinary,
147                op,
148            ),
149            Operand::SimpleAttributeOperand(ref op) => ExtensionObject::from_encodable(
150                ObjectId::SimpleAttributeOperand_Encoding_DefaultBinary,
151                op,
152            ),
153        }
154    }
155}
156
157impl From<Operand> for ExtensionObject {
158    fn from(v: Operand) -> Self {
159        Self::from(&v)
160    }
161}
162
163impl From<(FilterOperator, Vec<Operand>)> for ContentFilterElement {
164    fn from(v: (FilterOperator, Vec<Operand>)) -> ContentFilterElement {
165        ContentFilterElement {
166            filter_operator: v.0,
167            filter_operands: Some(v.1.iter().map(|op| op.into()).collect()),
168        }
169    }
170}
171
172impl From<ElementOperand> for Operand {
173    fn from(v: ElementOperand) -> Operand {
174        Operand::ElementOperand(v)
175    }
176}
177
178impl From<LiteralOperand> for Operand {
179    fn from(v: LiteralOperand) -> Self {
180        Operand::LiteralOperand(v)
181    }
182}
183
184impl From<SimpleAttributeOperand> for Operand {
185    fn from(v: SimpleAttributeOperand) -> Self {
186        Operand::SimpleAttributeOperand(v)
187    }
188}
189
190impl Operand {
191    pub fn element(index: u32) -> Operand {
192        ElementOperand { index }.into()
193    }
194
195    pub fn literal<T>(literal: T) -> Operand
196    where
197        T: Into<LiteralOperand>,
198    {
199        Operand::LiteralOperand(literal.into())
200    }
201
202    /// Creates a simple attribute operand. The browse path is the browse name using / as a separator.
203    pub fn simple_attribute<T>(
204        type_definition_id: T,
205        browse_path: &str,
206        attribute_id: AttributeId,
207        index_range: UAString,
208    ) -> Operand
209    where
210        T: Into<NodeId>,
211    {
212        SimpleAttributeOperand::new(type_definition_id, browse_path, attribute_id, index_range)
213            .into()
214    }
215
216    pub fn operand_type(&self) -> OperandType {
217        match self {
218            Operand::ElementOperand(_) => OperandType::ElementOperand,
219            Operand::LiteralOperand(_) => OperandType::LiteralOperand,
220            Operand::AttributeOperand(_) => OperandType::AttributeOperand,
221            Operand::SimpleAttributeOperand(_) => OperandType::SimpleAttributeOperand,
222        }
223    }
224
225    pub fn is_element(&self) -> bool {
226        self.operand_type() == OperandType::ElementOperand
227    }
228
229    pub fn is_literal(&self) -> bool {
230        self.operand_type() == OperandType::LiteralOperand
231    }
232
233    pub fn is_attribute(&self) -> bool {
234        self.operand_type() == OperandType::AttributeOperand
235    }
236
237    pub fn is_simple_attribute(&self) -> bool {
238        self.operand_type() == OperandType::SimpleAttributeOperand
239    }
240}
241
242/// This is a convenience for building [`ContentFilter`] using operands as building blocks
243/// This builder does not check to see that the content filter is valid, i.e. if you
244/// reference an element by index that doesn't exist, or introduce a loop then you will
245/// not get an error until you feed it to a server and the server rejects it or breaks.
246///
247/// The builder takes generic types to make it easier to work with. Operands are converted to
248/// extension objects.
249pub struct ContentFilterBuilder {
250    elements: Vec<ContentFilterElement>,
251}
252
253impl Default for ContentFilterBuilder {
254    fn default() -> Self {
255        ContentFilterBuilder {
256            elements: Vec::with_capacity(20),
257        }
258    }
259}
260
261impl ContentFilterBuilder {
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    fn add_element(
267        mut self,
268        filter_operator: FilterOperator,
269        filter_operands: Vec<Operand>,
270    ) -> Self {
271        let filter_operands = filter_operands.iter().map(ExtensionObject::from).collect();
272        self.elements.push(ContentFilterElement {
273            filter_operator,
274            filter_operands: Some(filter_operands),
275        });
276        self
277    }
278
279    pub fn eq<T, S>(self, o1: T, o2: S) -> Self
280    where
281        T: Into<Operand>,
282        S: Into<Operand>,
283    {
284        self.add_element(FilterOperator::Equals, vec![o1.into(), o2.into()])
285    }
286
287    pub fn null<T>(self, o1: T) -> Self
288    where
289        T: Into<Operand>,
290    {
291        self.add_element(FilterOperator::IsNull, vec![o1.into()])
292    }
293
294    pub fn gt<T, S>(self, o1: T, o2: S) -> Self
295    where
296        T: Into<Operand>,
297        S: Into<Operand>,
298    {
299        self.add_element(FilterOperator::GreaterThan, vec![o1.into(), o2.into()])
300    }
301
302    pub fn lt<T, S>(self, o1: T, o2: S) -> Self
303    where
304        T: Into<Operand>,
305        S: Into<Operand>,
306    {
307        self.add_element(FilterOperator::LessThan, vec![o1.into(), o2.into()])
308    }
309
310    pub fn gte<T, S>(self, o1: T, o2: S) -> Self
311    where
312        T: Into<Operand>,
313        S: Into<Operand>,
314    {
315        self.add_element(
316            FilterOperator::GreaterThanOrEqual,
317            vec![o1.into(), o2.into()],
318        )
319    }
320
321    pub fn lte<T, S>(self, o1: T, o2: S) -> Self
322    where
323        T: Into<Operand>,
324        S: Into<Operand>,
325    {
326        self.add_element(FilterOperator::LessThanOrEqual, vec![o1.into(), o2.into()])
327    }
328
329    pub fn like<T, S>(self, o1: T, o2: S) -> Self
330    where
331        T: Into<Operand>,
332        S: Into<Operand>,
333    {
334        self.add_element(FilterOperator::Like, vec![o1.into(), o2.into()])
335    }
336
337    pub fn not<T>(self, o1: T) -> Self
338    where
339        T: Into<Operand>,
340    {
341        self.add_element(FilterOperator::Not, vec![o1.into()])
342    }
343
344    pub fn between<T, S, U>(self, o1: T, o2: S, o3: U) -> Self
345    where
346        T: Into<Operand>,
347        S: Into<Operand>,
348        U: Into<Operand>,
349    {
350        self.add_element(
351            FilterOperator::Between,
352            vec![o1.into(), o2.into(), o3.into()],
353        )
354    }
355
356    pub fn in_list<T, S>(self, o1: T, list_items: Vec<S>) -> Self
357    where
358        T: Into<Operand>,
359        S: Into<Operand>,
360    {
361        // Make a list from the operand and then the items
362        let mut filter_operands = Vec::with_capacity(list_items.len() + 1);
363        filter_operands.push(o1.into());
364        list_items.into_iter().for_each(|list_item| {
365            filter_operands.push(list_item.into());
366        });
367        self.add_element(FilterOperator::InList, filter_operands)
368    }
369
370    pub fn and<T, S>(self, o1: T, o2: S) -> Self
371    where
372        T: Into<Operand>,
373        S: Into<Operand>,
374    {
375        self.add_element(FilterOperator::And, vec![o1.into(), o2.into()])
376    }
377
378    pub fn or<T, S>(self, o1: T, o2: S) -> Self
379    where
380        T: Into<Operand>,
381        S: Into<Operand>,
382    {
383        self.add_element(FilterOperator::Or, vec![o1.into(), o2.into()])
384    }
385
386    pub fn cast<T, S>(self, o1: T, o2: S) -> Self
387    where
388        T: Into<Operand>,
389        S: Into<Operand>,
390    {
391        self.add_element(FilterOperator::Cast, vec![o1.into(), o2.into()])
392    }
393
394    pub fn bitwise_and<T, S>(self, o1: T, o2: S) -> Self
395    where
396        T: Into<Operand>,
397        S: Into<Operand>,
398    {
399        self.add_element(FilterOperator::BitwiseAnd, vec![o1.into(), o2.into()])
400    }
401
402    pub fn bitwise_or<T, S>(self, o1: T, o2: S) -> Self
403    where
404        T: Into<Operand>,
405        S: Into<Operand>,
406    {
407        self.add_element(FilterOperator::BitwiseOr, vec![o1.into(), o2.into()])
408    }
409
410    pub fn build(self) -> ContentFilter {
411        ContentFilter {
412            elements: Some(self.elements),
413        }
414    }
415}
416
417impl SimpleAttributeOperand {
418    pub fn new<T>(
419        type_definition_id: T,
420        browse_path: &str,
421        attribute_id: AttributeId,
422        index_range: UAString,
423    ) -> Self
424    where
425        T: Into<NodeId>,
426    {
427        // An improbable string to replace escaped forward slashes.
428        const ESCAPE_PATTERN: &str = "###!!!###@@@$$$$";
429        // Any escaped forward slashes will be replaced temporarily to allow split to work.
430        let browse_path = browse_path.replace(r"\/", ESCAPE_PATTERN);
431        // If we had a regex with look around support then we could split a pattern such as `r"(?<!\\)/"` where it
432        // matches only if the pattern `/` isn't preceded by a backslash. Unfortunately the regex crate doesn't offer
433        // this so an escaped forward slash is replaced with an improbable string instead.
434        let browse_path = browse_path
435            .split('/')
436            .map(|s| QualifiedName::new(0, s.replace(ESCAPE_PATTERN, "/")))
437            .collect();
438        SimpleAttributeOperand {
439            type_definition_id: type_definition_id.into(),
440            browse_path: Some(browse_path),
441            attribute_id: attribute_id as u32,
442            index_range,
443        }
444    }
445}