up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
211
src/Cli/StellaOps.Cli/Output/CliErrorRenderer.cs
Normal file
211
src/Cli/StellaOps.Cli/Output/CliErrorRenderer.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.Text.Json;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace StellaOps.Cli.Output;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for rendering CLI errors consistently.
|
||||
/// CLI-SDK-62-002: Provides standardized error output with error.code and trace_id.
|
||||
/// </summary>
|
||||
internal static class CliErrorRenderer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Renders an error to the console.
|
||||
/// </summary>
|
||||
public static void Render(CliError error, bool verbose = false, bool asJson = false)
|
||||
{
|
||||
if (asJson)
|
||||
{
|
||||
RenderJson(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderConsole(error, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders an error as JSON.
|
||||
/// </summary>
|
||||
public static void RenderJson(CliError error)
|
||||
{
|
||||
var output = new
|
||||
{
|
||||
error = new
|
||||
{
|
||||
code = error.Code,
|
||||
message = error.Message,
|
||||
detail = error.Detail,
|
||||
target = error.Target,
|
||||
help_url = error.HelpUrl,
|
||||
retry_after = error.RetryAfter,
|
||||
metadata = error.Metadata
|
||||
},
|
||||
trace_id = error.TraceId,
|
||||
request_id = error.RequestId,
|
||||
exit_code = error.ExitCode
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(output, JsonOptions);
|
||||
AnsiConsole.WriteLine(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders an error to the console with formatting.
|
||||
/// </summary>
|
||||
public static void RenderConsole(CliError error, bool verbose = false)
|
||||
{
|
||||
// Main error message
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(error.Message)}");
|
||||
|
||||
// Error code
|
||||
AnsiConsole.MarkupLine($"[grey]Code:[/] {Markup.Escape(error.Code)}");
|
||||
|
||||
// Detail (if present)
|
||||
if (!string.IsNullOrWhiteSpace(error.Detail))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Detail:[/] {Markup.Escape(error.Detail)}");
|
||||
}
|
||||
|
||||
// Target (if present)
|
||||
if (!string.IsNullOrWhiteSpace(error.Target))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Target:[/] {Markup.Escape(error.Target)}");
|
||||
}
|
||||
|
||||
// Help URL (if present)
|
||||
if (!string.IsNullOrWhiteSpace(error.HelpUrl))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Help:[/] [link]{Markup.Escape(error.HelpUrl)}[/]");
|
||||
}
|
||||
|
||||
// Retry-after (if present)
|
||||
if (error.RetryAfter.HasValue)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Retry after:[/] {error.RetryAfter} seconds");
|
||||
}
|
||||
|
||||
// Trace/Request IDs (shown in verbose mode or always for debugging)
|
||||
if (verbose || !string.IsNullOrWhiteSpace(error.TraceId))
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
if (!string.IsNullOrWhiteSpace(error.TraceId))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Trace ID:[/] {Markup.Escape(error.TraceId)}");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(error.RequestId))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Request ID:[/] {Markup.Escape(error.RequestId)}");
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata (shown in verbose mode)
|
||||
if (verbose && error.Metadata is not null && error.Metadata.Count > 0)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[grey]Metadata:[/]");
|
||||
foreach (var (key, value) in error.Metadata)
|
||||
{
|
||||
AnsiConsole.MarkupLine($" [grey]{Markup.Escape(key)}:[/] {Markup.Escape(value)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders a simple error message.
|
||||
/// </summary>
|
||||
public static void RenderSimple(string message, string? code = null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(message)}");
|
||||
if (!string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]Code:[/] {Markup.Escape(code)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders a warning message.
|
||||
/// </summary>
|
||||
public static void RenderWarning(string message)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Warning:[/] {Markup.Escape(message)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders scope guidance for invalid_scope errors.
|
||||
/// </summary>
|
||||
public static void RenderScopeGuidance(CliError error)
|
||||
{
|
||||
if (!error.Code.Contains("SCOPE", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[yellow]The requested operation requires additional OAuth scopes.[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("To resolve this issue:");
|
||||
AnsiConsole.MarkupLine(" 1. Check your CLI profile configuration: [cyan]stella profile show[/]");
|
||||
AnsiConsole.MarkupLine(" 2. Update your profile with required scopes: [cyan]stella profile edit <name>[/]");
|
||||
AnsiConsole.MarkupLine(" 3. Request additional scopes from your administrator");
|
||||
AnsiConsole.MarkupLine(" 4. Re-authenticate with the updated scopes: [cyan]stella auth login[/]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders rate limit guidance.
|
||||
/// </summary>
|
||||
public static void RenderRateLimitGuidance(CliError error)
|
||||
{
|
||||
if (!error.Code.Contains("RATE_LIMIT", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[yellow]Rate limit exceeded.[/]");
|
||||
|
||||
if (error.RetryAfter.HasValue)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"Please wait [cyan]{error.RetryAfter}[/] seconds before retrying.");
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine("Please wait before retrying your request.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders authentication guidance.
|
||||
/// </summary>
|
||||
public static void RenderAuthGuidance(CliError error)
|
||||
{
|
||||
if (!error.Code.StartsWith("ERR_AUTH_", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
if (error.Code == CliErrorCodes.Unauthorized)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Authentication required.[/]");
|
||||
AnsiConsole.MarkupLine("Please authenticate using: [cyan]stella auth login[/]");
|
||||
}
|
||||
else if (error.Code == CliErrorCodes.Forbidden)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Access denied.[/]");
|
||||
AnsiConsole.MarkupLine("You do not have permission to perform this operation.");
|
||||
AnsiConsole.MarkupLine("Contact your administrator to request access.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders contextual guidance based on error code.
|
||||
/// </summary>
|
||||
public static void RenderGuidance(CliError error)
|
||||
{
|
||||
RenderScopeGuidance(error);
|
||||
RenderRateLimitGuidance(error);
|
||||
RenderAuthGuidance(error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user