fix(cli): resolve CLI-002..007 — robust response rendering, URL/JSON arg validation, credential env-vars, doc refresh
This commit is contained in:
@@ -31,14 +31,23 @@ internal static class CommandHelpers
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate credentials
|
||||
var username = result.GetValue(usernameOption);
|
||||
var password = result.GetValue(passwordOption);
|
||||
if (!IsValidManagementUrl(url))
|
||||
{
|
||||
OutputFormatter.WriteError(
|
||||
$"Invalid management URL '{url}'. Expected an absolute http/https URL (e.g. http://localhost:9001).",
|
||||
"INVALID_URL");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve credentials: command-line options take precedence, then the
|
||||
// SCADALINK_USERNAME / SCADALINK_PASSWORD environment variables.
|
||||
var username = ResolveCredential(result.GetValue(usernameOption), config.Username);
|
||||
var password = ResolveCredential(result.GetValue(passwordOption), config.Password);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
OutputFormatter.WriteError(
|
||||
"Credentials required. Use --username and --password options.",
|
||||
"Credentials required. Use --username/--password or set SCADALINK_USERNAME/SCADALINK_PASSWORD.",
|
||||
"NO_CREDENTIALS");
|
||||
return 1;
|
||||
}
|
||||
@@ -74,10 +83,40 @@ internal static class CommandHelpers
|
||||
return string.IsNullOrWhiteSpace(config.DefaultFormat) ? "json" : config.DefaultFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a single credential: an explicit command-line value wins, otherwise the
|
||||
/// environment-variable fallback (from <see cref="CliConfig"/>) is used.
|
||||
/// </summary>
|
||||
internal static string? ResolveCredential(string? commandLineValue, string? envValue)
|
||||
=> string.IsNullOrWhiteSpace(commandLineValue) ? envValue : commandLineValue;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a management URL is an absolute http/https URL. A malformed URL
|
||||
/// (missing scheme, empty, or a non-http scheme) would otherwise reach
|
||||
/// <c>new Uri(...)</c> in the <see cref="ManagementHttpClient"/> constructor and throw
|
||||
/// an unhandled <see cref="UriFormatException"/>.
|
||||
/// </summary>
|
||||
internal static bool IsValidManagementUrl(string? url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
return false;
|
||||
|
||||
return Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
}
|
||||
|
||||
internal static int HandleResponse(ManagementResponse response, string format)
|
||||
{
|
||||
if (response.JsonData != null)
|
||||
{
|
||||
// A success status with an empty/whitespace body (e.g. a 204 from a delete)
|
||||
// is a "command succeeded, no output" case — do not attempt to parse it.
|
||||
if (string.IsNullOrWhiteSpace(response.JsonData))
|
||||
{
|
||||
Console.WriteLine("(ok)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (string.Equals(format, "table", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
WriteAsTable(response.JsonData);
|
||||
@@ -98,46 +137,62 @@ internal static class CommandHelpers
|
||||
|
||||
private static void WriteAsTable(string json)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Array)
|
||||
JsonDocument doc;
|
||||
try
|
||||
{
|
||||
var items = root.EnumerateArray().ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
Console.WriteLine("(no results)");
|
||||
return;
|
||||
}
|
||||
doc = JsonDocument.Parse(json);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// The server returned a success status but a non-JSON body (e.g. a proxy
|
||||
// HTML error page, or a plain-text message). Print it verbatim rather than
|
||||
// crashing — mirrors the raw-body fallback on the JSON path.
|
||||
Console.WriteLine(json);
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = items[0].ValueKind == JsonValueKind.Object
|
||||
? items[0].EnumerateObject().Select(p => p.Name).ToArray()
|
||||
: new[] { "Value" };
|
||||
using (doc)
|
||||
{
|
||||
var root = doc.RootElement;
|
||||
|
||||
var rows = items.Select(item =>
|
||||
if (root.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
if (item.ValueKind == JsonValueKind.Object)
|
||||
var items = root.EnumerateArray().ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return headers.Select(h =>
|
||||
item.TryGetProperty(h, out var val)
|
||||
? val.ValueKind == JsonValueKind.Null ? "" : val.ToString()
|
||||
: "").ToArray();
|
||||
Console.WriteLine("(no results)");
|
||||
return;
|
||||
}
|
||||
return new[] { item.ToString() };
|
||||
});
|
||||
|
||||
OutputFormatter.WriteTable(rows, headers);
|
||||
}
|
||||
else if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var headers = new[] { "Property", "Value" };
|
||||
var rows = root.EnumerateObject().Select(p =>
|
||||
new[] { p.Name, p.Value.ValueKind == JsonValueKind.Null ? "" : p.Value.ToString() });
|
||||
OutputFormatter.WriteTable(rows, headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(root.ToString());
|
||||
var headers = items[0].ValueKind == JsonValueKind.Object
|
||||
? items[0].EnumerateObject().Select(p => p.Name).ToArray()
|
||||
: new[] { "Value" };
|
||||
|
||||
var rows = items.Select(item =>
|
||||
{
|
||||
if (item.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
return headers.Select(h =>
|
||||
item.TryGetProperty(h, out var val)
|
||||
? val.ValueKind == JsonValueKind.Null ? "" : val.ToString()
|
||||
: "").ToArray();
|
||||
}
|
||||
return new[] { item.ToString() };
|
||||
});
|
||||
|
||||
OutputFormatter.WriteTable(rows, headers);
|
||||
}
|
||||
else if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var headers = new[] { "Property", "Value" };
|
||||
var rows = root.EnumerateObject().Select(p =>
|
||||
new[] { p.Name, p.Value.ValueKind == JsonValueKind.Null ? "" : p.Value.ToString() });
|
||||
OutputFormatter.WriteTable(rows, headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(root.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user