fix(driver-abcip-cli): resolve Medium code-review findings (Driver.AbCip.Cli-001, -002)
Driver.AbCip.Cli-001: WriteCommand.ParseValue wraps FormatException/ OverflowException as CommandException so bad --value input yields a clean CLI error instead of a raw stack trace. Driver.AbCip.Cli-002: probe/read/subscribe commands reject Structure types up front (RejectStructure helper), matching the write guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,4 +56,19 @@ public abstract class AbCipCommandBase : DriverCommandBase
|
||||
/// multiple gateways in parallel can distinguish the logs.
|
||||
/// </summary>
|
||||
protected string DriverInstanceId => $"abcip-cli-{Gateway}";
|
||||
|
||||
/// <summary>
|
||||
/// Guards against <see cref="AbCipDataType.Structure"/> being passed to a command
|
||||
/// that does not support UDT layouts. Call at the top of <c>ExecuteAsync</c> for any
|
||||
/// command that accepts <c>--type</c> but cannot handle memberless Structure tags.
|
||||
/// Throws a <see cref="CliFx.Exceptions.CommandException"/> if <paramref name="type"/>
|
||||
/// is <see cref="AbCipDataType.Structure"/>.
|
||||
/// </summary>
|
||||
protected static void RejectStructure(AbCipDataType type)
|
||||
{
|
||||
if (type == AbCipDataType.Structure)
|
||||
throw new CliFx.Exceptions.CommandException(
|
||||
"Structure (UDT) reads are out of scope for this command — those need an explicit " +
|
||||
"member layout, which belongs in a real driver config.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class ProbeCommand : AbCipCommandBase
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
RejectStructure(DataType);
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
var probeTag = new AbCipTagDefinition(
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class ReadCommand : AbCipCommandBase
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
RejectStructure(DataType);
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
var tagName = SynthesiseTagName(TagPath, DataType);
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class SubscribeCommand : AbCipCommandBase
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
RejectStructure(DataType);
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
var tagName = ReadCommand.SynthesiseTagName(TagPath, DataType);
|
||||
|
||||
@@ -66,23 +66,40 @@ public sealed class WriteCommand : AbCipCommandBase
|
||||
/// <summary>
|
||||
/// Parse the operator's <c>--value</c> string into the CLR type the driver expects
|
||||
/// for the declared <see cref="AbCipDataType"/>. Invariant culture everywhere.
|
||||
/// Bad input (non-numeric text, out-of-range value) is caught and rethrown as a
|
||||
/// <see cref="CliFx.Exceptions.CommandException"/> so CliFx renders a clean one-line
|
||||
/// error rather than a full .NET stack trace.
|
||||
/// </summary>
|
||||
internal static object ParseValue(string raw, AbCipDataType type) => type switch
|
||||
internal static object ParseValue(string raw, AbCipDataType type)
|
||||
{
|
||||
AbCipDataType.Bool => ParseBool(raw),
|
||||
AbCipDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.DInt or AbCipDataType.Dt => int.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.String => raw,
|
||||
_ => throw new CliFx.Exceptions.CommandException($"Unsupported DataType '{type}' for write."),
|
||||
};
|
||||
try
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
AbCipDataType.Bool => ParseBool(raw),
|
||||
AbCipDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.DInt or AbCipDataType.Dt => int.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture),
|
||||
AbCipDataType.String => raw,
|
||||
_ => throw new CliFx.Exceptions.CommandException($"Unsupported DataType '{type}' for write."),
|
||||
};
|
||||
}
|
||||
catch (Exception ex) when (ex is FormatException or OverflowException)
|
||||
{
|
||||
throw new CliFx.Exceptions.CommandException(
|
||||
$"Cannot parse '{raw}' as {type}. " +
|
||||
$"Check the value is within the valid range for {type} and uses invariant-culture " +
|
||||
$"decimal notation (e.g. '3.14', not '3,14').",
|
||||
innerException: ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseBool(string raw) => raw.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user