fix(flow-debugger): final-review remediation — 2000-row trace pull, shared ExceptionHeader parser, surrogate-safe clip, wording, alarm source, register renumber (I1/I2/M1-M8)
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (final review M1): the ONE parser for the header line of a captured
|
||||
/// exception detail. A .NET <c>Exception.ToString()</c> opens with
|
||||
/// <c>Namespace.Type: message</c>, and a wrapper (an <c>AggregateException</c>, a rethrow)
|
||||
/// chains its inner exceptions on that same first line with <c>" ---> "</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// Two surfaces render that header — the graph node's subtitle (namespace stripped, so the
|
||||
/// badge stays narrow) and the step detail panel's exception block (type in full) — and they
|
||||
/// had drifted: the panel showed the OUTERMOST exception while the node showed the innermost.
|
||||
/// Both now call this, so they always agree on which exception is being named.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class ExceptionHeader
|
||||
{
|
||||
private const string InnerMarker = " ---> ";
|
||||
|
||||
/// <summary>
|
||||
/// Parses the innermost exception's type and message out of <paramref name="detail"/>.
|
||||
/// Takes the first line, follows the <c>" ---> "</c> chain to its LAST segment (the
|
||||
/// innermost exception, which is the one a script author has to act on) and splits it on
|
||||
/// the first <c>": "</c>.
|
||||
/// </summary>
|
||||
/// <param name="detail">The captured detail (a full <c>ToString()</c>), or null.</param>
|
||||
/// <param name="shortenType">
|
||||
/// When true the namespace is stripped from the head of the type name — only the head,
|
||||
/// so a generic arity/argument suffix (<c>`1[System.String]</c>) is kept verbatim.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The type and message; the whole line as the type with an EMPTY message when the line
|
||||
/// carries no <c>": "</c>; null when there is no detail, or nothing left after the chain
|
||||
/// is followed (callers fall back to their own error summary in both those cases).
|
||||
/// </returns>
|
||||
public static (string Type, string Message)? Parse(string? detail, bool shortenType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(detail)) return null;
|
||||
|
||||
var first = detail.Split('\n', 2)[0].TrimEnd('\r').Trim();
|
||||
var inner = first.LastIndexOf(InnerMarker, StringComparison.Ordinal);
|
||||
if (inner >= 0) first = first[(inner + InnerMarker.Length)..].Trim();
|
||||
if (first.Length == 0) return null;
|
||||
|
||||
var split = first.IndexOf(": ", StringComparison.Ordinal);
|
||||
if (split < 0) return (first, string.Empty);
|
||||
|
||||
var type = first[..split];
|
||||
var message = first[(split + 2)..];
|
||||
if (!shortenType) return (type, message);
|
||||
|
||||
// Shorten only the leading namespace: everything from a generic marker on is part of the name.
|
||||
var generic = type.IndexOfAny(['`', '[']);
|
||||
var head = generic < 0 ? type : type[..generic];
|
||||
var dot = head.LastIndexOf('.');
|
||||
if (dot >= 0 && dot < head.Length - 1)
|
||||
{
|
||||
type = head[(dot + 1)..] + (generic < 0 ? string.Empty : type[generic..]);
|
||||
}
|
||||
return (type, message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user