Not-found lookups return null/false instead of throwing NotFoundException across all services — a missing row is expected control flow, not an exceptional condition. NotFoundException stays for embedded precondition checks inside mutations (missing parent, invalid foreign reference). Guard (copied from mic-check) enforces required arguments at the top of every service method. A ported IModelValidator<T> framework validates every request DTO at the API layer via a new ValidationEndpointFilter, returning a 400 with field-level messages; services re-run the same validator and throw for direct callers that bypass the API. Endpoints translate null/false into 404 via a new ToApiResult() helper. The agent toolset boundary translates the same nullable/bool results into the tool-error text the model already expected.
18 lines
666 B
C#
18 lines
666 B
C#
namespace Novelly.Api.Common.Validation;
|
|
|
|
public static class ValidationResultExtensions
|
|
{
|
|
/// <summary>
|
|
/// The service-level half of "validate again and throw if invalid": callers that reach
|
|
/// a service directly (agent tools, MCP, tests) skip the API's <see cref="ValidationEndpointFilter"/>,
|
|
/// so services re-run the same validator and throw rather than act on bad data.
|
|
/// </summary>
|
|
public static void ThrowIfInvalid(this ValidationResult result)
|
|
{
|
|
if (result.IsInvalid)
|
|
{
|
|
throw new ArgumentException(string.Join("; ", result.Errors.Select(e => $"{e.PropertyName}: {e.Message}")));
|
|
}
|
|
}
|
|
}
|