Every public type, in one page. The XML documentation shipped with the packages is the authoritative version — this page is for scanning.
Namespace Offside.
public enum ErrorKind
{
Unexpected, Unauthorized, Forbidden, TooManyRequests, Conflict,
PreconditionFailed, Gone, Unprocessable, NotFound, Validation, BadRequest
}
The closed set of failure species. Selects the HTTP status and the severity rank. Declaration order is not severity order — see the status and severity tables.
public sealed class Error : IEquatable<Error>
| Member | Description |
|---|---|
string Code { get; } |
Stable identifier and message-catalog key |
ErrorKind Kind { get; } |
Failure species |
IReadOnlyDictionary<string, object?> Arguments { get; } |
Read-only snapshot of the template values |
string? Field { get; } |
Offending field, when attributable |
static Error NotFound(string resource, object? id = null) |
Code not_found |
static Error Gone(string resource, object? id = null) |
Code gone |
static Error Conflict(string resource, string? reason = null) |
Code conflict |
static Error Validation(string field, string? code = null, object? attemptedValue = null) |
Code validation or code; sets Field |
static Error BadRequest(string? reason = null) |
Code bad_request |
static Error Unauthorized(string? reason = null) |
Code unauthorized |
static Error Forbidden(string? reason = null) |
Code forbidden |
static Error PreconditionFailed(string? reason = null) |
Code precondition_failed |
static Error Unprocessable(string? reason = null) |
Code unprocessable |
static Error TooManyRequests(string? reason = null) |
Code too_many_requests |
static Error Unexpected(string? detail = null) |
Code unexpected; detail is diagnostic only |
static Error Custom(string code, ErrorKind kind, object? arguments = null, string? field = null) |
Business-rule error. Throws ArgumentException on a blank code |
DomainException ToException() |
Escape hatch |
bool Equals(Error?), operator ==, operator != |
Value equality, arguments included |
The constructor is internal; construction goes through the factories.
public readonly struct Result
| Member | Description |
|---|---|
bool IsSuccess { get; } / bool IsFailure { get; } |
Outcome |
IReadOnlyList<Error> Errors { get; } |
Errors on failure; empty on success |
TOut Match<TOut>(Func<TOut> onSuccess, Func<IReadOnlyList<Error>, TOut> onFailure) |
Branch into a value |
static Result Success() |
Success |
static Result Failure(params Error[] errors) |
Failure. Throws ArgumentException if empty |
static Result Failure(IEnumerable<Error> errors) |
Failure from a sequence, copied immediately |
static Result Combine(params Result[] results) |
Merge, concatenating errors in argument order |
static Result Combine<T>(params Result<T>[] results) |
Merge value results, discarding the values |
default(Result) is a success.
public readonly struct Result<T>
| Member | Description |
|---|---|
bool IsSuccess { get; } / bool IsFailure { get; } |
Outcome |
T Value { get; } |
The value. Throws InvalidOperationException on failure |
IReadOnlyList<Error> Errors { get; } |
Errors on failure; empty on success |
bool TryGetValue(out T value) |
Non-throwing read |
TOut Match<TOut>(Func<T, TOut> onSuccess, Func<IReadOnlyList<Error>, TOut> onFailure) |
Branch into a value |
Result<TOut> Map<TOut>(Func<T, TOut> map) |
Transform the value; short-circuits on failure |
Result<TOut> Bind<TOut>(Func<T, Result<TOut>> bind) |
Chain a fallible operation; short-circuits on failure |
static Result<T> Success(T value) |
Success |
static Result<T> Failure(params Error[] errors) |
Failure. Throws ArgumentException if empty |
static Result<T> Failure(IEnumerable<Error> errors) |
Failure from a sequence, copied immediately |
No implicit conversion from T, and no Apply — see deliberate omissions.
public sealed class DomainException : Exception
{
public IReadOnlyList<Error> Errors { get; }
public DomainException(IReadOnlyList<Error> errors);
}
Message is the first error’s Code. Produced by Error.ToException().
public interface IErrorMessageResolver
{
string GetMessage(Error error, CultureInfo culture);
}
Implement to source messages from somewhere other than JSON. By convention, return error.Code when no message is found.
public sealed class JsonErrorCatalog
{
public CultureInfo Culture { get; }
public Stream Json { get; }
public JsonErrorCatalog(CultureInfo culture, Stream json);
}
Throws ArgumentNullException on a null culture or stream.
public sealed class JsonErrorMessageResolver : IErrorMessageResolver
{
public JsonErrorMessageResolver(IEnumerable<JsonErrorCatalog> catalogs);
public string GetMessage(Error error, CultureInfo culture);
}
Parses all catalogs in the constructor. Throws InvalidOperationException when no invariant catalog is supplied. Lookup order: exact culture → parent → invariant; then the code itself.
public sealed class OffsideOptions
{
public OffsideOptions AddJson(CultureInfo culture, string json);
public OffsideOptions AddJson(CultureInfo culture, Stream json);
}
Both overloads take catalog content, not a path. Fluent.
public static IServiceCollection AddOffside(this IServiceCollection services, Action<OffsideOptions> configure);
Builds a JsonErrorMessageResolver eagerly and registers it as the singleton IErrorMessageResolver.
Namespace Offside.AspNetCore.
public sealed class OffsideAspNetCoreOptions
{
public bool ExposeExceptionDetails { get; set; }
public static OffsideAspNetCoreOptions FromEnvironment(IHostEnvironment environment);
}
ExposeExceptionDetails gates the debug field only; the client-facing detail of a 500 is always the generic message.
public static IServiceCollection AddOffsideAspNetCore(this IServiceCollection services);
Registers OffsideAspNetCoreOptions as a singleton, defaulting ExposeExceptionDetails from IHostEnvironment.IsDevelopment() when one is present, otherwise false.
public sealed class OffsideProblem
{
public required string Type { get; init; }
public required string Title { get; init; }
public int Status { get; init; }
public required string Detail { get; init; }
public required string TraceId { get; init; }
public string? Debug { get; init; } // omitted from JSON when null
public required IReadOnlyList<Item> Errors { get; init; }
public static OffsideProblem Create(
IReadOnlyList<Error> errors,
IErrorMessageResolver resolver,
CultureInfo culture,
string traceId,
bool exposeExceptionDetails = false);
public sealed class Item
{
public required string Code { get; init; }
public required string Kind { get; init; }
public required string Detail { get; init; }
public string? Field { get; init; }
}
}
Serialized as application/problem+json with camelCase names. See the response shape.
public static class ResultHttpExtensions
Minimal APIs — success is 204 No Content for Result, 200 OK with the value for Result<T>:
IResult ToHttpResult(this Result result, IErrorMessageResolver resolver, bool exposeExceptionDetails = false);
IResult ToHttpResult(this Result result, IErrorMessageResolver resolver, CultureInfo culture, bool exposeExceptionDetails = false);
IResult ToHttpResult(this Result result, IErrorMessageResolver resolver, CultureInfo? culture, OffsideAspNetCoreOptions options);
IResult ToHttpResult(this Result result, HttpContext httpContext);
IResult ToHttpResult<T>(this Result<T> result, IErrorMessageResolver resolver, bool exposeExceptionDetails = false);
IResult ToHttpResult<T>(this Result<T> result, IErrorMessageResolver resolver, CultureInfo culture, bool exposeExceptionDetails = false);
IResult ToHttpResult<T>(this Result<T> result, IErrorMessageResolver resolver, CultureInfo? culture, OffsideAspNetCoreOptions options);
IResult ToHttpResult<T>(this Result<T> result, HttpContext httpContext);
MVC controllers — success is NoContentResult / OkObjectResult:
IActionResult ToActionResult(this Result result, IErrorMessageResolver resolver, CultureInfo culture, bool exposeExceptionDetails = false);
IActionResult ToActionResult(this Result result, IErrorMessageResolver resolver, CultureInfo? culture, OffsideAspNetCoreOptions options);
IActionResult ToActionResult<T>(this Result<T> result, IErrorMessageResolver resolver, bool exposeExceptionDetails = false);
IActionResult ToActionResult<T>(this Result<T> result, IErrorMessageResolver resolver, CultureInfo culture, bool exposeExceptionDetails = false);
IActionResult ToActionResult<T>(this Result<T> result, IErrorMessageResolver resolver, CultureInfo? culture, OffsideAspNetCoreOptions options);
Note the asymmetry: there is no ToActionResult(this Result, IErrorMessageResolver, bool) for the unit Result. Pass a culture, or pass null through the options overload.
A null culture means “derive it from Accept-Language”. All overloads throw ArgumentNullException on a null resolver, options, or HttpContext.
Namespace Offside.Tool.
public sealed class SkillInstaller
{
public const string CursorSkills = ".cursor/skills";
public const string AgentsSkills = ".agents/skills";
public const string ClaudeSkills = ".claude/skills";
public SkillInstaller(string skillsSource);
public static SkillInstaller FromToolLocation();
public IReadOnlyList<string> Install(string projectRoot, bool force);
}
Install returns every path written, in write order. Throws DirectoryNotFoundException when the skills source or an expected skill folder is missing. See the CLI page.