From 5d0f5b2a0b0927c0eadf298e073dcdccabfd06a2 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 12:42:26 -0700 Subject: [PATCH 01/18] implement utility methods for `ua::RelativePath` --- src/ua/data_types/relative_path.rs | 67 +++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 6fdf229e..147a77e6 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,4 +1,9 @@ -use crate::ua; +use std::ptr; + +use open62541_sys::UA_RelativePath_parse; +use open62541_sys::UA_RelativePath_print; + +use crate::{DataType as _, Error, ua}; crate::data_type!(RelativePath); @@ -9,4 +14,64 @@ impl RelativePath { array.move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); self } + + #[must_use] + pub fn elements(&self) -> Option<&[ua::RelativePathElement]> { + // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. + // Pointer validity is checked in `Array::slice_from_raw_parts()`. + unsafe { + ua::Array::slice_from_raw_parts(self.0.elementsSize, self.0.elements) + } + } + + #[must_use] + pub fn elements_mut(&mut self) -> Option<&mut [ua::RelativePathElement]> { + // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. + // Pointer validity is checked in `Array::slice_from_raw_parts_mut()`. + unsafe { + ua::Array::slice_from_raw_parts_mut(self.0.elementsSize, self.0.elements) + } + } + + pub fn len(&self) -> usize { + self.0.elementsSize + } + + pub fn is_empty(&self) -> bool { + self.0.elementsSize == 0 + } + + pub fn parse(path: &str) -> Result { + let path = ua::String::new(path)?; + let mut slf = Self::init(); + + let result = unsafe { + UA_RelativePath_parse(slf.as_mut_ptr(), ptr::read(path.as_ptr())) + }; + + let status_code = ua::StatusCode::new(result); + + if status_code.is_good() { + Ok(slf) + } else { + Err(Error::new(status_code)) + } + } + + pub fn to_string(&self) -> Result { + let mut str = ua::String::null(); + + let status_code = ua::StatusCode::new(unsafe { + // SAFETY: `UA_RelativePath_print` will initialize the string if null. + // See UA_RelativePath_print docs (https://open62541.org/doc/master/util.html#example-relativepaths) + UA_RelativePath_print(&self.0, str.as_mut_ptr()) + }); + + if status_code.is_good() { + Ok(str.to_string()) + } else { + Err(Error::new(status_code)) + } + } } + From 4614d308779c2c338490eacec18bcaf574a3de95 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 12:46:00 -0700 Subject: [PATCH 02/18] update doc comments --- src/ua/data_types/relative_path.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 147a77e6..8d636c26 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -33,14 +33,18 @@ impl RelativePath { } } + /// Returns the number of elements in the relative path. pub fn len(&self) -> usize { self.0.elementsSize } + /// Returns true if the relative path has no elements. pub fn is_empty(&self) -> bool { self.0.elementsSize == 0 } + /// Parses a relative path from a string, using the limited grammar implemented by open62541. + /// See [UA_RelativePath_parse](https://open62541.org/doc/master/util.html#example-relativepaths) docs. pub fn parse(path: &str) -> Result { let path = ua::String::new(path)?; let mut slf = Self::init(); @@ -58,6 +62,8 @@ impl RelativePath { } } + /// Renders the relative path as a string. + /// See [UA_RelativePath_print](https://open62541.org/doc/master/util.html#example-relativepaths) docs. pub fn to_string(&self) -> Result { let mut str = ua::String::null(); From 956cb90b297e47e4b4974ccee2c81b73dae27f88 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 13:28:30 -0700 Subject: [PATCH 03/18] accessors for `ua::RelativePathElement` --- src/ua/data_types/relative_path_element.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ua/data_types/relative_path_element.rs b/src/ua/data_types/relative_path_element.rs index 55a5a002..00ef53e5 100644 --- a/src/ua/data_types/relative_path_element.rs +++ b/src/ua/data_types/relative_path_element.rs @@ -26,4 +26,20 @@ impl RelativePathElement { target_name.clone_into_raw(&mut self.0.targetName); self } + + pub fn target_name(&self) -> &ua::QualifiedName { + ua::QualifiedName::raw_ref(&self.0.targetName) + } + + pub const fn is_inverse(&self) -> bool { + self.0.isInverse + } + + pub const fn include_subtypes(&self) -> bool { + self.0.includeSubtypes + } + + pub fn reference_type_id(&self) -> &ua::NodeId { + ua::NodeId::raw_ref(&self.0.referenceTypeId) + } } From 12c962cfc343bc2478e9c9ca396ba27e5827c73e Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 13:45:48 -0700 Subject: [PATCH 04/18] implement `FromStr` for `ua::RelativePath` --- src/ua/data_types/relative_path.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 8d636c26..2849ccc7 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,4 +1,5 @@ use std::ptr; +use std::str::FromStr; use open62541_sys::UA_RelativePath_parse; use open62541_sys::UA_RelativePath_print; @@ -81,3 +82,11 @@ impl RelativePath { } } +impl FromStr for RelativePath { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} + From 50d577ef96c192b658c3c8e47b3e7f6ae346b061 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 14:06:22 -0700 Subject: [PATCH 05/18] accessors for `ua::BrowsePath` --- src/ua/data_types/browse_path.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ua/data_types/browse_path.rs b/src/ua/data_types/browse_path.rs index 860d9846..d405b781 100644 --- a/src/ua/data_types/browse_path.rs +++ b/src/ua/data_types/browse_path.rs @@ -14,4 +14,12 @@ impl BrowsePath { relative_path.clone_into_raw(&mut self.0.relativePath); self } + + pub fn starting_node(&self) -> &ua::NodeId { + ua::NodeId::raw_ref(&self.0.startingNode) + } + + pub fn relative_path(&self) -> &ua::RelativePath { + ua::RelativePath::raw_ref(&self.0.relativePath) + } } From b1163758dd8f04494566ac3fbde3ff16b049e005 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 24 Jul 2026 14:34:40 -0700 Subject: [PATCH 06/18] relative path stringification is actually pretty safe --- src/ua/data_types/browse_path.rs | 2 + src/ua/data_types/relative_path.rs | 60 ++++++++++++---------- src/ua/data_types/relative_path_element.rs | 4 ++ 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/ua/data_types/browse_path.rs b/src/ua/data_types/browse_path.rs index d405b781..0184cde9 100644 --- a/src/ua/data_types/browse_path.rs +++ b/src/ua/data_types/browse_path.rs @@ -15,10 +15,12 @@ impl BrowsePath { self } + #[must_use] pub fn starting_node(&self) -> &ua::NodeId { ua::NodeId::raw_ref(&self.0.startingNode) } + #[must_use] pub fn relative_path(&self) -> &ua::RelativePath { ua::RelativePath::raw_ref(&self.0.relativePath) } diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 2849ccc7..0741382f 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::ptr; use std::str::FromStr; @@ -20,39 +21,39 @@ impl RelativePath { pub fn elements(&self) -> Option<&[ua::RelativePathElement]> { // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. // Pointer validity is checked in `Array::slice_from_raw_parts()`. - unsafe { - ua::Array::slice_from_raw_parts(self.0.elementsSize, self.0.elements) - } + unsafe { ua::Array::slice_from_raw_parts(self.0.elementsSize, self.0.elements) } } #[must_use] pub fn elements_mut(&mut self) -> Option<&mut [ua::RelativePathElement]> { // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. // Pointer validity is checked in `Array::slice_from_raw_parts_mut()`. - unsafe { - ua::Array::slice_from_raw_parts_mut(self.0.elementsSize, self.0.elements) - } + unsafe { ua::Array::slice_from_raw_parts_mut(self.0.elementsSize, self.0.elements) } } /// Returns the number of elements in the relative path. - pub fn len(&self) -> usize { + #[must_use] + pub const fn len(&self) -> usize { self.0.elementsSize } /// Returns true if the relative path has no elements. - pub fn is_empty(&self) -> bool { + #[must_use] + pub const fn is_empty(&self) -> bool { self.0.elementsSize == 0 } /// Parses a relative path from a string, using the limited grammar implemented by open62541. + /// + /// # Errors + /// Will return an error if the string is not parseable as a relative path. + /// /// See [UA_RelativePath_parse](https://open62541.org/doc/master/util.html#example-relativepaths) docs. pub fn parse(path: &str) -> Result { let path = ua::String::new(path)?; let mut slf = Self::init(); - let result = unsafe { - UA_RelativePath_parse(slf.as_mut_ptr(), ptr::read(path.as_ptr())) - }; + let result = unsafe { UA_RelativePath_parse(slf.as_mut_ptr(), ptr::read(path.as_ptr())) }; let status_code = ua::StatusCode::new(result); @@ -62,31 +63,34 @@ impl RelativePath { Err(Error::new(status_code)) } } +} + +impl FromStr for RelativePath { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} - /// Renders the relative path as a string. - /// See [UA_RelativePath_print](https://open62541.org/doc/master/util.html#example-relativepaths) docs. - pub fn to_string(&self) -> Result { +impl fmt::Display for RelativePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut str = ua::String::null(); let status_code = ua::StatusCode::new(unsafe { // SAFETY: `UA_RelativePath_print` will initialize the string if null. // See UA_RelativePath_print docs (https://open62541.org/doc/master/util.html#example-relativepaths) - UA_RelativePath_print(&self.0, str.as_mut_ptr()) + UA_RelativePath_print(&raw const self.0, str.as_mut_ptr()) }); - if status_code.is_good() { - Ok(str.to_string()) - } else { - Err(Error::new(status_code)) - } - } -} + // A lot of chained `UA_String_append` & `UA_String_escapeAppend` + // calls + an optimistic stack allocation w/ retry if the buffer is too small. + // SAFETY: `UA_RelativePath_print` will only return an error on a failed allocation. + debug_assert!( + status_code.is_good(), + "failed to print relative path: {status_code}" + ); -impl FromStr for RelativePath { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::parse(s) + write!(f, "{str}") } } - diff --git a/src/ua/data_types/relative_path_element.rs b/src/ua/data_types/relative_path_element.rs index 00ef53e5..094f4f02 100644 --- a/src/ua/data_types/relative_path_element.rs +++ b/src/ua/data_types/relative_path_element.rs @@ -27,18 +27,22 @@ impl RelativePathElement { self } + #[must_use] pub fn target_name(&self) -> &ua::QualifiedName { ua::QualifiedName::raw_ref(&self.0.targetName) } + #[must_use] pub const fn is_inverse(&self) -> bool { self.0.isInverse } + #[must_use] pub const fn include_subtypes(&self) -> bool { self.0.includeSubtypes } + #[must_use] pub fn reference_type_id(&self) -> &ua::NodeId { ua::NodeId::raw_ref(&self.0.referenceTypeId) } From 30ba6c9675fd58910cfe36279a53ab578e7a1cb7 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 09:24:06 -0500 Subject: [PATCH 07/18] address comments --- src/ua/data_types/relative_path.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 0741382f..b346424a 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,9 +1,6 @@ -use std::fmt; -use std::ptr; -use std::str::FromStr; +use std::{fmt, ptr, str::FromStr}; -use open62541_sys::UA_RelativePath_parse; -use open62541_sys::UA_RelativePath_print; +use open62541_sys::{UA_RelativePath_parse, UA_RelativePath_print}; use crate::{DataType as _, Error, ua}; @@ -51,14 +48,15 @@ impl RelativePath { /// See [UA_RelativePath_parse](https://open62541.org/doc/master/util.html#example-relativepaths) docs. pub fn parse(path: &str) -> Result { let path = ua::String::new(path)?; - let mut slf = Self::init(); + let mut parsed_path = Self::init(); - let result = unsafe { UA_RelativePath_parse(slf.as_mut_ptr(), ptr::read(path.as_ptr())) }; + let result = + unsafe { UA_RelativePath_parse(parsed_path.as_mut_ptr(), ptr::read(path.as_ptr())) }; let status_code = ua::StatusCode::new(result); if status_code.is_good() { - Ok(slf) + Ok(parsed_path) } else { Err(Error::new(status_code)) } @@ -91,6 +89,6 @@ impl fmt::Display for RelativePath { "failed to print relative path: {status_code}" ); - write!(f, "{str}") + str.fmt(f) } } From afedbd64b9128dc376c3c7e288e9c25b07461110 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 10:53:39 -0500 Subject: [PATCH 08/18] add indexing/accessor impls --- src/ua/data_types/relative_path.rs | 85 +++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index b346424a..5aee2ece 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,4 +1,4 @@ -use std::{fmt, ptr, str::FromStr}; +use std::{fmt, ops::{Index, IndexMut}, ptr, range::{Range, RangeInclusive}, slice::SliceIndex, str::FromStr}; use open62541_sys::{UA_RelativePath_parse, UA_RelativePath_print}; @@ -14,6 +14,9 @@ impl RelativePath { self } + /// Returns a view of the elements that make up this relative path. + /// + /// Returns [`Some`] if the relative path element array is valid, otherwise [`None`]. #[must_use] pub fn elements(&self) -> Option<&[ua::RelativePathElement]> { // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. @@ -21,6 +24,9 @@ impl RelativePath { unsafe { ua::Array::slice_from_raw_parts(self.0.elementsSize, self.0.elements) } } + /// Returns a mutable view of the elements that make up this relative path. + /// + /// Returns [`Some`] if the relative path element array is valid, otherwise [`None`]. #[must_use] pub fn elements_mut(&mut self) -> Option<&mut [ua::RelativePathElement]> { // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. @@ -28,6 +34,31 @@ impl RelativePath { unsafe { ua::Array::slice_from_raw_parts_mut(self.0.elementsSize, self.0.elements) } } + /// Attempts to access the element at `index`. + /// + /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] + #[must_use] + pub fn get(&self, index: I) -> Option<&>::Output> + where + I: SliceIndex<[ua::RelativePathElement]> + { + + self.elements() + .and_then(|elements| elements.get(index)) + } + + /// Attempts to access the element at `index` mutably. + /// + /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] + #[must_use] + pub fn get_mut(&mut self, index: I) -> Option<&mut >::Output> + where + I: SliceIndex<[ua::RelativePathElement]> + { + self.elements_mut() + .and_then(|elements| elements.get_mut(index)) + } + /// Returns the number of elements in the relative path. #[must_use] pub const fn len(&self) -> usize { @@ -61,6 +92,58 @@ impl RelativePath { Err(Error::new(status_code)) } } + + #[must_use] + pub fn iter(&self) -> impl ExactSizeIterator { + self.elements().unwrap_or(&[]).iter() + } + + #[must_use] + pub fn iter_mut(&mut self) -> impl ExactSizeIterator { + self.elements_mut().unwrap_or(&mut []).iter_mut() + } +} + +impl Index for RelativePath { + type Output = ua::RelativePathElement; + + fn index(&self, index: usize) -> &Self::Output { + &self.elements().unwrap()[index] + } +} + +impl IndexMut for RelativePath { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.elements_mut().unwrap()[index] + } +} + +impl Index> for RelativePath { + type Output = [ua::RelativePathElement]; + + fn index(&self, index: Range) -> &Self::Output { + &self.elements().unwrap()[index] + } +} + +impl IndexMut> for RelativePath { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + &mut self.elements_mut().unwrap()[index] + } +} + +impl Index> for RelativePath { + type Output = [ua::RelativePathElement]; + + fn index(&self, index: RangeInclusive) -> &Self::Output { + &self.elements().unwrap()[index] + } +} + +impl IndexMut> for RelativePath { + fn index_mut(&mut self, index: RangeInclusive) -> &mut Self::Output { + &mut self.elements_mut().unwrap()[index] + } } impl FromStr for RelativePath { From e6990c124447984e42962ad771dd80e4852dfda8 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 10:53:39 -0500 Subject: [PATCH 09/18] add tests --- src/ua/data_types/relative_path.rs | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 5aee2ece..66fcfdd2 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -175,3 +175,42 @@ impl fmt::Display for RelativePath { str.fmt(f) } } + +#[cfg(test)] +mod tests { + use super::RelativePath; + + #[test] + fn well_formed_path_parses_with_expected_output() { + let parsed = RelativePath::parse("/Objects/Server").expect("well-formed `RelativePath` string should parse"); + + assert_eq!(parsed.len(), 2); + + let elem = parsed.get(0).unwrap(); + assert_eq!(elem.target_name().to_string(), "Objects"); + assert!(!elem.is_inverse(), "`isInverse` wasn't specified for element 0"); + assert!(!elem.include_subtypes(), "`includeSubtypes` wasn't specified for element 0"); + + let elem = parsed.get(1).unwrap(); + assert_eq!(elem.target_name().to_string(), "Server"); + assert!(elem.is_inverse(), "`isInverse` was specified for element 1"); + assert_eq!(elem.reference_type_id().to_string(), "ns=0;i=34", "`HasChild` is a well-known `ReferenceType` with node ID of 'ns=0;i=34'"); + } + + #[test] + fn well_formed_path_survives_round_trip() { + const GOOD_PATH: &str = "/2:PLC1/2:GVL_MAIN"; + let parsed = RelativePath::parse(GOOD_PATH).expect("well-formed `RelativePath` string should parse"); + + assert_eq!(parsed.len(), 2); + assert_eq!(parsed.to_string(), GOOD_PATH); + } + + #[test] + fn malformed_path_fails_to_parse() { + const BAD_PATH: &str = "/!Bad!>/Path:1"; + let result = RelativePath::parse(BAD_PATH); + assert!(result.is_err(), "parsing a bad path should fail"); + } +} + From 55701fea537c8f48aece594fbf35337bf2d8ada9 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 11:27:11 -0500 Subject: [PATCH 10/18] update tests --- src/ua/data_types/relative_path.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 66fcfdd2..85da43ac 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -178,28 +178,37 @@ impl fmt::Display for RelativePath { #[cfg(test)] mod tests { + use crate::{DataType as _, ua}; use super::RelativePath; #[test] fn well_formed_path_parses_with_expected_output() { - let parsed = RelativePath::parse("/Objects/Server").expect("well-formed `RelativePath` string should parse"); + let parsed = RelativePath::parse("/Objects<#!HasChild>Server").expect("well-formed `RelativePath` string should parse"); assert_eq!(parsed.len(), 2); let elem = parsed.get(0).unwrap(); - assert_eq!(elem.target_name().to_string(), "Objects"); - assert!(!elem.is_inverse(), "`isInverse` wasn't specified for element 0"); - assert!(!elem.include_subtypes(), "`includeSubtypes` wasn't specified for element 0"); + let expected = ua::RelativePathElement::init() + .with_include_subtypes(true) + .with_is_inverse(false) + .with_target_name(&ua::QualifiedName::new(0, "Objects")) + .with_reference_type_id(&ua::NodeId::numeric(0, 33)); + + assert_eq!(elem, &expected); let elem = parsed.get(1).unwrap(); - assert_eq!(elem.target_name().to_string(), "Server"); - assert!(elem.is_inverse(), "`isInverse` was specified for element 1"); - assert_eq!(elem.reference_type_id().to_string(), "ns=0;i=34", "`HasChild` is a well-known `ReferenceType` with node ID of 'ns=0;i=34'"); + let expected = ua::RelativePathElement::init() + .with_include_subtypes(false) + .with_is_inverse(true) + .with_target_name(&ua::QualifiedName::new(0, "Server")) + .with_reference_type_id(&ua::NodeId::numeric(0, 34)); + + assert_eq!(elem, &expected); } #[test] fn well_formed_path_survives_round_trip() { - const GOOD_PATH: &str = "/2:PLC1/2:GVL_MAIN"; + const GOOD_PATH: &str = "2:PLC1/2:GVL_MAIN"; let parsed = RelativePath::parse(GOOD_PATH).expect("well-formed `RelativePath` string should parse"); assert_eq!(parsed.len(), 2); From bc7b60f53d5f44939ae4249f236b0559ca6ac8a0 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 11:32:27 -0500 Subject: [PATCH 11/18] fmt + clippy --- src/ua/data_types/relative_path.rs | 35 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 85da43ac..76f0df75 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,4 +1,11 @@ -use std::{fmt, ops::{Index, IndexMut}, ptr, range::{Range, RangeInclusive}, slice::SliceIndex, str::FromStr}; +use std::{ + fmt, + ops::{Index, IndexMut}, + ptr, + range::{Range, RangeInclusive}, + slice::SliceIndex, + str::FromStr, +}; use open62541_sys::{UA_RelativePath_parse, UA_RelativePath_print}; @@ -40,20 +47,21 @@ impl RelativePath { #[must_use] pub fn get(&self, index: I) -> Option<&>::Output> where - I: SliceIndex<[ua::RelativePathElement]> + I: SliceIndex<[ua::RelativePathElement]>, { - - self.elements() - .and_then(|elements| elements.get(index)) + self.elements().and_then(|elements| elements.get(index)) } /// Attempts to access the element at `index` mutably. /// /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] #[must_use] - pub fn get_mut(&mut self, index: I) -> Option<&mut >::Output> + pub fn get_mut( + &mut self, + index: I, + ) -> Option<&mut >::Output> where - I: SliceIndex<[ua::RelativePathElement]> + I: SliceIndex<[ua::RelativePathElement]>, { self.elements_mut() .and_then(|elements| elements.get_mut(index)) @@ -93,12 +101,12 @@ impl RelativePath { } } - #[must_use] + pub fn iter(&self) -> impl ExactSizeIterator { self.elements().unwrap_or(&[]).iter() } - #[must_use] + pub fn iter_mut(&mut self) -> impl ExactSizeIterator { self.elements_mut().unwrap_or(&mut []).iter_mut() } @@ -178,12 +186,13 @@ impl fmt::Display for RelativePath { #[cfg(test)] mod tests { - use crate::{DataType as _, ua}; use super::RelativePath; + use crate::{DataType as _, ua}; #[test] fn well_formed_path_parses_with_expected_output() { - let parsed = RelativePath::parse("/Objects<#!HasChild>Server").expect("well-formed `RelativePath` string should parse"); + let parsed = RelativePath::parse("/Objects<#!HasChild>Server") + .expect("well-formed `RelativePath` string should parse"); assert_eq!(parsed.len(), 2); @@ -209,7 +218,8 @@ mod tests { #[test] fn well_formed_path_survives_round_trip() { const GOOD_PATH: &str = "2:PLC1/2:GVL_MAIN"; - let parsed = RelativePath::parse(GOOD_PATH).expect("well-formed `RelativePath` string should parse"); + let parsed = + RelativePath::parse(GOOD_PATH).expect("well-formed `RelativePath` string should parse"); assert_eq!(parsed.len(), 2); assert_eq!(parsed.to_string(), GOOD_PATH); @@ -222,4 +232,3 @@ mod tests { assert!(result.is_err(), "parsing a bad path should fail"); } } - From 290f35931e0fc153be193317e72e4807bd8b79af Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 11:44:05 -0500 Subject: [PATCH 12/18] simplify `Index(Mut)` impl --- src/ua/data_types/relative_path.rs | 45 +++++------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 76f0df75..2aab0d15 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -45,7 +45,7 @@ impl RelativePath { /// /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] #[must_use] - pub fn get(&self, index: I) -> Option<&>::Output> + pub fn get(&self, index: I) -> Option<&I::Output> where I: SliceIndex<[ua::RelativePathElement]>, { @@ -56,10 +56,7 @@ impl RelativePath { /// /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] #[must_use] - pub fn get_mut( - &mut self, - index: I, - ) -> Option<&mut >::Output> + pub fn get_mut(&mut self, index: I) -> Option<&mut I::Output> where I: SliceIndex<[ua::RelativePathElement]>, { @@ -112,44 +109,16 @@ impl RelativePath { } } -impl Index for RelativePath { - type Output = ua::RelativePathElement; +impl> Index for RelativePath { + type Output = I::Output; - fn index(&self, index: usize) -> &Self::Output { + fn index(&self, index: I) -> &Self::Output { &self.elements().unwrap()[index] } } -impl IndexMut for RelativePath { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.elements_mut().unwrap()[index] - } -} - -impl Index> for RelativePath { - type Output = [ua::RelativePathElement]; - - fn index(&self, index: Range) -> &Self::Output { - &self.elements().unwrap()[index] - } -} - -impl IndexMut> for RelativePath { - fn index_mut(&mut self, index: Range) -> &mut Self::Output { - &mut self.elements_mut().unwrap()[index] - } -} - -impl Index> for RelativePath { - type Output = [ua::RelativePathElement]; - - fn index(&self, index: RangeInclusive) -> &Self::Output { - &self.elements().unwrap()[index] - } -} - -impl IndexMut> for RelativePath { - fn index_mut(&mut self, index: RangeInclusive) -> &mut Self::Output { +impl> IndexMut for RelativePath { + fn index_mut(&mut self, index: I) -> &mut Self::Output { &mut self.elements_mut().unwrap()[index] } } From 19ce29ae5e57df72efd0fb30fdabc7bdc41d0703 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 2 Oct 2026 11:46:10 -0500 Subject: [PATCH 13/18] fmt + clippy --- src/ua/data_types/relative_path.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 2aab0d15..144465f9 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -2,7 +2,6 @@ use std::{ fmt, ops::{Index, IndexMut}, ptr, - range::{Range, RangeInclusive}, slice::SliceIndex, str::FromStr, }; @@ -98,12 +97,10 @@ impl RelativePath { } } - pub fn iter(&self) -> impl ExactSizeIterator { self.elements().unwrap_or(&[]).iter() } - pub fn iter_mut(&mut self) -> impl ExactSizeIterator { self.elements_mut().unwrap_or(&mut []).iter_mut() } From bc9f7e97f192c213af2aaf5eca49ee8b61c59ba4 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Mon, 5 Oct 2026 07:45:53 -0500 Subject: [PATCH 14/18] remove unnecessary indexing ops --- src/ua/data_types/relative_path.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 144465f9..9d6aa024 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -1,10 +1,4 @@ -use std::{ - fmt, - ops::{Index, IndexMut}, - ptr, - slice::SliceIndex, - str::FromStr, -}; +use std::{fmt, ptr, slice::SliceIndex, str::FromStr}; use open62541_sys::{UA_RelativePath_parse, UA_RelativePath_print}; @@ -106,20 +100,6 @@ impl RelativePath { } } -impl> Index for RelativePath { - type Output = I::Output; - - fn index(&self, index: I) -> &Self::Output { - &self.elements().unwrap()[index] - } -} - -impl> IndexMut for RelativePath { - fn index_mut(&mut self, index: I) -> &mut Self::Output { - &mut self.elements_mut().unwrap()[index] - } -} - impl FromStr for RelativePath { type Err = Error; From 4cce6790f400097d99d8f01a6c0299f5593f007b Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 23 Oct 2026 15:44:38 -0500 Subject: [PATCH 15/18] update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b2c1de..7507b4ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### + +- Implemented accessor/utility methods for `ua::RelativePath`: + - `get`/`get_mut` methods for safe indexing/slicing of the underlying `ua::RelativePathElement` + array + - `elements`/`elements_mut` to access the `ua::Array` as an slice + - `iter`/`iter_mut` for iteration over the contained `ua::RelativePathElement`s + - `FromStr`/`Display` implementations that defer to native `UA_RelativePath_parse`/`_print` + implementations +- Implemented member accessor methods for `ua::BrowsePath` & `ua::RelativePathElement` + ## [0.12.1] - 2026-10-22 ### Added From ba45c44794d741bc4c4024aa2589bfa178385c3c Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Mon, 26 Oct 2026 16:20:04 -0500 Subject: [PATCH 16/18] fix changelog --- CHANGELOG.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7507b4ca..aec22111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -### - -- Implemented accessor/utility methods for `ua::RelativePath`: - - `get`/`get_mut` methods for safe indexing/slicing of the underlying `ua::RelativePathElement` - array - - `elements`/`elements_mut` to access the `ua::Array` as an slice - - `iter`/`iter_mut` for iteration over the contained `ua::RelativePathElement`s - - `FromStr`/`Display` implementations that defer to native `UA_RelativePath_parse`/`_print` - implementations -- Implemented member accessor methods for `ua::BrowsePath` & `ua::RelativePathElement` +### Added + +- Implemented accessor/utility methods for `ua::RelativePath`, `ua::BrowsePath` & + `ua::RelativePathElement` ## [0.12.1] - 2026-10-22 From 18ce6f02f4dbe5de498384a3997949e3ec79d90f Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 26 Oct 2026 23:23:46 +0100 Subject: [PATCH 17/18] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aec22111..521c3e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added -- Implemented accessor/utility methods for `ua::RelativePath`, `ua::BrowsePath` & +- Implemented accessor/utility methods for `ua::RelativePath`, `ua::BrowsePath`, and `ua::RelativePathElement` ## [0.12.1] - 2026-10-22 From 29b5228527139e492546a6544163d0142259e32d Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 26 Oct 2026 23:24:39 +0100 Subject: [PATCH 18/18] Apply suggestion from @uklotzde --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 521c3e6a..8cb36c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Implemented accessor/utility methods for `ua::RelativePath`, `ua::BrowsePath`, and - `ua::RelativePathElement` + `ua::RelativePathElement`. ## [0.12.1] - 2026-10-22