Enhance enum definition handling and validation (#3124)

* Enhance enum definition handling and validation

Added a `string enumTypeName` parameter to the `ToEnumDefinition` methods in `ComplexTypeSystem.cs` and `DataTypeDefinitionExtension.cs` to improve enum field name generation, especially when original names are null or empty. Implemented a fallback mechanism to ensure robust naming.

In `ComplexTypeBuilder.cs`, added null checks for `typeName` to prevent runtime errors and ensure valid type definitions. Updated logic for defining enum literals to handle empty field names, ensuring unique names are generated based on `typeName` and enum value.

* Update Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Libraries/Opc.Ua.Client.ComplexTypes/DataTypeDefinitionExtension.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix issue from code review

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Marc Schier
2025-07-24 21:16:06 +02:00
committed by GitHub
co-authored by Copilot
parent bd6c91d6df
commit eeb58c2fa6
3 changed files with 84 additions and 18 deletions
@@ -899,7 +899,7 @@ namespace Opc.Ua.Client.ComplexTypes
if (item is Schema.Binary.EnumeratedType enumeratedObject)
{
// 1. use Dictionary entry
var enumDefinition = enumeratedObject.ToEnumDefinition();
var enumDefinition = enumeratedObject.ToEnumDefinition(enumeratedObject.Name);
// Add EnumDefinition to cache
m_dataTypeDefinitionCache[enumType.NodeId] = enumDefinition;
@@ -971,12 +971,12 @@ namespace Opc.Ua.Client.ComplexTypes
if (enumTypeArray is ExtensionObject[] extensionObject)
{
// 2. use EnumValues
enumDefinition = extensionObject.ToEnumDefinition();
enumDefinition = extensionObject.ToEnumDefinition(name.Name);
}
else if (enumTypeArray is LocalizedText[] localizedText)
{
// 3. use EnumStrings
enumDefinition = localizedText.ToEnumDefinition();
enumDefinition = localizedText.ToEnumDefinition(name.Name);
}
else
{
@@ -237,20 +237,33 @@ namespace Opc.Ua.Client.ComplexTypes
/// Convert a binary schema enumerated type to an enum data type definition
/// Available before OPC UA V1.04.
/// </summary>
public static EnumDefinition ToEnumDefinition(this Schema.Binary.EnumeratedType enumeratedType)
public static EnumDefinition ToEnumDefinition(this Schema.Binary.EnumeratedType enumeratedType, string enumTypeName)
{
var enumDefinition = new EnumDefinition();
foreach (var enumValue in enumeratedType.EnumeratedValue)
if (enumeratedType.EnumeratedValue != null)
{
var enumTypeField = new EnumField
foreach (var enumValue in enumeratedType.EnumeratedValue)
{
Name = enumValue.Name,
Value = enumValue.Value,
Description = enumValue.Documentation?.Text?.FirstOrDefault(),
DisplayName = enumValue.Name
};
enumDefinition.Fields.Add(enumTypeField);
var fieldName = enumValue.Name;
if (string.IsNullOrEmpty(fieldName))
{
if (string.IsNullOrEmpty(enumTypeName))
{
// Here we give up because the overall type is broken
return null;
}
fieldName = $"{enumTypeName}_{enumValue.Value}";
}
var enumTypeField = new EnumField {
Name = fieldName,
Value = enumValue.Value,
Description = enumValue.Documentation?.Text?.FirstOrDefault(),
DisplayName = enumValue.Name
};
enumDefinition.Fields.Add(enumTypeField);
}
}
return enumDefinition;
@@ -260,14 +273,30 @@ namespace Opc.Ua.Client.ComplexTypes
/// Convert a list of EnumValues to an enum data type definition
/// Available before OPC UA V1.04.
/// </summary>
public static EnumDefinition ToEnumDefinition(this ExtensionObject[] enumValueTypes)
public static EnumDefinition ToEnumDefinition(this ExtensionObject[] enumValueTypes, string enumTypeName)
{
var enumDefinition = new EnumDefinition();
foreach (var extensionObject in enumValueTypes)
{
var enumValue = extensionObject.Body as EnumValueType;
if (extensionObject.Body is not EnumValueType enumValue)
{
// All we can do here is skip this value. Since there is no
// fallback it is better to include all other type fields if
// they are in the extension object array.
continue;
}
var name = enumValue.DisplayName.Text;
if (string.IsNullOrEmpty(name))
{
if (string.IsNullOrEmpty(enumTypeName))
{
// Here we give up because the overall type is broken
return null;
}
name = $"{enumTypeName}_{enumValue.Value}";
}
var enumTypeField = new EnumField {
Name = name,
@@ -284,7 +313,7 @@ namespace Opc.Ua.Client.ComplexTypes
/// Convert a list of EnumValues to an enum data type definition
/// Available before OPC UA V1.04.
/// </summary>
public static EnumDefinition ToEnumDefinition(this LocalizedText[] enumFieldNames)
public static EnumDefinition ToEnumDefinition(this LocalizedText[] enumFieldNames, string enumTypeName)
{
var enumDefinition = new EnumDefinition();
@@ -293,6 +322,16 @@ namespace Opc.Ua.Client.ComplexTypes
LocalizedText enumFieldName = enumFieldNames[ii];
var name = enumFieldName.Text;
if (string.IsNullOrEmpty(name))
{
if (string.IsNullOrEmpty(enumTypeName))
{
// Here we give up because the overall type is broken
return null;
}
name = $"{enumTypeName}_{ii}";
}
var enumTypeField = new EnumField {
Name = name,
Value = ii,
@@ -28,6 +28,7 @@
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -79,16 +80,42 @@ namespace Opc.Ua.Client.ComplexTypes
{
throw new ArgumentNullException(nameof(enumDefinition));
}
if (typeName == null)
{
throw new ArgumentNullException(nameof(typeName));
}
if (string.IsNullOrEmpty(typeName.Name))
{
// The type name should not be null or empty then the type definition
// or xml is broken. For the latter we want to go into the fallback path
// And try to read the data type definition's enum definition.
return null;
}
var enumBuilder = m_moduleBuilder.DefineEnum(
GetFullQualifiedTypeName(typeName),
TypeAttributes.Public,
typeof(int));
enumBuilder.DataContractAttribute(m_targetNamespace);
foreach (var enumValue in enumDefinition.Fields)
if (enumDefinition.Fields != null)
{
var newEnum = enumBuilder.DefineLiteral(enumValue.Name, (int)enumValue.Value);
newEnum.EnumMemberAttribute(enumValue.Name, (int)enumValue.Value);
var fieldNames = new HashSet<string>();
foreach (EnumField enumValue in enumDefinition.Fields)
{
// Create a field from the type name and ensure it is not a duplicate
var fieldName = enumValue.Name;
if (string.IsNullOrEmpty(fieldName))
{
// This is to be super safe, but we should never get here.
fieldName = $"{typeName.Name}_{enumValue.Value}";
}
if (fieldNames.Add(fieldName))
{
var newEnum = enumBuilder.DefineLiteral(fieldName, (int)enumValue.Value);
newEnum.EnumMemberAttribute(fieldName, (int)enumValue.Value);
}
}
}
return enumBuilder.CreateTypeInfo();
}