opcua_types/
qualified_name.rs1use std::{
7 fmt::Display,
8 io::{Read, Write},
9 sync::LazyLock,
10};
11
12use percent_encoding_rfc3986::percent_decode_str;
13use regex::Regex;
14
15use crate::{
16 encoding::{BinaryDecodable, BinaryEncodable, EncodingResult},
17 string::*,
18 NamespaceMap, UaNullable,
19};
20
21#[allow(unused)]
22mod opcua {
23 pub(super) use crate as types;
24}
25
26#[derive(PartialEq, Debug, Clone, Eq, Hash)]
42pub struct QualifiedName {
43 pub namespace_index: u16,
45 pub name: UAString,
47}
48
49impl UaNullable for QualifiedName {
50 fn is_ua_null(&self) -> bool {
51 self.is_null()
52 }
53}
54
55#[cfg(feature = "xml")]
56mod xml {
57 use crate::{xml::*, UAString};
58
59 use super::QualifiedName;
60
61 impl XmlType for QualifiedName {
62 const TAG: &'static str = "QualifiedName";
63 }
64
65 impl XmlEncodable for QualifiedName {
66 fn encode(
67 &self,
68 writer: &mut XmlStreamWriter<&mut dyn std::io::Write>,
69 context: &Context<'_>,
70 ) -> EncodingResult<()> {
71 let namespace_index = context.resolve_namespace_index_inverse(self.namespace_index)?;
72 writer.encode_child("NamespaceIndex", &namespace_index, context)?;
73 writer.encode_child("Name", &self.name, context)?;
74 Ok(())
75 }
76 }
77
78 impl XmlDecodable for QualifiedName {
79 fn decode(
80 read: &mut XmlStreamReader<&mut dyn std::io::Read>,
81 context: &Context<'_>,
82 ) -> Result<Self, Error> {
83 let mut namespace_index = None;
84 let mut name: Option<UAString> = None;
85
86 read.iter_children(
87 |key, stream, ctx| {
88 match key.as_str() {
89 "NamespaceIndex" => {
90 namespace_index = Some(XmlDecodable::decode(stream, ctx)?)
91 }
92 "Name" => name = Some(XmlDecodable::decode(stream, ctx)?),
93 _ => {
94 stream.skip_value()?;
95 }
96 }
97 Ok(())
98 },
99 context,
100 )?;
101
102 let Some(name) = name else {
103 return Ok(QualifiedName::null());
104 };
105
106 if let Some(namespace_index) = namespace_index {
107 Ok(QualifiedName {
108 namespace_index: context.resolve_namespace_index(namespace_index)?,
109 name,
110 })
111 } else {
112 Ok(QualifiedName::new(0, name))
113 }
114 }
115 }
116}
117
118#[cfg(feature = "json")]
119mod json {
120 use super::QualifiedName;
121
122 use crate::json::*;
123
124 impl JsonEncodable for QualifiedName {
126 fn encode(
127 &self,
128 stream: &mut JsonStreamWriter<&mut dyn std::io::Write>,
129 _ctx: &crate::Context<'_>,
130 ) -> crate::EncodingResult<()> {
131 if self.is_null() {
132 stream.null_value()?;
133 return Ok(());
134 }
135 stream.string_value(&self.to_string())?;
136 Ok(())
137 }
138 }
139
140 impl JsonDecodable for QualifiedName {
141 fn decode(
142 stream: &mut JsonStreamReader<&mut dyn std::io::Read>,
143 ctx: &Context<'_>,
144 ) -> crate::EncodingResult<Self> {
145 if matches!(stream.peek()?, ValueType::Null) {
146 return Ok(QualifiedName::null());
147 }
148
149 let raw = stream.next_str()?;
150 Ok(QualifiedName::parse(raw, ctx.namespaces()))
151 }
152 }
153}
154
155impl Default for QualifiedName {
156 fn default() -> Self {
157 Self::null()
158 }
159}
160
161impl<'a> From<&'a str> for QualifiedName {
162 fn from(value: &'a str) -> Self {
163 Self {
164 namespace_index: 0,
165 name: UAString::from(value),
166 }
167 }
168}
169
170impl From<&String> for QualifiedName {
171 fn from(value: &String) -> Self {
172 Self {
173 namespace_index: 0,
174 name: UAString::from(value),
175 }
176 }
177}
178
179impl From<String> for QualifiedName {
180 fn from(value: String) -> Self {
181 Self {
182 namespace_index: 0,
183 name: UAString::from(value),
184 }
185 }
186}
187
188impl BinaryEncodable for QualifiedName {
189 fn byte_len(&self, ctx: &opcua::types::Context<'_>) -> usize {
190 let mut size: usize = 0;
191 size += self.namespace_index.byte_len(ctx);
192 size += self.name.byte_len(ctx);
193 size
194 }
195
196 fn encode<S: Write + ?Sized>(
197 &self,
198 stream: &mut S,
199 ctx: &crate::Context<'_>,
200 ) -> EncodingResult<()> {
201 self.namespace_index.encode(stream, ctx)?;
202 self.name.encode(stream, ctx)
203 }
204}
205impl BinaryDecodable for QualifiedName {
206 fn decode<S: Read + ?Sized>(stream: &mut S, ctx: &crate::Context<'_>) -> EncodingResult<Self> {
207 let namespace_index = u16::decode(stream, ctx)?;
208 let name = UAString::decode(stream, ctx)?;
209 Ok(QualifiedName {
210 namespace_index,
211 name,
212 })
213 }
214}
215
216impl Display for QualifiedName {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 if self.namespace_index > 0 {
219 write!(f, "{}:{}", self.namespace_index, self.name)
220 } else {
221 write!(f, "{}", self.name)
222 }
223 }
224}
225
226static NUMERIC_QNAME_REGEX: LazyLock<Regex> =
227 LazyLock::new(|| Regex::new(r#"^(\d+):(.*)$"#).unwrap());
228
229impl QualifiedName {
230 pub fn new<T>(namespace_index: u16, name: T) -> QualifiedName
232 where
233 T: Into<UAString>,
234 {
235 QualifiedName {
236 namespace_index,
237 name: name.into(),
238 }
239 }
240
241 pub fn null() -> QualifiedName {
243 QualifiedName {
244 namespace_index: 0,
245 name: UAString::null(),
246 }
247 }
248
249 pub fn is_null(&self) -> bool {
251 self.namespace_index == 0 && self.name.is_null()
252 }
253
254 pub fn parse(raw: &str, namespaces: &NamespaceMap) -> QualifiedName {
258 if let Some(caps) = NUMERIC_QNAME_REGEX.captures(raw) {
260 if let Ok(namespace_index) = caps.get(1).unwrap().as_str().parse::<u16>() {
262 let name = caps.get(2).unwrap().as_str();
263 if namespaces
264 .known_namespaces()
265 .iter()
266 .any(|n| n.1 == &namespace_index)
267 {
268 return QualifiedName::new(namespace_index, name);
269 }
270 }
271 }
272
273 if let Some((l, r)) = raw.split_once(";") {
275 if let Ok(l) = percent_decode_str(l) {
276 if let Some(namespace_index) = namespaces.get_index(l.decode_utf8_lossy().as_ref())
277 {
278 return QualifiedName::new(namespace_index, r);
279 }
280 }
281 }
282
283 QualifiedName::new(0, raw)
284 }
285}