opcua_types/
string.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Contains the implementation of `UAString`.
6
7use std::{
8    fmt,
9    io::{Read, Write},
10};
11
12use crate::{
13    encoding::{process_decode_io_result, process_encode_io_result, write_i32, EncodingResult},
14    read_i32, DecodingOptions, Error, OutOfRange, SimpleBinaryDecodable, SimpleBinaryEncodable,
15    UaNullable,
16};
17
18/// To avoid naming conflict hell, the OPC UA String type is typed `UAString` so it does not collide
19/// with the Rust `String`.
20///
21/// A string contains UTF-8 encoded characters or a null value. A null value is distinct from
22/// being an empty string so internally, the code maintains that distinction by holding the value
23/// as an `Option<String>`.
24#[derive(Eq, PartialEq, Debug, Clone, Hash)]
25pub struct UAString {
26    value: Option<String>,
27}
28
29impl fmt::Display for UAString {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        if let Some(ref value) = self.value {
32            write!(f, "{value}")
33        } else {
34            write!(f, "[null]")
35        }
36    }
37}
38
39impl UaNullable for UAString {
40    fn is_ua_null(&self) -> bool {
41        self.is_null()
42    }
43}
44
45#[cfg(feature = "json")]
46mod json {
47    use std::io::{Read, Write};
48    use struson::{
49        reader::{JsonReader, JsonStreamReader, ValueType},
50        writer::{JsonStreamWriter, JsonWriter},
51    };
52
53    use crate::json::{Context, JsonDecodable, JsonEncodable};
54
55    use super::{EncodingResult, UAString};
56
57    impl JsonEncodable for UAString {
58        fn encode(
59            &self,
60            stream: &mut JsonStreamWriter<&mut dyn Write>,
61            _ctx: &Context<'_>,
62        ) -> EncodingResult<()> {
63            if let Some(s) = self.value() {
64                stream.string_value(s)?;
65            } else {
66                stream.null_value()?;
67            }
68
69            Ok(())
70        }
71    }
72
73    impl JsonDecodable for UAString {
74        fn decode(
75            stream: &mut JsonStreamReader<&mut dyn Read>,
76            _ctx: &Context<'_>,
77        ) -> EncodingResult<Self> {
78            match stream.peek()? {
79                ValueType::String => Ok(stream.next_string()?.into()),
80                _ => {
81                    stream.next_null()?;
82                    Ok(UAString::null())
83                }
84            }
85        }
86    }
87}
88
89impl SimpleBinaryEncodable for UAString {
90    fn byte_len(&self) -> usize {
91        // Length plus the actual string length in bytes for a non-null string.
92        4 + match &self.value {
93            Some(s) => s.len(),
94            None => 0,
95        }
96    }
97
98    fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
99        // Strings are encoded as UTF8 chars preceded by an Int32 length. A -1 indicates a null string
100        match &self.value {
101            Some(s) => {
102                write_i32(stream, s.len() as i32)?;
103                let buf = s.as_bytes();
104                process_encode_io_result(stream.write_all(buf))
105            }
106            None => write_i32(stream, -1),
107        }
108    }
109}
110
111impl SimpleBinaryDecodable for UAString {
112    fn decode<S: Read + ?Sized>(
113        stream: &mut S,
114        decoding_options: &DecodingOptions,
115    ) -> EncodingResult<Self> {
116        let len = read_i32(stream)?;
117        // Null string?
118        if len == -1 {
119            Ok(UAString::null())
120        } else if len < -1 {
121            Err(Error::decoding(format!(
122                "String buf length is a negative number {len}"
123            )))
124        } else if len as usize > decoding_options.max_string_length {
125            Err(Error::decoding(format!(
126                "String buf length {} exceeds decoding limit {}",
127                len, decoding_options.max_string_length
128            )))
129        } else {
130            // Create a buffer filled with zeroes and read the string over the top
131            let mut buf = vec![0u8; len as usize];
132            process_decode_io_result(stream.read_exact(&mut buf))?;
133            let value = String::from_utf8(buf).map_err(|err| {
134                Error::decoding(format!("Decoded string was not valid UTF-8 - {err}"))
135            })?;
136            Ok(UAString::from(value))
137        }
138    }
139}
140
141#[cfg(feature = "xml")]
142mod xml {
143    use crate::xml::*;
144    use std::io::{Read, Write};
145
146    use super::UAString;
147
148    impl XmlType for UAString {
149        const TAG: &'static str = "String";
150    }
151
152    impl XmlEncodable for UAString {
153        fn encode(
154            &self,
155            writer: &mut XmlStreamWriter<&mut dyn Write>,
156            _ctx: &Context<'_>,
157        ) -> Result<(), Error> {
158            if let Some(s) = self.value() {
159                writer.write_text(s)?;
160            }
161
162            Ok(())
163        }
164    }
165
166    impl XmlDecodable for UAString {
167        fn decode(
168            read: &mut XmlStreamReader<&mut dyn Read>,
169            _context: &Context<'_>,
170        ) -> Result<Self, Error> {
171            Ok(read.consume_as_text()?.into())
172        }
173    }
174}
175
176impl From<UAString> for String {
177    fn from(value: UAString) -> Self {
178        value.as_ref().to_string()
179    }
180}
181
182impl AsRef<str> for UAString {
183    fn as_ref(&self) -> &str {
184        if self.is_null() {
185            ""
186        } else {
187            self.value.as_ref().unwrap()
188        }
189    }
190}
191
192impl<'a> From<&'a str> for UAString {
193    fn from(value: &'a str) -> Self {
194        Self::from(value.to_string())
195    }
196}
197
198impl From<&String> for UAString {
199    fn from(value: &String) -> Self {
200        UAString {
201            value: Some(value.clone()),
202        }
203    }
204}
205
206impl From<String> for UAString {
207    fn from(value: String) -> Self {
208        UAString { value: Some(value) }
209    }
210}
211
212impl From<Option<String>> for UAString {
213    fn from(value: Option<String>) -> Self {
214        UAString { value }
215    }
216}
217
218impl Default for UAString {
219    fn default() -> Self {
220        UAString::null()
221    }
222}
223
224impl PartialEq<str> for UAString {
225    fn eq(&self, other: &str) -> bool {
226        match self.value {
227            None => false,
228            Some(ref v) => v.eq(other),
229        }
230    }
231}
232
233impl PartialEq<&str> for UAString {
234    fn eq(&self, other: &&str) -> bool {
235        match self.value {
236            None => false,
237            Some(ref v) => v.eq(other),
238        }
239    }
240}
241
242impl PartialEq<&String> for UAString {
243    fn eq(&self, other: &&String) -> bool {
244        match self.value {
245            None => false,
246            Some(ref v) => v.eq(*other),
247        }
248    }
249}
250
251impl PartialEq<String> for UAString {
252    fn eq(&self, other: &String) -> bool {
253        match self.value {
254            None => false,
255            Some(ref v) => v.eq(other),
256        }
257    }
258}
259
260impl UAString {
261    /// Get the inner raw value.
262    pub fn value(&self) -> &Option<String> {
263        &self.value
264    }
265
266    /// Set the inner value.
267    pub fn set_value(&mut self, value: Option<String>) {
268        self.value = value;
269    }
270
271    /// Returns true if the string is null or empty, false otherwise
272    pub fn is_empty(&self) -> bool {
273        self.value.is_none() || self.value.as_ref().is_some_and(|v| v.is_empty())
274    }
275
276    /// Returns the length of the string in bytes or -1 for null.
277    pub fn len(&self) -> isize {
278        if self.value.is_none() {
279            -1
280        } else {
281            self.value.as_ref().unwrap().len() as isize
282        }
283    }
284
285    /// Create a null string (not the same as an empty string).
286    pub fn null() -> UAString {
287        UAString { value: None }
288    }
289
290    /// Test if the string is null.
291    pub fn is_null(&self) -> bool {
292        self.value.is_none()
293    }
294
295    /// This function is meant for use with NumericRange. It creates a substring from this string
296    /// from min up to and inclusive of max. Note that min must have an index within the string
297    /// but max is allowed to be beyond the end in which case the remainder of the string is
298    /// returned (see docs for NumericRange).
299    pub fn substring(&self, min: usize, max: usize) -> Result<UAString, OutOfRange> {
300        if let Some(ref v) = self.value() {
301            if min >= v.len() {
302                Err(OutOfRange)
303            } else {
304                let max = if max >= v.len() { v.len() - 1 } else { max };
305                Ok(UAString::from(&v[min..=max]))
306            }
307        } else {
308            Err(OutOfRange)
309        }
310    }
311}
312
313#[test]
314fn string_null() {
315    let s = UAString::null();
316    assert!(s.is_null());
317    assert!(s.is_empty());
318    assert_eq!(s.len(), -1);
319}
320
321#[test]
322fn string_empty() {
323    let s = UAString::from("");
324    assert!(!s.is_null());
325    assert!(s.is_empty());
326    assert_eq!(s.len(), 0);
327}
328
329#[test]
330fn string_value() {
331    let v = "Mary had a little lamb";
332    let s = UAString::from(v);
333    assert!(!s.is_null());
334    assert!(!s.is_empty());
335    assert_eq!(s.as_ref(), v);
336}
337
338#[test]
339#[allow(clippy::comparison_to_empty)]
340fn string_eq() {
341    let s = UAString::null();
342    assert!(!s.eq(""));
343
344    let s = UAString::from("");
345    assert!(s.eq(""));
346
347    let s = UAString::from("Sunshine");
348    assert!(s.ne("Moonshine"));
349    assert!(s.eq("Sunshine"));
350    assert!(!s.eq("Sunshine "));
351}
352
353#[test]
354fn string_substring() {
355    let a = "Mary had a little lamb";
356    let v = UAString::from(a);
357    let v2 = v.substring(0, 4).unwrap();
358    let a2 = v2.as_ref();
359    assert_eq!(a2, "Mary ");
360
361    let v2 = v.substring(2, 2).unwrap();
362    let a2 = v2.as_ref();
363    assert_eq!(a2, "r");
364
365    let v2 = v.substring(0, 2000).unwrap();
366    assert_eq!(v, v2);
367    assert_eq!(v2.as_ref(), a);
368
369    assert!(v.substring(22, 10000).is_err());
370
371    assert!(UAString::null().substring(0, 0).is_err());
372}