diff --git a/async-opcua-types/src/errors.rs b/async-opcua-types/src/errors.rs index 8f4c05f8..d04cfc0e 100644 --- a/async-opcua-types/src/errors.rs +++ b/async-opcua-types/src/errors.rs @@ -5,12 +5,12 @@ use thiserror::Error; -use crate::VariantScalarTypeId; +use crate::{StatusCode, VariantScalarTypeId}; /// Rust OpcUa specific errors #[allow(missing_docs)] #[derive(Error, Debug)] -pub enum OpcUAError { +pub enum OpcUaError { #[error("Received an unexpected variant type")] UnexpectedVariantType { variant_id: Option, @@ -18,4 +18,30 @@ pub enum OpcUAError { }, #[error("The requested namespace does not exists")] NamespaceDoesNotExist(String), + #[error("Namespace is out of range of a u16.")] + NamespaceOutOfRange, + #[error("Supplied node resolver was unable to resolve a reference type.")] + UnresolvedReferenceType, + #[error("Path does not match a node.")] + NoMatch, + #[error("Path segment is unusually long and has been rejected.")] + PathSegmentTooLong, + #[error("Number of elements in relative path is too large.")] + TooManyElementsInPath, + #[error("Request returned a StatusCode Error.")] + StatusCodeError(StatusCode), + #[error("Generic Error.")] + Error(crate::Error), +} + +impl From for OpcUaError { + fn from(value: StatusCode) -> Self { + OpcUaError::StatusCodeError(value) + } +} + +impl From for OpcUaError { + fn from(value: crate::Error) -> Self { + OpcUaError::Error(value) + } } diff --git a/async-opcua-types/src/impls.rs b/async-opcua-types/src/impls.rs index ac2781b6..6f330178 100644 --- a/async-opcua-types/src/impls.rs +++ b/async-opcua-types/src/impls.rs @@ -18,9 +18,8 @@ use crate::{ AnonymousIdentityToken, ApplicationDescription, CallMethodRequest, DataTypeId, EndpointDescription, Error, ExpandedNodeId, HistoryUpdateType, IdentityCriteriaType, MessageSecurityMode, MonitoredItemCreateRequest, MonitoringMode, MonitoringParameters, - NumericRange, ObjectId, ReadValueId, ReferenceTypeId, RelativePath, RelativePathElement, - ServiceCounterDataType, ServiceFault, SignatureData, UserNameIdentityToken, UserTokenPolicy, - UserTokenType, + NumericRange, ObjectId, ReadValueId, ServiceCounterDataType, ServiceFault, SignatureData, + UserNameIdentityToken, UserTokenPolicy, UserTokenType, }; use super::PerformUpdateType; @@ -415,20 +414,3 @@ impl Default for IdentityCriteriaType { Self::Anonymous } } - -impl From<&[QualifiedName]> for RelativePath { - fn from(value: &[QualifiedName]) -> Self { - let elements = value - .iter() - .map(|qn| RelativePathElement { - reference_type_id: ReferenceTypeId::HierarchicalReferences.into(), - is_inverse: false, - include_subtypes: true, - target_name: qn.clone(), - }) - .collect(); - Self { - elements: Some(elements), - } - } -} diff --git a/async-opcua-types/src/namespaces.rs b/async-opcua-types/src/namespaces.rs index 995239cb..b119dae9 100644 --- a/async-opcua-types/src/namespaces.rs +++ b/async-opcua-types/src/namespaces.rs @@ -2,7 +2,7 @@ use hashbrown::HashMap; -use crate::{errors::OpcUAError, ExpandedNodeId, NodeId, Variant}; +use crate::{errors::OpcUaError, ExpandedNodeId, NodeId, Variant}; /// Utility for handling assignment of namespaces on server startup. #[derive(Debug, Default, Clone)] @@ -28,7 +28,7 @@ impl NamespaceMap { /// Create a new namespace map from a vec of variant as we get when reading /// the namespace array from the server - pub fn new_from_variant_array(array: &[Variant]) -> Result { + pub fn new_from_variant_array(array: &[Variant]) -> Result { let known_namespaces: HashMap = array .iter() .enumerate() @@ -36,7 +36,7 @@ impl NamespaceMap { if let Variant::String(s) = v { Ok((s.value().clone().unwrap_or(String::new()), idx as u16)) } else { - Err(OpcUAError::UnexpectedVariantType { + Err(OpcUaError::UnexpectedVariantType { variant_id: v.scalar_type_id(), message: "Namespace array on server contains invalid data".to_string(), }) diff --git a/async-opcua-types/src/relative_path.rs b/async-opcua-types/src/relative_path.rs index d4265332..c6c1cbd4 100644 --- a/async-opcua-types/src/relative_path.rs +++ b/async-opcua-types/src/relative_path.rs @@ -13,6 +13,7 @@ use log::error; use regex::Regex; use crate::{ + errors::OpcUaError, node_id::{Identifier, NodeId}, qualified_name::QualifiedName, string::UAString, @@ -28,7 +29,7 @@ impl RelativePath { /// Converts a string into a relative path. Caller must supply a `node_resolver` which will /// be used to look up nodes from their browse name. The function will reject strings /// that look unusually long or contain too many elements. - pub fn from_str(path: &str, node_resolver: &CB) -> Result + pub fn from_str(path: &str, node_resolver: &CB) -> Result where CB: Fn(u16, &str) -> Option, { @@ -66,14 +67,14 @@ impl RelativePath { } if token.len() > Self::MAX_TOKEN_LEN { error!("Path segment seems unusually long and has been rejected"); - return Err(RelativePathError::PathSegmentTooLong); + return Err(OpcUaError::PathSegmentTooLong); } } if !token.is_empty() { if elements.len() == Self::MAX_ELEMENTS { error!("Number of elements in relative path is too long, rejecting it"); - return Err(RelativePathError::TooManyElements); + return Err(OpcUaError::TooManyElementsInPath); } elements.push(RelativePathElement::from_str(&token, node_resolver)?); } @@ -84,6 +85,31 @@ impl RelativePath { } } +impl From<&[QualifiedName]> for RelativePath { + fn from(value: &[QualifiedName]) -> Self { + let elements = value + .iter() + .map(|qn| RelativePathElement { + reference_type_id: ReferenceTypeId::HierarchicalReferences.into(), + is_inverse: false, + include_subtypes: true, + target_name: qn.clone(), + }) + .collect(); + Self { + elements: Some(elements), + } + } +} + +impl TryFrom<&str> for RelativePath { + type Error = OpcUaError; + + fn try_from(value: &str) -> Result { + RelativePath::from_str(value, &RelativePathElement::default_node_resolver) + } +} + impl<'a> From<&'a RelativePathElement> for String { fn from(element: &'a RelativePathElement) -> String { let mut result = element @@ -226,10 +252,7 @@ impl RelativePathElement { /// * `foo` /// * `<#!2:MyReftype>2:blah` /// - pub fn from_str( - path: &str, - node_resolver: &CB, - ) -> Result + pub fn from_str(path: &str, node_resolver: &CB) -> Result where CB: Fn(u16, &str) -> Option, { @@ -270,7 +293,7 @@ impl RelativePathElement { node_resolver(namespace, browse_name) } else { error!("Namespace {} is out of range", namespace); - return Err(RelativePathError::NamespaceOutOfRange); + return Err(OpcUaError::NamespaceOutOfRange); } } else { node_resolver(0, browse_name) @@ -280,7 +303,7 @@ impl RelativePathElement { "Supplied node resolver was unable to resolve a reference type from {}", path ); - return Err(RelativePathError::UnresolvedReferenceType); + return Err(OpcUaError::UnresolvedReferenceType); } (reference_type_id.unwrap(), include_subtypes, is_inverse) } @@ -293,7 +316,7 @@ impl RelativePathElement { }) } else { error!("Path {} does not match a relative path", path); - Err(RelativePathError::NoMatch) + Err(OpcUaError::NoMatch) } } @@ -340,21 +363,6 @@ impl RelativePathElement { } } -#[derive(Debug, Clone)] -/// Error returned from parsing a relative path. -pub enum RelativePathError { - /// Namespace is out of range of a u16. - NamespaceOutOfRange, - /// Supplied node resolver was unable to resolve a reference type. - UnresolvedReferenceType, - /// Path does not match a relative path. - NoMatch, - /// Path segment is unusually long and has been rejected. - PathSegmentTooLong, - /// Number of elements in relative path is too large. - TooManyElements, -} - impl<'a> From<&'a RelativePath> for String { fn from(path: &'a RelativePath) -> String { if let Some(ref elements) = path.elements { @@ -398,7 +406,7 @@ fn unescape_browse_name(name: &str) -> String { /// * 0:foo /// * bar /// -fn target_name(target_name: &str) -> Result { +fn target_name(target_name: &str) -> Result { static RE: LazyLock = LazyLock::new(|| Regex::new(r"((?P[0-9+]):)?(?P.*)").unwrap()); if let Some(captures) = RE.captures(target_name) { @@ -410,7 +418,7 @@ fn target_name(target_name: &str) -> Result { "Namespace {} for target name is out of range", namespace.as_str() ); - return Err(RelativePathError::NamespaceOutOfRange); + return Err(OpcUaError::NamespaceOutOfRange); } } else { 0 diff --git a/samples/custom-structures-client/src/main.rs b/samples/custom-structures-client/src/main.rs index 50a91737..29643dbc 100644 --- a/samples/custom-structures-client/src/main.rs +++ b/samples/custom-structures-client/src/main.rs @@ -14,8 +14,9 @@ use opcua::{ crypto::SecurityPolicy, types::{ custom::{DynamicStructure, DynamicTypeLoader}, - BrowsePath, MessageSecurityMode, ObjectId, StatusCode, TimestampsToReturn, TypeLoader, - UserTokenPolicy, Variant, + errors::OpcUaError, + BrowsePath, MessageSecurityMode, ObjectId, TimestampsToReturn, TypeLoader, UserTokenPolicy, + Variant, }, }; @@ -100,7 +101,7 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn read_structure_var(session: &Arc, ns: u16) -> Result<(), StatusCode> { +async fn read_structure_var(session: &Arc, ns: u16) -> Result<(), OpcUaError> { let type_tree = DataTypeTreeBuilder::new(|f| f.namespace <= ns) .build(session) .await @@ -112,7 +113,7 @@ async fn read_structure_var(session: &Arc, ns: u16) -> Result<(), Statu let res = session .translate_browse_paths_to_node_ids(&[BrowsePath { starting_node: ObjectId::ObjectsFolder.into(), - relative_path: (&["ErrorData".into()][..]).into(), + relative_path: "/0:ErrorData".try_into()?, }]) .await?; let Some(target) = &res[0].targets else {