From 636f533e83254a67987e8c8684fc910c6a5ac496 Mon Sep 17 00:00:00 2001 From: Einar Omang Date: Thu, 23 Jan 2025 08:56:13 +0100 Subject: [PATCH] Add some context to code gen errors A bit of a rough way to do it, but it's relatively easy and not that intrusive. It's very nice to get some general context about where in the process an error occurred when generating code, so that you can CTRL-F into the offending XML file and find where the code gen gave up. This adds some basic context to `CodeGenError` that lets us capture this information and pass it along. --- async-opcua-codegen/src/error.rs | 80 +++++++++++++++++-- async-opcua-codegen/src/ids/gen.rs | 4 +- async-opcua-codegen/src/lib.rs | 50 ++++++++---- .../src/nodeset/events/collector.rs | 25 +++--- async-opcua-codegen/src/nodeset/events/gen.rs | 33 ++++---- async-opcua-codegen/src/nodeset/gen.rs | 65 ++++++++++++--- async-opcua-codegen/src/nodeset/mod.rs | 9 ++- async-opcua-codegen/src/nodeset/render.rs | 18 ++--- async-opcua-codegen/src/nodeset/value.rs | 30 +++---- async-opcua-codegen/src/types/gen.rs | 14 ++-- async-opcua-codegen/src/types/loader.rs | 44 +++++++--- async-opcua-codegen/src/types/mod.rs | 7 +- 12 files changed, 271 insertions(+), 108 deletions(-) diff --git a/async-opcua-codegen/src/error.rs b/async-opcua-codegen/src/error.rs index ee3f61db6..d3c2bad5b 100644 --- a/async-opcua-codegen/src/error.rs +++ b/async-opcua-codegen/src/error.rs @@ -1,4 +1,5 @@ use std::{ + fmt::Display, num::{ParseFloatError, ParseIntError}, str::ParseBoolError, }; @@ -6,9 +7,9 @@ use std::{ use thiserror::Error; #[derive(Error, Debug)] -pub enum CodeGenError { +pub enum CodeGenErrorKind { #[error("Failed to load XML: {0}")] - XML(#[from] opcua_xml::XmlError), + Xml(#[from] opcua_xml::XmlError), #[error("Missing required field: {0}")] MissingRequiredValue(&'static str), #[error("Wrong format on field. Expected {0}, got {1}")] @@ -27,26 +28,93 @@ pub enum CodeGenError { Io(String, std::io::Error), } +#[derive(Error, Debug)] +pub struct CodeGenError { + #[source] + pub kind: Box, + pub context: Option, + pub file: Option, +} + +impl Display for CodeGenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Code generation failed: {}", self.kind)?; + if let Some(context) = &self.context { + write!(f, ", while {context}")?; + } + if let Some(file) = &self.file { + write!(f, ", while loading file {file}")?; + } + Ok(()) + } +} + impl From for CodeGenError { fn from(value: ParseIntError) -> Self { - Self::ParseInt("content".to_owned(), value) + Self::new(CodeGenErrorKind::ParseInt("content".to_owned(), value)) } } impl From for CodeGenError { fn from(value: ParseBoolError) -> Self { - Self::ParseBool("content".to_owned(), value) + Self::new(CodeGenErrorKind::ParseBool("content".to_owned(), value)) } } impl From for CodeGenError { fn from(value: ParseFloatError) -> Self { - Self::ParseFloat("content".to_owned(), value) + Self::new(CodeGenErrorKind::ParseFloat("content".to_owned(), value)) + } +} + +impl From for CodeGenError { + fn from(value: opcua_xml::XmlError) -> Self { + Self::new(value.into()) + } +} + +impl From for CodeGenError { + fn from(value: syn::Error) -> Self { + Self::new(value.into()) } } impl CodeGenError { pub fn io(msg: &str, e: std::io::Error) -> Self { - Self::Io(msg.to_owned(), e) + Self::new(CodeGenErrorKind::Io(msg.to_owned(), e)) + } + + pub fn other(msg: impl Into) -> Self { + Self::new(CodeGenErrorKind::Other(msg.into())) + } + + pub fn parse_int(field: impl Into, error: ParseIntError) -> Self { + Self::new(CodeGenErrorKind::ParseInt(field.into(), error)) + } + + pub fn wrong_format(format: impl Into, value: impl Into) -> Self { + Self::new(CodeGenErrorKind::WrongFormat(format.into(), value.into())) + } + + pub fn missing_required_value(name: &'static str) -> Self { + Self::new(CodeGenErrorKind::MissingRequiredValue(name)) + } + + pub fn with_context(mut self, context: impl Into) -> Self { + self.context = Some(context.into()); + self + } + + pub fn in_file(mut self, file: impl Into) -> Self { + self.file = Some(file.into()); + self + } + + pub fn new(kind: CodeGenErrorKind) -> Self { + Self { + kind: Box::new(kind), + context: None, + file: None, + } } } diff --git a/async-opcua-codegen/src/ids/gen.rs b/async-opcua-codegen/src/ids/gen.rs index a27d2ea2b..8d67cfa5b 100644 --- a/async-opcua-codegen/src/ids/gen.rs +++ b/async-opcua-codegen/src/ids/gen.rs @@ -35,7 +35,7 @@ pub fn parse( let vals: Vec<_> = line.split(",").collect(); if vals.len() == 2 { let Some(type_name) = type_name else { - return Err(CodeGenError::Other(format!("CSV file {file_name} has only two columns, but no type name fallback was specified"))); + return Err(CodeGenError::other(format!("CSV file {file_name} has only two columns, but no type name fallback was specified"))); }; types .entry(type_name.to_owned()) @@ -50,7 +50,7 @@ pub fn parse( .variants .push((vals[1].parse()?, vals[0].to_owned())); } else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "CSV file {file_name} is on incorrect format. Expected two or three columns, got {}", vals.len() ))); diff --git a/async-opcua-codegen/src/lib.rs b/async-opcua-codegen/src/lib.rs index 7b92b3663..8ef91527d 100644 --- a/async-opcua-codegen/src/lib.rs +++ b/async-opcua-codegen/src/lib.rs @@ -116,7 +116,8 @@ pub fn run_codegen(config: &CodeGenConfig, root_path: &str) -> Result<(), CodeGe match target { CodeGenTarget::Types(t) => { println!("Running data type code generation for {}", t.file_path); - let (types, target_namespace) = generate_types(t, root_path)?; + let (types, target_namespace) = + generate_types(t, root_path).map_err(|e| e.in_file(&t.file_path))?; println!("Writing {} types to {}", types.len(), t.output_dir); let header = make_header(&t.file_path, &[&config.extra_header, &t.extra_header]); @@ -132,13 +133,15 @@ pub fn run_codegen(config: &CodeGenConfig, root_path: &str) -> Result<(), CodeGe } } - let modules = write_to_directory(&t.output_dir, root_path, &header, types)?; + let modules = write_to_directory(&t.output_dir, root_path, &header, types) + .map_err(|e| e.in_file(&t.file_path))?; let mut module_file = create_module_file(modules); module_file .items .extend(type_loader_impl(&object_ids, &target_namespace).into_iter()); - write_module_file(&t.output_dir, root_path, &header, module_file)?; + write_module_file(&t.output_dir, root_path, &header, module_file) + .map_err(|e| e.in_file(&t.file_path))?; } CodeGenTarget::Nodes(n) => { println!("Running node set code generation for {}", n.file_path); @@ -148,13 +151,19 @@ pub fn run_codegen(config: &CodeGenConfig, root_path: &str) -> Result<(), CodeGe CodeGenError::io(&format!("Failed to read file {}", n.file_path), e) })?; let node_set = load_nodeset2_file(&node_set)?; - let nodes = node_set.node_set.as_ref().ok_or_else(|| { - CodeGenError::Other("Missing UANodeSet in xml schema".to_owned()) - })?; + let nodes = node_set + .node_set + .as_ref() + .ok_or_else(|| { + CodeGenError::other("Missing UANodeSet in xml schema".to_owned()) + }) + .map_err(|e| e.in_file(&n.file_path))?; println!("Found {} nodes in node set", nodes.nodes.len()); - let chunks = generate_target(n, nodes, &config.preferred_locale, root_path)?; - let module_file = make_root_module(&chunks, n)?; + let chunks = generate_target(n, nodes, &config.preferred_locale, root_path) + .map_err(|e| e.in_file(&n.file_path))?; + let module_file = + make_root_module(&chunks, n).map_err(|e| e.in_file(&n.file_path))?; println!("Writing {} files to {}", chunks.len() + 1, n.output_dir); @@ -176,17 +185,22 @@ pub fn run_codegen(config: &CodeGenConfig, root_path: &str) -> Result<(), CodeGe &format!("Failed to read file {}", n.file_path), e, ) - })?; + }) + .map_err(|e| e.in_file(&n.file_path))?; let node_set = load_nodeset2_file(&node_set)?; p_sets.push((node_set, nodeset_file.import_path.as_str())); } for set in &p_sets { sets.push(( - set.0.node_set.as_ref().ok_or_else(|| { - CodeGenError::Other( - "Missing UANodeSet in dependent xml schema".to_owned(), - ) - })?, + set.0 + .node_set + .as_ref() + .ok_or_else(|| { + CodeGenError::other( + "Missing UANodeSet in dependent xml schema".to_owned(), + ) + }) + .map_err(|e| e.in_file(&n.file_path))?, set.1, )); } @@ -199,19 +213,21 @@ pub fn run_codegen(config: &CodeGenConfig, root_path: &str) -> Result<(), CodeGe &[&config.extra_header, &events_target.extra_header], ); let modules = - write_to_directory(&events_target.output_dir, root_path, &header, events)?; + write_to_directory(&events_target.output_dir, root_path, &header, events) + .map_err(|e| e.in_file(&n.file_path))?; write_module_file( &events_target.output_dir, root_path, &header, create_module_file(modules), - )?; + ) + .map_err(|e| e.in_file(&n.file_path))?; println!("Created {} event types", cnt); } } CodeGenTarget::Ids(n) => { println!("Running node ID code generation for {}", n.file_path); - let gen = generate_node_ids(n, root_path)?; + let gen = generate_node_ids(n, root_path).map_err(|e| e.in_file(&n.file_path))?; let mut file = std::fs::File::options() .create(true) .truncate(true) diff --git a/async-opcua-codegen/src/nodeset/events/collector.rs b/async-opcua-codegen/src/nodeset/events/collector.rs index 39f3c9a2a..bdcfb3d6b 100644 --- a/async-opcua-codegen/src/nodeset/events/collector.rs +++ b/async-opcua-codegen/src/nodeset/events/collector.rs @@ -169,7 +169,7 @@ impl<'a> TypeCollector<'a> { ) -> Result<(), CodeGenError> { // Type must exist, otherwise it's going to cause trouble. let Some(node) = self.nodes.get(type_id) else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Referenced type with id {type_id} not found." ))); }; @@ -227,39 +227,44 @@ impl<'a> TypeCollector<'a> { } let Some(target_node) = self.nodes.get(target) else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Node {target} not found in node dict" - ))); + )) + .with_context(format!("collecting type {type_id}"))); }; let kind = match &target_node.node { UANode::Object(_) => { let Some(type_def) = type_def else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Property {target} is missing type definition" - ))); + )) + .with_context(format!("collecting type {type_id}"))); }; FieldKind::Object(type_def) } UANode::Variable(v) => { let Some(type_def) = type_def else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Property {target} is missing type definition" - ))); + )) + .with_context(format!("collecting type {type_id}"))); }; data_type_id = Some(target_node.lookup_node_id(v.data_type.0.as_str())); FieldKind::Variable(type_def) } UANode::Method(_) => FieldKind::Method, _ => { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Property {target} has unexpected node class" - ))) + )) + .with_context(format!("collecting type {type_id}"))) } }; let browse_name = target_node.node.base().browse_name.0.as_str(); - let (name, _) = split_qualified_name(browse_name)?; + let (name, _) = split_qualified_name(browse_name) + .map_err(|e| e.with_context(format!("collecting type {type_id}")))?; fields.insert( name, diff --git a/async-opcua-codegen/src/nodeset/events/gen.rs b/async-opcua-codegen/src/nodeset/events/gen.rs index 5c480d80f..c787870ac 100644 --- a/async-opcua-codegen/src/nodeset/events/gen.rs +++ b/async-opcua-codegen/src/nodeset/events/gen.rs @@ -41,7 +41,7 @@ impl<'a> EventGenerator<'a> { for (ty, _) in self.types.iter().filter(|t| { matches!(t.1.kind, TypeKind::EventType) && t.1.nodeset_index == self.nodeset_index }) { - self.add_type_to_render(ty, &mut collected)?; + self.add_type_to_render(ty, &mut collected); } let mut items = Vec::new(); @@ -61,45 +61,39 @@ impl<'a> EventGenerator<'a> { && (typ.parent.is_none() || typ.parent.is_some_and(|t| self.is_simple(t))) } - fn add_type_to_render( - &self, - ty: &'a str, - collected: &mut HashMap<&'a str, CollectedType<'a>>, - ) -> Result<(), CodeGenError> { + fn add_type_to_render(&self, ty: &'a str, collected: &mut HashMap<&'a str, CollectedType<'a>>) { if collected.contains_key(ty) { - return Ok(()); + return; } // Don't render the base event type. if ty == "i=2041" { - return Ok(()); + return; } // Don't render simple types. if self.is_simple(ty) { - return Ok(()); + return; } let typ = self.types.get(ty).unwrap(); if typ.nodeset_index != self.nodeset_index { - return Ok(()); + return; } collected.insert(ty, typ.clone()); for field in typ.fields.values() { match field.type_id { FieldKind::Object(r) | FieldKind::Variable(r) => { - self.add_type_to_render(r, collected)? + self.add_type_to_render(r, collected); } FieldKind::Method => (), } } if let Some(parent) = typ.parent { - self.add_type_to_render(parent, collected)?; + self.add_type_to_render(parent, collected); } - - Ok(()) } fn render_type(&self, ty: CollectedType<'a>, id: &'a str) -> Result { @@ -107,15 +101,16 @@ impl<'a> EventGenerator<'a> { TypeKind::EventType => self.render_event(&ty, id), TypeKind::ObjectType => self.render_object_type(&ty), TypeKind::VariableType => self.render_variable_type(&ty), - r => Err(CodeGenError::Other(format!( + r => Err(CodeGenError::other(format!( "Got unexpected type kind to render: {r:?}" ))), } + .map_err(|e| e.with_context(format!("rendering type {}", ty.name))) } fn get_data_type(&self, data_type_id: &str) -> Result { let Some(data_type) = self.types.get(data_type_id) else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Data type {data_type_id} not found for variable" ))); }; @@ -161,7 +156,7 @@ impl<'a> EventGenerator<'a> { let typ = self.types.get(v).unwrap(); if self.is_simple(v) { let data_type_id = field.data_type_id.ok_or_else(|| { - CodeGenError::Other(format!("Missing valid data type for variable {v}")) + CodeGenError::other(format!("Missing valid data type for variable {v}")) })?; self.get_data_type(data_type_id)? @@ -266,7 +261,7 @@ impl<'a> EventGenerator<'a> { if !value_in_parent { let data_type_id = ty.data_type_id.ok_or_else(|| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Missing valid data type for variable type {}", ty.name )) @@ -313,7 +308,7 @@ impl<'a> EventGenerator<'a> { let identifier = format!("{k}{v}"); let opcua_attr = if namespace > 0 { let namespace_uri = self.namespaces.get(namespace as usize).ok_or_else(|| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Namespace index {namespace} is out of range of provided namespace table" )) })?; diff --git a/async-opcua-codegen/src/nodeset/gen.rs b/async-opcua-codegen/src/nodeset/gen.rs index 91e847178..52ed6e700 100644 --- a/async-opcua-codegen/src/nodeset/gen.rs +++ b/async-opcua-codegen/src/nodeset/gen.rs @@ -90,8 +90,24 @@ impl<'a> NodeSetCodeGenerator<'a> { let mut fields = quote! {}; for f in &def.fields { let value = f.value; - let display_name = self.get_localized_text(&f.display_names).render()?; - let description = self.get_localized_text(&f.descriptions).render()?; + let display_name = self + .get_localized_text(&f.display_names) + .render() + .map_err(|e| { + e.with_context(format!( + "rendering field {} in enum definition {}", + f.name, def.name.0 + )) + })?; + let description = self + .get_localized_text(&f.descriptions) + .render() + .map_err(|e| { + e.with_context(format!( + "rendering field {} in enum definition {}", + f.name, def.name.0 + )) + })?; let name = &f.name; fields.extend(quote! { opcua::types::EnumField { @@ -114,14 +130,39 @@ impl<'a> NodeSetCodeGenerator<'a> { let mut fields = quote! {}; let mut any_optional = false; for f in &def.fields { - let description = self.get_localized_text(&f.descriptions).render()?; + let description = self + .get_localized_text(&f.descriptions) + .render() + .map_err(|e| { + e.with_context(format!( + "rendering field {} in structure definition {}", + f.name, def.name.0 + )) + })?; let name = &f.name; - let data_type = self.resolve_node_id(&f.data_type)?; + let data_type = self.resolve_node_id(&f.data_type).map_err(|e| { + e.with_context(format!( + "rendering field {} in structure definition {}", + f.name, def.name.0 + )) + })?; let value_rank = f.value_rank.0; let array_dimensions = self - .parse_array_dimensions(&f.array_dimensions)? + .parse_array_dimensions(&f.array_dimensions) + .map_err(|e| { + e.with_context(format!( + "rendering field {} in structure definition {}", + f.name, def.name.0 + )) + })? .as_ref() - .render()?; + .render() + .map_err(|e| { + e.with_context(format!( + "rendering field {} in structure definition {}", + f.name, def.name.0 + )) + })?; let max_string_length = f.max_string_length as u32; let is_optional = f.is_optional; any_optional |= is_optional; @@ -169,7 +210,7 @@ impl<'a> NodeSetCodeGenerator<'a> { let mut values = Vec::with_capacity(1); for it in dims.0.split(',') { values.push(it.trim().parse::().map_err(|_| { - CodeGenError::Other(format!("Invalid array dimensions: {}", dims.0)) + CodeGenError::other(format!("Invalid array dimensions: {}", dims.0)) })?); } @@ -368,7 +409,12 @@ impl<'a> NodeSetCodeGenerator<'a> { let func_name: Ident = parse_str(&func_name_str)?; self.node_counter += 1; - let references = self.generate_references(node.base())?; + let references = self.generate_references(node.base()).map_err(|e| { + e.with_context(format!( + "generating references for node {}", + node.base().node_id.0 + )) + })?; let node = match &node { UANode::Object(n) => self.generate_object(n), UANode::Variable(n) => self.generate_variable(n), @@ -378,7 +424,8 @@ impl<'a> NodeSetCodeGenerator<'a> { UANode::VariableType(n) => self.generate_variable_type(n), UANode::DataType(n) => self.generate_data_type(n), UANode::ReferenceType(n) => self.generate_reference_type(n), - }?; + } + .map_err(|e| e.with_context(format!("generating node {}", node.base().node_id.0)))?; let func: ItemFn = parse_quote! { #[allow(unused)] diff --git a/async-opcua-codegen/src/nodeset/mod.rs b/async-opcua-codegen/src/nodeset/mod.rs index db94dab1f..ea22831fa 100644 --- a/async-opcua-codegen/src/nodeset/mod.rs +++ b/async-opcua-codegen/src/nodeset/mod.rs @@ -67,7 +67,8 @@ pub fn make_type_dict( let xsd_file = std::fs::read_to_string(format!("{}/{}", root_path, file.file_path)) .map_err(|e| CodeGenError::io(&format!("Failed to read file {}", file.file_path), e))?; let path: Path = parse_str(&file.root_path)?; - let xsd_file = load_xsd_schema(&xsd_file)?; + let xsd_file = load_xsd_schema(&xsd_file) + .map_err(|e| CodeGenError::from(e).in_file(&file.file_path))?; for it in xsd_file.items { let (ty, name) = match it { @@ -159,7 +160,11 @@ pub fn generate_target( let mut fns = Vec::with_capacity(nodes.nodes.len()); for node in &nodes.nodes { - fns.push(generator.generate_item(node)?); + fns.push( + generator + .generate_item(node) + .map_err(|e| e.in_file(&config.file_path))?, + ); } fns.sort_by(|a, b| a.name.cmp(&b.name)); println!("Generated {} node creation methods", fns.len()); diff --git a/async-opcua-codegen/src/nodeset/render.rs b/async-opcua-codegen/src/nodeset/render.rs index d9d8e02b5..86f0c8006 100644 --- a/async-opcua-codegen/src/nodeset/render.rs +++ b/async-opcua-codegen/src/nodeset/render.rs @@ -28,11 +28,11 @@ fn nodeid_regex() -> &'static Regex { pub fn split_node_id(id: &str) -> Result<(&str, &str, u16), CodeGenError> { let captures = nodeid_regex() .captures(id) - .ok_or_else(|| CodeGenError::Other(format!("Invalid nodeId: {}", id)))?; + .ok_or_else(|| CodeGenError::other(format!("Invalid nodeId: {}", id)))?; let namespace = if let Some(ns) = captures.name("ns") { ns.as_str() .parse::() - .map_err(|_| CodeGenError::Other(format!("Invalid nodeId: {}", id)))? + .map_err(|_| CodeGenError::other(format!("Invalid nodeId: {}", id)))? } else { 0 }; @@ -40,7 +40,7 @@ pub fn split_node_id(id: &str) -> Result<(&str, &str, u16), CodeGenError> { let t = captures.name("t").unwrap(); let idf = t.as_str(); if idf.len() < 2 { - Err(CodeGenError::Other(format!("Invalid nodeId: {}", id)))?; + Err(CodeGenError::other(format!("Invalid nodeId: {}", id)))?; } let k = &idf[..2]; let v = &idf[2..]; @@ -57,7 +57,7 @@ impl RenderExpr for NodeId { "i=" => { let i = v .parse::() - .map_err(|_| CodeGenError::Other(format!("Invalid nodeId: {}", id)))?; + .map_err(|_| CodeGenError::other(format!("Invalid nodeId: {}", id)))?; parse_quote! { #i } } "s=" => { @@ -65,17 +65,17 @@ impl RenderExpr for NodeId { } "g=" => { let uuid = uuid::Uuid::parse_str(v) - .map_err(|e| CodeGenError::Other(format!("Invalid nodeId: {}, {e}", id)))?; + .map_err(|e| CodeGenError::other(format!("Invalid nodeId: {}, {e}", id)))?; let bytes = uuid.as_bytes(); parse_quote! { opcua::types::Uuid::from_slice(&[#(#bytes)*,]).unwrap() } } "b=" => { let bytes = base64::engine::general_purpose::STANDARD .decode(v) - .map_err(|e| CodeGenError::Other(format!("Invalid nodeId: {}, {e}", id)))?; + .map_err(|e| CodeGenError::other(format!("Invalid nodeId: {}, {e}", id)))?; parse_quote! { opcua::types::ByteString::from(vec![#(#bytes)*,]) } } - _ => return Err(CodeGenError::Other(format!("Invalid nodeId: {}", id))), + _ => return Err(CodeGenError::other(format!("Invalid nodeId: {}", id))), }; let ns_item = if namespace == 0 { @@ -101,12 +101,12 @@ fn qualified_name_regex() -> &'static Regex { pub fn split_qualified_name(name: &str) -> Result<(&str, u16), CodeGenError> { let captures = qualified_name_regex() .captures(name) - .ok_or_else(|| CodeGenError::Other(format!("Invalid qualifiedname: {}", name)))?; + .ok_or_else(|| CodeGenError::other(format!("Invalid qualifiedname: {}", name)))?; let namespace = if let Some(ns) = captures.name("ns") { ns.as_str() .parse::() - .map_err(|_| CodeGenError::Other(format!("Invalid nodeId: {}", name)))? + .map_err(|_| CodeGenError::other(format!("Invalid nodeId: {}", name)))? } else { 0 }; diff --git a/async-opcua-codegen/src/nodeset/value.rs b/async-opcua-codegen/src/nodeset/value.rs index 3e269830f..fdd794b18 100644 --- a/async-opcua-codegen/src/nodeset/value.rs +++ b/async-opcua-codegen/src/nodeset/value.rs @@ -298,14 +298,14 @@ impl<'a> ValueBuilder<'a> { // Is the type a ListOf type? We don't support that at all in this position, since the standard // doesn't actually define data types for the ListOf items. if ty.starts_with("ListOf") { - return Err(CodeGenError::Other("Got ListOf type inside extension object, this is not supported, use ListOfExtensionObject instead.".to_string())); + return Err(CodeGenError::other("Got ListOf type inside extension object, this is not supported, use ListOfExtensionObject instead.".to_string())); } let Some(typ) = self.types.get(ty) else { - return Err(CodeGenError::Other(format!("Unknown type {ty}"))); + return Err(CodeGenError::other(format!("Unknown type {ty}"))); }; // First, we need to evaluate the type - let type_ref = self.make_type_ref(typ).map_err(CodeGenError::Other)?; + let type_ref = self.make_type_ref(typ).map_err(CodeGenError::other)?; // Now for rendering the type itself, self.render_complex_type(&type_ref, data) @@ -321,7 +321,7 @@ impl<'a> ValueBuilder<'a> { let (ident, _) = safe_ident(e.name); // An enum must have content let Some(val) = &node.text else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Expected value for type, got {node:?}" ))); }; @@ -331,7 +331,7 @@ impl<'a> ValueBuilder<'a> { // value as a number. let val = val .parse::() - .map_err(|e| CodeGenError::ParseInt("Content".to_owned(), e))?; + .map_err(|e| CodeGenError::parse_int("Content".to_owned(), e))?; let path = e.path; Ok(quote! { #path::#ident::from_bits_truncate(#val.into()) @@ -339,7 +339,7 @@ impl<'a> ValueBuilder<'a> { } else { // Else it should be on the form Key_0, parse it let Some(end) = val.rfind("_") else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Invalid enum value: {val}, should be on the form Key_0" ))); }; @@ -382,7 +382,7 @@ impl<'a> ValueBuilder<'a> { .as_ref() .is_some_and(|m| !matches!(m, MaxOccurs::Count(1))); let Some(type_name) = &field.r#type else { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Failed to render field, element {} has no type", name ))); @@ -398,7 +398,7 @@ impl<'a> ValueBuilder<'a> { .get(type_name) .map(|t| self.make_type_ref(t)) .transpose() - .map_err(CodeGenError::Other)?; + .map_err(CodeGenError::other)?; if is_array { let items: Vec<_> = node.children_with_name(name).collect(); @@ -416,7 +416,7 @@ impl<'a> ValueBuilder<'a> { }) } else { let Some(r) = &ty else { - return Err(CodeGenError::Other(format!("Type {type_name} not found"))); + return Err(CodeGenError::other(format!("Type {type_name} not found"))); }; let rendered = self.render_complex_type(r, item)?; it.extend(quote! { @@ -440,7 +440,7 @@ impl<'a> ValueBuilder<'a> { Self::render_primitive(item, type_name) } else { let Some(r) = &ty else { - return Err(CodeGenError::Other(format!("Type {type_name} not found"))); + return Err(CodeGenError::other(format!("Type {type_name} not found"))); }; self.render_complex_type(r, item) } @@ -501,7 +501,7 @@ impl<'a> ValueBuilder<'a> { "Variant" => "Variant", "StatusCode" => "StatusCode", _ => { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "ListOf type {ty} is not supported, use ListOfExtensionObject instead" ))) } @@ -529,7 +529,7 @@ impl<'a> ValueBuilder<'a> { "Guid" => { if let Some(data) = node.child_content("String") { let uuid = uuid::Uuid::parse_str(data).map_err(|e| { - CodeGenError::Other(format!("Failed to parse uuid {data}: {e}")) + CodeGenError::other(format!("Failed to parse uuid {data}: {e}")) })?; let bytes = uuid.as_bytes(); return Ok(quote! { @@ -585,12 +585,12 @@ impl<'a> ValueBuilder<'a> { }); } "Variant" => { - return Err(CodeGenError::Other( + return Err(CodeGenError::other( "Nested variants are not currently supported".to_owned(), )) } "ExtensionObject" => { - return Err(CodeGenError::Other( + return Err(CodeGenError::other( "Nested extensionobjects are not currently supported".to_owned(), )) } @@ -620,7 +620,7 @@ impl<'a> ValueBuilder<'a> { "dateTime" => { let ts = chrono::DateTime::parse_from_rfc3339(data) .map_err(|e| { - CodeGenError::Other(format!("Failed to parse datetime {data}: {e}")) + CodeGenError::other(format!("Failed to parse datetime {data}: {e}")) })? .timestamp_micros(); Ok(quote! { diff --git a/async-opcua-codegen/src/types/gen.rs b/async-opcua-codegen/src/types/gen.rs index f26b72404..5c86913d6 100644 --- a/async-opcua-codegen/src/types/gen.rs +++ b/async-opcua-codegen/src/types/gen.rs @@ -268,7 +268,7 @@ impl CodeGenerator { let value_token = match item.typ { EnumReprType::u8 => { let value: u8 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to u8, {} is out of range", value )) @@ -277,7 +277,7 @@ impl CodeGenerator { } EnumReprType::i16 => { let value: i16 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to i16, {} is out of range", value )) @@ -286,7 +286,7 @@ impl CodeGenerator { } EnumReprType::i32 => { let value: i32 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to i32, {} is out of range", value )) @@ -314,7 +314,7 @@ impl CodeGenerator { let mut impls = Vec::new(); let size: usize = item.size.try_into().map_err(|_| { - CodeGenError::Other(format!("Value {} does not fit in a usize", item.size)) + CodeGenError::other(format!("Value {} does not fit in a usize", item.size)) })?; let write_method = Ident::new(&format!("write_{}", item.typ), Span::call_site()); @@ -455,7 +455,7 @@ impl CodeGenerator { let value_token = match item.typ { EnumReprType::u8 => { let value: u8 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to u8, {} is out of range", value )) @@ -464,7 +464,7 @@ impl CodeGenerator { } EnumReprType::i16 => { let value: i16 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to i16, {} is out of range", value )) @@ -473,7 +473,7 @@ impl CodeGenerator { } EnumReprType::i32 => { let value: i32 = value.try_into().map_err(|_| { - CodeGenError::Other(format!( + CodeGenError::other(format!( "Unexpected error converting to i32, {} is out of range", value )) diff --git a/async-opcua-codegen/src/types/loader.rs b/async-opcua-codegen/src/types/loader.rs index a322d2b87..e95687e45 100644 --- a/async-opcua-codegen/src/types/loader.rs +++ b/async-opcua-codegen/src/types/loader.rs @@ -19,7 +19,7 @@ pub struct BsdTypeLoader { fn strip_first_segment<'a>(val: &'a str, sep: &'static str) -> Result<&'a str, CodeGenError> { val.split_once(sep) - .ok_or_else(|| CodeGenError::WrongFormat(format!("A{sep}B.."), val.to_owned())) + .ok_or_else(|| CodeGenError::wrong_format(format!("A{sep}B.."), val)) .map(|v| v.1) } @@ -75,11 +75,16 @@ impl BsdTypeLoader { for field in item.fields { let field_name = to_snake_case(&field.name); - let type_name = field + let typ = field .type_name - .ok_or(CodeGenError::MissingRequiredValue("TypeName"))?; - let typ = strip_first_segment(&type_name, ":")?; - let typ = self.massage_type_name(typ); + .ok_or(CodeGenError::missing_required_value("TypeName")) + .and_then(|r| Ok(self.massage_type_name(strip_first_segment(&r, ":")?))) + .map_err(|e| { + e.with_context(format!( + "while loading field {} in struct {}", + field_name, item.description.name + )) + })?; if let Some(length_field) = field.length_field { fields_to_add.push(StructureField { @@ -109,7 +114,12 @@ impl BsdTypeLoader { fn load_enum(&self, item: EnumeratedType) -> Result { let Some(len) = item.opaque.length_in_bits else { - return Err(CodeGenError::MissingRequiredValue("LengthInBits")); + return Err( + CodeGenError::missing_required_value("LengthInBits").with_context(format!( + "while loading enum {}", + item.opaque.description.name + )), + ); }; let len_bytes = ((len as f64) / 8.0).ceil() as u64; @@ -119,19 +129,33 @@ impl BsdTypeLoader { 4 => EnumReprType::i32, 8 => EnumReprType::i64, r => { - return Err(CodeGenError::Other(format!( + return Err(CodeGenError::other(format!( "Unexpected enum length. {r} bytes for {}", item.opaque.description.name + )) + .with_context(format!( + "while loading enum {}", + item.opaque.description.name ))) } }; let mut variants = Vec::new(); for val in item.variants { let Some(value) = val.value else { - return Err(CodeGenError::MissingRequiredValue("Value")); + return Err( + CodeGenError::missing_required_value("Value").with_context(format!( + "while loading enum {}", + item.opaque.description.name + )), + ); }; let Some(name) = val.name else { - return Err(CodeGenError::MissingRequiredValue("Name")); + return Err( + CodeGenError::missing_required_value("Name").with_context(format!( + "while loading enum {}", + item.opaque.description.name + )), + ); }; variants.push(EnumValue { name, value }); @@ -274,7 +298,7 @@ impl BsdTypeLoader { }; let typ = type_names .get(typ) - .ok_or_else(|| CodeGenError::Other(format!("Unknown type: {typ}")))?; + .ok_or_else(|| CodeGenError::other(format!("Unknown type: {typ}")))?; let value_rank: Option = field.attribute("ValueRank").and_then(|v| v.parse().ok()); diff --git a/async-opcua-codegen/src/types/mod.rs b/async-opcua-codegen/src/types/mod.rs index 57695708f..3e273aefb 100644 --- a/async-opcua-codegen/src/types/mod.rs +++ b/async-opcua-codegen/src/types/mod.rs @@ -23,7 +23,8 @@ pub fn generate_types( println!("Loading types from {}", target.file_path); let data = std::fs::read_to_string(format!("{}/{}", root_path, &target.file_path)) .map_err(|e| CodeGenError::io(&format!("Failed to read file {}", target.file_path), e))?; - let type_dictionary = load_bsd_file(&data)?; + let type_dictionary = + load_bsd_file(&data).map_err(|e| CodeGenError::from(e).in_file(&target.file_path))?; println!( "Found {} raw elements in the type dictionary.", type_dictionary.elements.len() @@ -39,7 +40,9 @@ pub fn generate_types( type_dictionary, )?; let target_namespace = type_loader.target_namespace(); - let types = type_loader.from_bsd()?; + let types = type_loader + .from_bsd() + .map_err(|e| e.in_file(&target.file_path))?; println!("Generated code for {} types", types.len()); let mut types_import_map = basic_types_import_map();