Skip to content

Commit

Permalink
Various fixes to codegen
Browse files Browse the repository at this point in the history
 - Fix rendering of DateTime<Utc>
 - Fix rendering of Guid
 - Skip rendering type loader if it is empty
 - Properly handle native rust types
 - Support ListOf[Struct] types. It won't work at the top level, but I
   don't think that is actually valid either way.
  • Loading branch information
einarmo committed Jan 24, 2025
1 parent d5afc2c commit 1a93e8c
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 14 deletions.
80 changes: 70 additions & 10 deletions async-opcua-codegen/src/nodeset/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl<'a> ValueBuilder<'a> {
Variant::Guid(v) => {
let bytes = v.as_bytes();
quote::quote! {
opcua::types::Uuid::from_slice(&[#(#bytes),*]).unwrap()
opcua::types::Guid::from_bytes(&[#(#bytes),*])
}
}
Variant::ListOfGuid(v) => {
Expand All @@ -103,7 +103,7 @@ impl<'a> ValueBuilder<'a> {
for it in bytes {
items.extend(quote::quote! {
#items,
opcua::types::Uuid::from_slice(&[#(#it),*]).unwrap(),
opcua::types::Guid::from_bytes(&[#(#it),*]),
});
}
quote::quote! {
Expand Down Expand Up @@ -334,7 +334,7 @@ impl<'a> ValueBuilder<'a> {
.map_err(|e| CodeGenError::parse_int("Content".to_owned(), e))?;
let path = e.path;
Ok(quote! {
#path::#ident::from_bits_truncate(#val.into())
#path::#ident::from_bits_truncate(#val.try_into().unwrap())
})
} else {
// Else it should be on the form Key_0, parse it
Expand Down Expand Up @@ -393,9 +393,10 @@ impl<'a> ValueBuilder<'a> {
type_name
};
let is_primitive = Self::is_primitive(type_name);
let list_type = type_name.strip_prefix("ListOf");
let ty = self
.types
.get(type_name)
.get(list_type.unwrap_or(type_name))
.map(|t| self.make_type_ref(t))
.transpose()
.map_err(CodeGenError::other)?;
Expand All @@ -418,7 +419,11 @@ impl<'a> ValueBuilder<'a> {
let Some(r) = &ty else {
return Err(CodeGenError::other(format!("Type {type_name} not found")));
};
let rendered = self.render_complex_type(r, item)?;
let rendered = if let Some(element_type) = list_type {
self.render_list(r, item, element_type)?
} else {
self.render_complex_type(r, item)?
};
it.extend(quote! {
#rendered,
})
Expand All @@ -442,11 +447,40 @@ impl<'a> ValueBuilder<'a> {
let Some(r) = &ty else {
return Err(CodeGenError::other(format!("Type {type_name} not found")));
};
self.render_complex_type(r, item)
if let Some(element_type) = list_type {
self.render_list(r, item, element_type)
} else {
self.render_complex_type(r, item)
}
}
}
}

fn render_list(
&self,
ty: &TypeRef,
node: &XmlElement,
list_type: &str,
) -> Result<TokenStream, CodeGenError> {
let items: Vec<_> = node.children_with_name(list_type).collect();
if items.is_empty() {
return Ok(quote! {
None
});
}

let mut it = quote! {};
for item in node.children_with_name(list_type) {
let rendered = self.render_complex_type(ty, item)?;
it.extend(quote! {
#rendered,
});
}
Ok(quote! {
Some(vec![#it])
})
}

fn is_primitive(type_name: &str) -> bool {
matches!(
type_name,
Expand All @@ -472,7 +506,33 @@ impl<'a> ValueBuilder<'a> {
| "ExtensionObject"
| "Variant"
| "StatusCode"
) || type_name.starts_with("ListOf")
) || type_name.strip_prefix("ListOf").is_some_and(|e| {
matches!(
e,
"Boolean"
| "SByte"
| "Byte"
| "Int16"
| "UInt16"
| "Int32"
| "UInt32"
| "Int64"
| "UInt64"
| "Float"
| "Double"
| "String"
| "DateTime"
| "Guid"
| "ByteString"
| "QualifiedName"
| "LocalizedText"
| "NodeId"
| "ExpandedNodeId"
| "ExtensionObject"
| "Variant"
| "StatusCode"
)
})
}

fn render_primitive(node: &XmlElement, ty: &str) -> Result<TokenStream, CodeGenError> {
Expand Down Expand Up @@ -533,11 +593,11 @@ impl<'a> ValueBuilder<'a> {
})?;
let bytes = uuid.as_bytes();
return Ok(quote! {
opcua::types::Uuid::from_slice(&[#(#bytes),*]).unwrap()
opcua::types::Guid::from_bytes([#(#bytes),*])
});
} else {
return Ok(quote! {
opcua::types::Uuid::nil()
opcua::types::Guid::nil()
});
}
}
Expand Down Expand Up @@ -624,7 +684,7 @@ impl<'a> ValueBuilder<'a> {
})?
.timestamp_micros();
Ok(quote! {
opcua::types::DateTimeUtc::from_timestamp_micros(#ts).unwrap()
opcua::types::DateTimeUtc::from_timestamp_micros(#ts).unwrap().into()
})
}
"base64Binary" => {
Expand Down
12 changes: 8 additions & 4 deletions async-opcua-codegen/src/types/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ pub struct CodeGenerator {
default_excluded: HashSet<String>,
config: CodeGenItemConfig,
target_namespace: String,
native_types: HashSet<String>,
}

impl CodeGenerator {
pub fn new(
external_import_map: HashMap<String, ExternalType>,
native_types: HashSet<String>,
input: Vec<LoadedType>,
default_excluded: HashSet<String>,
config: CodeGenItemConfig,
Expand Down Expand Up @@ -128,6 +130,7 @@ impl CodeGenerator {
config,
default_excluded,
target_namespace,
native_types,
}
}

Expand Down Expand Up @@ -242,8 +245,12 @@ impl CodeGenerator {
if let Some(ext) = self.import_map.get(name) {
return format!("{}::{}", ext.path, name);
}
// Is it a native type?
if self.native_types.contains(name) {
return name.to_owned();
}
// Assume the type is a builtin.
name.to_string()
format!("opcua::types::{}", name)
}

fn has_default(&self, name: &str) -> bool {
Expand Down Expand Up @@ -595,9 +602,6 @@ impl CodeGenerator {
let mut encoding_ids = None;
// Generate impls
// Has message info
// TODO: This won't work for custom types. It may be possible
// to change `MessageInfo` to return a NodeId, then figure out the
// correct value of that during codegen.
if item
.base_type
.as_ref()
Expand Down
10 changes: 10 additions & 0 deletions async-opcua-codegen/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub fn generate_types(

let generator = CodeGenerator::new(
types_import_map,
[
"bool", "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "f32", "f64", "i32",
]
.into_iter()
.map(|v| v.to_owned())
.collect(),
types,
target.default_excluded.clone(),
CodeGenItemConfig {
Expand All @@ -65,6 +71,10 @@ pub fn generate_types(
}

pub fn type_loader_impl(ids: &[(EncodingIds, String)], namespace: &str) -> Vec<Item> {
if ids.is_empty() {
return Vec::new();
}

let mut ids: Vec<_> = ids.iter().collect();
ids.sort_by(|a, b| a.1.cmp(&b.1));
let mut res = Vec::new();
Expand Down
7 changes: 7 additions & 0 deletions async-opcua-types/src/guid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,11 @@ impl Guid {
uuid: Uuid::from_bytes(bytes),
}
}

/// Creates an UUID from a byte slice of exactly 16 bytes.
pub fn from_slice(bytes: &[u8]) -> Result<Guid, uuid::Error> {
Ok(Guid {
uuid: Uuid::from_slice(bytes)?,
})
}
}

0 comments on commit 1a93e8c

Please sign in to comment.