Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 198 additions & 114 deletions Stack/Opc.Ua.Core/Types/BuiltIn/ExpandedNodeId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -622,23 +622,216 @@ public static ExpandedNodeId Parse(
/// <exception cref="ServiceResultException">Thrown under a variety of circumstances, each time with a specific message.</exception>
public static ExpandedNodeId Parse(string text)
{
if (!InternalTryParse(text, out ExpandedNodeId value, out string errorMessage))
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
errorMessage ?? Utils.Format("Cannot parse expanded node id text: '{0}'", text));
}
return value;
}

/// <summary>
/// Tries to parse an expanded node id string and returns true if successful.
/// </summary>
/// <remarks>
/// Tries to parse an ExpandedNodeId String and returns an ExpandedNodeId object if successful.
/// </remarks>
/// <param name="text">The ExpandedNodeId value as a string.</param>
/// <param name="value">The parsed ExpandedNodeId if successful, otherwise ExpandedNodeId.Null.</param>
/// <returns>True if the parsing was successful, false otherwise.</returns>
public static bool TryParse(string text, out ExpandedNodeId value)
{
return InternalTryParse(text, out value, out _);
}

/// <summary>
/// Internal try parse method that returns error message on failure.
/// </summary>
/// <param name="text">The ExpandedNodeId value as string.</param>
/// <param name="value">The parsed ExpandedNodeId if successful, otherwise ExpandedNodeId.Null.</param>
/// <param name="errorMessage">Error message if parsing fails.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
private static bool InternalTryParse(string text, out ExpandedNodeId value, out string errorMessage)
{
errorMessage = null;
value = Null;

try
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please also rework this try/catch to use a TryParse model instead of creating the ExpandedNodeId with the text to parse and catching any errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored the ExpandedNodeId parsing in commit 38c62d6. The static InternalTryParse method now directly parses the server index, namespace URI, and inner NodeId components without creating an ExpandedNodeId object in a try-catch block.

Also updated the instance InternalParse method to use a new InternalTryParseInstance helper that implements the TryParse pattern. InternalParse now calls this helper and throws appropriate exceptions (ArgumentException or ServiceResultException) based on the error type.

All 1643 BuiltInType tests pass.

{
// check for null.
if (string.IsNullOrEmpty(text))
{
return Null;
value = Null;
return true;
}

return new ExpandedNodeId(text);
value = new ExpandedNodeId(text);
return true;
}
catch (Exception e)
{
errorMessage = Utils.Format("Cannot parse expanded node id text: '{0}': {1}", text, e.Message);
value = Null;
return false;
}
}

/// <summary>
/// Tries to parse an ExpandedNodeId formatted as a string and converts it to an ExpandedNodeId.
/// </summary>
/// <param name="context">The current context.</param>
/// <param name="text">The text to parse.</param>
/// <param name="value">The parsed ExpandedNodeId if successful, otherwise ExpandedNodeId.Null.</param>
/// <param name="options">The options to use when parsing the ExpandedNodeId.</param>
/// <returns>True if the parsing was successful, false otherwise.</returns>
public static bool TryParse(
IServiceMessageContext context,
string text,
out ExpandedNodeId value,
NodeIdParsingOptions options = null)
{
return InternalTryParseWithContext(context, text, options, out value, out _);
}

/// <summary>
/// Parses an ExpandedNodeId formatted as a string and converts it a local NodeId.
/// </summary>
/// <param name="context">The current context,</param>
/// <param name="text">The text to parse.</param>
/// <param name="options">The options to use when parsing the ExpandedNodeId.</param>
/// <returns>The local identifier.</returns>
/// <exception cref="ServiceResultException">Thrown if the namespace URI is not in the namespace table.</exception>
public static ExpandedNodeId Parse(
IServiceMessageContext context,
string text,
NodeIdParsingOptions options = null)
{
if (!InternalTryParseWithContext(context, text, options, out ExpandedNodeId value, out string errorMessage))
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
Utils.Format("Cannot parse expanded node id text: '{0}'", text),
e);
errorMessage ?? Utils.Format("Cannot parse expanded node id text: '{0}'", text));
}
return value;
}

/// <summary>
/// Internal try parse method with context that returns error message on failure.
/// </summary>
/// <param name="context">The current context.</param>
/// <param name="text">The text to parse.</param>
/// <param name="options">The options to use when parsing the ExpandedNodeId.</param>
/// <param name="value">The parsed ExpandedNodeId if successful, otherwise ExpandedNodeId.Null.</param>
/// <param name="errorMessage">Error message if parsing fails.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
private static bool InternalTryParseWithContext(
IServiceMessageContext context,
string text,
NodeIdParsingOptions options,
out ExpandedNodeId value,
out string errorMessage)
{
errorMessage = null;
value = Null;

if (string.IsNullOrEmpty(text))
{
value = Null;
return true;
}

string originalText = text;
int serverIndex = 0;

if (text.StartsWith("svu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
errorMessage = Utils.Format("Invalid ExpandedNodeId ({0}).", originalText);
return false;
}

string serverUri = Utils.UnescapeUri(text.AsSpan()[4..index]);
serverIndex =
options?.UpdateTables == true
? context.ServerUris.GetIndexOrAppend(serverUri)
: context.ServerUris.GetIndex(serverUri);

if (serverIndex < 0)
{
errorMessage = Utils.Format("No mapping to ServerIndex for ServerUri ({0}).", serverUri);
return false;
}

text = text[(index + 1)..];
}

if (text.StartsWith("svr=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
errorMessage = Utils.Format("Invalid ExpandedNodeId ({0}).", originalText);
return false;
}

if (ushort.TryParse(text[4..index], out ushort ns))
{
serverIndex = ns;

if (options.ServerMappings != null && options?.NamespaceMappings.Length < ns)
{
serverIndex = options.NamespaceMappings[ns];
}
}

text = text[(index + 1)..];
}

int namespaceIndex = 0;
string namespaceUri = null;

if (text.StartsWith("nsu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
errorMessage = Utils.Format("Invalid ExpandedNodeId ({0}).", originalText);
return false;
}

namespaceUri = Utils.UnescapeUri(text[4..index]);
namespaceIndex =
options?.UpdateTables == true
? context.NamespaceUris.GetIndexOrAppend(namespaceUri)
: context.NamespaceUris.GetIndex(namespaceUri);

text = text[(index + 1)..];
}

if (!NodeId.InternalTryParseWithContext(context, text, options, out NodeId nodeId, out errorMessage))
{
return false;
}

if (namespaceIndex > 0)
{
value = new ExpandedNodeId(
nodeId.Identifier,
(ushort)namespaceIndex,
null,
(uint)serverIndex);
}
else
{
value = new ExpandedNodeId(nodeId, namespaceUri, (uint)serverIndex);
}

return true;
}

/// <summary>
Expand Down Expand Up @@ -982,115 +1175,6 @@ internal void SetServerIndex(uint serverIndex)
ServerIndex = serverIndex;
}

/// <summary>
/// Parses an ExpandedNodeId formatted as a string and converts it a local NodeId.
/// </summary>
/// <param name="context">The current context,</param>
/// <param name="text">The text to parse.</param>
/// <param name="options">The options to use when parsing the ExpandedNodeId.</param>
/// <returns>The local identifier.</returns>
/// <exception cref="ServiceResultException">Thrown if the namespace URI is not in the namespace table.</exception>
public static ExpandedNodeId Parse(
IServiceMessageContext context,
string text,
NodeIdParsingOptions options = null)
{
if (string.IsNullOrEmpty(text))
{
return Null;
}

string originalText = text;
int serverIndex = 0;

if (text.StartsWith("svu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
$"Invalid ExpandedNodeId ({originalText}).");
}

string serverUri = Utils.UnescapeUri(text.AsSpan()[4..index]);
serverIndex =
options?.UpdateTables == true
? context.ServerUris.GetIndexOrAppend(serverUri)
: context.ServerUris.GetIndex(serverUri);

if (serverIndex < 0)
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
$"No mapping to ServerIndex for ServerUri ({serverUri}).");
}

text = text[(index + 1)..];
}

if (text.StartsWith("svr=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
$"Invalid ExpandedNodeId ({originalText}).");
}

if (ushort.TryParse(text[4..index], out ushort ns))
{
serverIndex = ns;

if (options.ServerMappings != null && options?.NamespaceMappings.Length < ns)
{
serverIndex = options.NamespaceMappings[ns];
}
}

text = text[(index + 1)..];
}

int namespaceIndex = 0;
string namespaceUri = null;

if (text.StartsWith("nsu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(
StatusCodes.BadNodeIdInvalid,
$"Invalid ExpandedNodeId ({originalText}).");
}

namespaceUri = Utils.UnescapeUri(text[4..index]);
namespaceIndex =
options?.UpdateTables == true
? context.NamespaceUris.GetIndexOrAppend(namespaceUri)
: context.NamespaceUris.GetIndex(namespaceUri);

text = text[(index + 1)..];
}

var nodeId = NodeId.Parse(context, text, options);

if (namespaceIndex > 0)
{
return new ExpandedNodeId(
nodeId.Identifier,
(ushort)namespaceIndex,
null,
(uint)serverIndex);
}

return new ExpandedNodeId(nodeId, namespaceUri, (uint)serverIndex);
}

/// <summary>
/// Formats a NodeId as a string.
/// </summary>
Expand Down
Loading
Loading