원클릭으로
dck-error-handling
Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when creating, removing, or troubleshooting AHKFlowApp git worktrees across Claude Code, Codex, or Copilot.
Use when optimizing AHKFlowApp agent workflow, worktrees, planning, verification loops, context budget, or tool usage.
Use when verifying AHKFlowApp build, tests, formatting, diagnostics, security, or readiness before commit, push, or PR.
Compact the current conversation into a handoff document for another agent to pick up.
Use to scan .NET code for performance anti-patterns across async, memory, strings, collections, LINQ, regex, and I/O.
Use to evaluate assertion depth and diversity across a test suite and flag assertion-free, shallow, or tautological tests.
| name | dck-error-handling |
| description | Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails. |
Result<T>. Never throw exceptions for not-found, validation, or conflict. These are expected outcomes.ValidatingUseCase<TRequest, TResult> runs before every handler. Handlers never receive invalid requests.result.ToActionResult(this) — From Ardalis.Result.AspNetCore. No manual if/else status code mapping.GlobalExceptionMiddleware handles unhandled exceptions.Use typed factory methods: Result.Success(value), Result.NotFound(), Result.Invalid(errors), Result.Conflict(), Result.Error().
// Application/Queries/GetHotstringHandler.cs
internal sealed class GetHotstringHandler(AppDbContext db)
: IUseCaseHandler<GetHotstringQuery, Result<HotstringDto>>
{
public async Task<Result<HotstringDto>> ExecuteAsync(
GetHotstringQuery request, CancellationToken ct)
{
var entity = await db.Hotstrings.FindAsync([request.Id], ct);
return entity is not null
? Result.Success(new HotstringDto(entity.Id, entity.Trigger, entity.Replacement))
: Result.NotFound();
}
}
// Application/Commands/CreateHotstringHandler.cs
internal sealed class CreateHotstringHandler(AppDbContext db)
: IUseCaseHandler<CreateHotstringCommand, Result<HotstringDto>>
{
public async Task<Result<HotstringDto>> ExecuteAsync(
CreateHotstringCommand request, CancellationToken ct)
{
var exists = await db.Hotstrings.AnyAsync(h => h.Trigger == request.Trigger, ct);
if (exists) return Result.Conflict();
var hotstring = new Hotstring { Trigger = request.Trigger, Replacement = request.Replacement };
db.Hotstrings.Add(hotstring);
await db.SaveChangesAsync(ct);
return Result.Success(new HotstringDto(hotstring.Id, hotstring.Trigger, hotstring.Replacement));
}
}
// API/Controllers/HotstringsController.cs
[ApiController]
[Route("api/v1/[controller]")]
public sealed class HotstringsController(
IUseCase<GetHotstringQuery, Result<HotstringDto>> getHotstring)
: ControllerBase
{
[HttpGet("{id:int}")]
[ProducesResponseType<HotstringDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id, CancellationToken ct)
{
var result = await getHotstring.ExecuteAsync(new GetHotstringQuery(id), ct);
return result.ToActionResult(this); // maps Result status to HTTP automatically
}
}
ToActionResult(this) maps:
Result.Success(value) → 200 OK (or 201 Created for POST)Result.NotFound() → 404Result.Invalid(errors) → 400 with validation errorsResult.Conflict() → 409Result.Error() → 500// Application/Behaviors/ValidatingUseCase.cs
internal sealed class ValidatingUseCase<TRequest, TResult>(
IEnumerable<IValidator<TRequest>> validators,
IUseCaseHandler<TRequest, TResult> inner)
: IUseCase<TRequest, TResult>
where TRequest : notnull
{
public async Task<TResult> ExecuteAsync(
TRequest request,
CancellationToken ct)
{
if (!validators.Any()) return await inner.ExecuteAsync(request, ct);
var context = new ValidationContext<TRequest>(request);
var failures = (await Task.WhenAll(
validators.Select(v => v.ValidateAsync(context, ct))))
.SelectMany(r => r.Errors)
.Where(f => f is not null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await inner.ExecuteAsync(request, ct);
}
}
Register in DI:
// Application DependencyInjection.cs
services.AddScoped(typeof(IUseCase<,>), typeof(ValidatingUseCase<,>));
services.AddUseCase<CreateHotstringCommand, Result<HotstringDto>, CreateHotstringCommandHandler>();
services.AddValidatorsFromAssembly(typeof(CreateHotstringValidator).Assembly);
// Application/Commands/CreateHotstringValidator.cs
public sealed class CreateHotstringValidator : AbstractValidator<CreateHotstringCommand>
{
public CreateHotstringValidator()
{
RuleFor(x => x.Trigger)
.NotEmpty().WithMessage("Trigger is required")
.MaximumLength(50).WithMessage("Trigger must be 50 characters or less");
RuleFor(x => x.Replacement)
.NotEmpty().WithMessage("Replacement is required")
.MaximumLength(500).WithMessage("Replacement must be 500 characters or less");
}
}
Catches unexpected exceptions and returns RFC 9457 ProblemDetails.
// API/Middleware/GlobalExceptionMiddleware.cs
public sealed class GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, "Unhandled exception for {Method} {Path}",
context.Request.Method, context.Request.Path);
var problem = new ProblemDetails
{
Title = "An unexpected error occurred",
Status = StatusCodes.Status500InternalServerError,
Type = "https://tools.ietf.org/html/rfc9110#section-15.6.1"
};
context.Response.StatusCode = problem.Status!.Value;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(problem);
}
}
}
// Registration in Program.cs
app.UseMiddleware<GlobalExceptionMiddleware>();
// BAD — rolling your own Result type
public class Result<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public List<string> Errors { get; }
}
// GOOD — use Ardalis.Result package
// dotnet add package Ardalis.Result
// dotnet add package Ardalis.Result.AspNetCore
// BAD — ValidationFilter as endpoint filter (Minimal API pattern)
group.MapPost("/", CreateHotstring)
.AddEndpointFilter<ValidationFilter<CreateHotstringCommand>>();
// GOOD — FluentValidation through ValidatingUseCase<TRequest, TResult>
// ValidatingUseCase<TRequest, TResult> runs automatically for all registered commands/queries
// BAD — manual if/else status code mapping
var result = await createHotstring.ExecuteAsync(command, ct);
if (result.IsSuccess) return Ok(result.Value);
if (result.Status == ResultStatus.NotFound) return NotFound();
return BadRequest();
// GOOD — ToActionResult does it automatically
return result.ToActionResult(this);
// BAD — exception for expected outcome
var hotstring = await db.Hotstrings.FindAsync(id)
?? throw new NotFoundException($"Hotstring {id} not found");
// GOOD — Ardalis.Result
var entity = await db.Hotstrings.FindAsync([id], ct);
return entity is not null ? Result.Success(...) : Result.NotFound();
// BAD — inconsistent error format
return BadRequest("Something went wrong");
return BadRequest(new { error = "Invalid input" });
// GOOD — ProblemDetails via ToActionResult (automatic) or GlobalExceptionMiddleware
// BAD
try { await ProcessAsync(ct); }
catch (Exception) { /* ignore */ }
// GOOD — log and return Result.Error()
try { await ProcessAsync(ct); }
catch (ExternalApiException ex)
{
logger.LogWarning(ex, "External API failed");
return Result.Error("External service unavailable");
}
| Scenario | Approach |
|---|---|
| Entity not found | Result.NotFound() |
| Input invalid | Result.Invalid(errors) from handlers, or ValidationException through ValidatingUseCase |
| Duplicate / conflict | Result.Conflict() |
| External service failure | Catch specific exception, return Result.Error() |
| Unhandled crash | GlobalExceptionMiddleware → ProblemDetails 500 |
| Controller HTTP mapping | result.ToActionResult(this) — always |
| Validation runs where | ValidatingUseCase<TRequest, TResult> — before handler, never in handler |