Knight.Response
1.1.0
dotnet add package Knight.Response --version 1.1.0
NuGet\Install-Package Knight.Response -Version 1.1.0
<PackageReference Include="Knight.Response" Version="1.1.0" />
<PackageVersion Include="Knight.Response" Version="1.1.0" />
<PackageReference Include="Knight.Response" />
paket add Knight.Response --version 1.1.0
#r "nuget: Knight.Response, 1.1.0"
#:package Knight.Response@1.1.0
#addin nuget:?package=Knight.Response&version=1.1.0
#tool nuget:?package=Knight.Response&version=1.1.0
Knight.Response
Knight.Response is a lightweight, immutable, fluent result library for C# services, APIs, and applications. It provides a clean and consistent way to handle outcomes, success/failure states, messages, and functional chaining — making your code simpler, safer, and more expressive.
Features
- Immutable
Result
andResult<T>
types - Statuses:
Completed
,Cancelled
,Failed
,Error
- Rich
Message
withMessageType
+ optional structuredMetadata
- Factory methods:
Success
,Failure
,Error
,Cancel
,NotFound
,FromCondition
,Aggregate
,Error(Exception)
- Functional extensions:
OnSuccess
,OnFailure
,Map
,Bind
- Advanced extensions:
Ensure
,Tap
,Recover
,WithMessage
,WithMessages
- Pattern matching via deconstruction
- Zero runtime dependencies
Installation
dotnet add package Knight.Response
Quick Start
using Knight.Response;
var userResult = Results.Success(new User("Knight"))
.Ensure(u => u!.IsActive, "User is not active")
.Tap(u => _audit.LogLogin(u!));
if (userResult.IsSuccess)
{
Console.WriteLine(userResult.Value!.Name);
}
Core Concepts
public class Result
{
public Status Status { get; }
public IReadOnlyList<Message> Messages { get; }
public bool IsSuccess { get; } // Status == Completed
}
public class Result<T> : Result
{
public T? Value { get; }
}
Status
Completed
Cancelled
Failed
Error
MessageType
Information
Warning
Error
Metadata
Message
supports optional Metadata
as a read-only dictionary for structured context, e.g.:
var msg = new Message(
MessageType.Warning,
"Rate limit exceeded",
new Dictionary<string, object?> { ["retryAfter"] = 30 }
);
Factory Methods
Method | Description |
---|---|
Results.Success() |
Create a success result |
Results.Success<T>(T value) |
Success result with data |
Results.Success<T>(T value, IReadOnlyList<Message> messages) |
Success with value and messages |
Results.Failure(string reason) |
Failure result with message |
Results.Failure(IReadOnlyList<Message> messages) |
Failure from messages |
Results.Error(string reason) |
Error result |
Results.Error(Exception ex) |
Error result from exception |
Results.Cancel(string reason) |
Cancelled result (defaults to Warning ) |
Results.Cancel(IReadOnlyList<Message> messages) |
Cancelled from messages |
Results.NotFound() |
"Not found" (defaults to Completed + Warning ) |
Results.NotFound(string message, Status status = Status.Completed) |
"Not found" with override status |
Results.FromCondition(bool condition, string failMessage) |
Success if true, Failure if false |
Results.Aggregate(IEnumerable<Result> results) |
Combine multiple results |
Core Extensions
result.OnSuccess(() => Console.WriteLine("All good"))
.OnFailure(msgs => Console.WriteLine($"Failed: {string.Join(", ", msgs.Select(m => m.Content))}"));
var mapped = Results.Success(2).Map(x => x * 5); // Success(10)
var bound = Results.Success("abc").Bind(v => Results.Success(v.ToUpper()));
- OnSuccess – Runs an action if result is success
- OnFailure – Runs an action if result is failure/error/cancelled
- Map – Transforms the value if success
- Bind – Chains another
Result
operation if success
Advanced Extensions
- Ensure – Ensures a predicate holds for success; otherwise returns failure
- Tap – Executes an action for side effects, preserving the result
- Recover – Transforms a failure into a success with fallback value
- WithMessage – Adds a single message to a result
- WithMessages – Adds multiple messages to a result
var ensured = Results.Success(5).Ensure(x => x > 0, "Must be positive");
var tapped = ensured.Tap(v => Console.WriteLine($"Value: {v}"));
var recovered = Results.Failure<int>("bad").Recover(_ => 42);
var withMsg = recovered.WithMessage(new Message(MessageType.Information, "Recovered with default"));
Pattern Matching
You can deconstruct results directly:
var (status, messages) = Results.Failure("fail!");
var (status, messages, value) = Results.Success(42);
Or use C# pattern matching:
switch (result)
{
case { Status: Status.Completed }:
Console.WriteLine("Success!");
break;
case { Status: Status.Failed, Messages: var msgs }:
Console.WriteLine($"Failed: {msgs[0].Content}");
break;
}
Important: Result<T>.Value
and default values
When T
is a non-nullable value type, failed results will contain the default value of that type (0
for int
, false
for bool
, etc.). This is a limitation of .NET generics, since value types always have a default.
var r1 = Results.Failure<int>("bad");
Console.WriteLine(r1.Value); // 0
var r2 = Results.Failure<int?>("bad");
Console.WriteLine(r2.Value); // null
Guidance
- Use nullable value types (e.g.
int?
,bool?
) if you wantnull
to represent "no value". - Use non-nullable value types if a default like
0
orfalse
makes sense in your domain. - Reference types (e.g.
string
,User
) will correctly returnnull
when notCompleted
.
Status values
The Status
enum indicates the outcome of an operation:
Completed
– successCancelled
– cancelled before completionFailed
– failed due to a known issueError
– unexpected/unhandled error
Validation Support (new in 1.1.0)
You can now construct Result
and Result<T>
from System.ComponentModel.DataAnnotations.ValidationResult
collections.
using System.ComponentModel.DataAnnotations;
var errors = new List<ValidationResult>
{
new("Name is required", new[] { "Name" }),
new("Amount must be greater than 0", new[] { "Amount" }),
new("General rule failed")
};
// Produces Error result with prefixed messages:
// "Name: Name is required", "Amount: Amount must be greater than 0", "General rule failed"
var result = Results.Validation(errors);
For generics:
var result = Results.Validation<MyEntity>(errors);
Metadata Enricher Overloads
Use an enricher delegate to attach metadata (e.g., field name) without altering text:
var result = Results.Validation(
errors,
(msg, field) => string.IsNullOrEmpty(field)
? msg
: msg.WithMeta("field", field) // your custom extension
);
Default behavior:
- Prefixed mapping: "Field: message"
- Enricher overload: raw message text, field passed separately
License
This project is licensed under the MIT License.
Contributing
Contributions are welcome! Please read CONTRIBUTING.md.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.ComponentModel.Annotations (>= 5.0.0)
- System.Text.Json (>= 9.0.8)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Knight.Response:
Package | Downloads |
---|---|
Knight.Response.Abstractions.Http
Framework-agnostic HTTP abstractions for Knight.Response (shared options and validation mapping). |
GitHub repositories
This package is not used by any popular GitHub repositories.
Added:
- Support for creating Results from **ValidationResult** collections:
• `Results.Validation(...)` and `Results.Validation{T}(...)`
• Converts validation errors into error messages with field prefixes ("Field: message").
- Enricher overloads for attaching metadata:
• `Results.Validation(errors, (msg, field) => ...)`
• Enables adding structured metadata (e.g., field names) without altering message text.
Changed:
- Normalized validation messages:
• Trimmed whitespace from content and field names.
• Fallback to "Validation error." when message is null/empty/whitespace.
- Minor internal refactors for robustness and testability.
Fixed:
- Ensured `Result` constructors normalize `null` message lists to empty collections.
- Achieved **100% mutation score** with Stryker.