Errors with Class: Beyond Simple Strings

Introduction

In tradition .NET development, error handling often falls into two camps: throwing heavy exceptions for flow control or returning vague string error messages which lose all context by the time they reach the logger.

The Esox.SharpAndRusty library introduces a third way, inspired by Rust’s robust error philosophy, it provides a production-ready Error type that treats failure as data, not just a message.

Context Chaining: The Breadcrum Trail

When a low-level I/O operation fails, a message like “File not found” is rarely helpful. You need to know why the application was trying to access that file, and which file it was trying to access.

SharpAndRusty uses Context Chaining to build a diagnostic trail as the error bubbles up. By using the Context() method you wrap the original error with higher-level business logic:

Result<Config, Error> LoadConfig(string path)
{
    return ReadFile(path)
        .Context($"Failed to load config from {path}") // Adds the "where"
        .Bind(content => ParseConfig(content)
            .Context("Failed to parse configuration")); // Adds the "why"
}

When you call GetFullMessage(), which is an instance method on Error, the library produces a formatted string showing the entire chain.

Type-Safe Metadata: No More String Parsing

Often you need to attache specific data to an error, like a userId, a retryCount or a timeStamp. Storing these in a single string makes them almost impossible to programmatically extract later.

SharpAndRusty provides a Type-Safe Metadata API. It validates types at the time the metadata is added, and stores them in an ImmutableDictionary for efficient sharing.

var error = Error.New("Operation failed")
    .WithMetadata("userId", 123)           // Type-safe int
    .WithMetadata("isRetryable", true);    // Type-safe bool


if (error.TryGetMetadata("userId", out int id)) 
{
    // Log specifically for this user
}

ErrorKind: Mapping to HTTP Responses

Not all errors are created equal. In a Web API, a “User Not Found” error should return a 404 status, whereas “Permission Denied” should return a 403 status code.

The library includes a number of predefined error kinds, such as NotFound, PermissionDenied, TimeOut and InvalidInput. This allows your API layer to map errors to status codes elegantly:

public IActionResult HandleError(Error error) => error.Kind switch
{
    ErrorKind.NotFound => NotFound(error.Message),
    ErrorKind.PermissionDenied => Forbid(),
    ErrorKind.InvalidInput => BadRequest(error.Message),
    _ => StatusCode(500)
};

Error objects which reference each other can easily lead to infinited loops and stack overflows during serialization or logging. SharpAndRusty is engineered to prevent these “hidden” production killers.

Conclusion

By moving beyond simple string and heavy exceptions, the Esox.SharpAndRusty library transforms error handling from a reactive afterthought into a pro-active architectural strength. It brigdes the gap between expressive error handling of languages like Rust and the robust ecosystem of .NET.

Through context chaing, you can provide future-you (and your DevOps team) with a clear narrative of what went wrong. With type-safe metadata, you ensure that errors remain machine-readable and actionable. Finally, by categorizing failures with ErrorKind, you can create a seemless bridg between your internal logic and your external API responses.

Adopting this pattern does not just make your code more stable; it makes your system more observable and probably your debugging sessions significantly shorter.

The Code Nomad
The Code Nomad
Articles: 165

Leave a Reply

Your email address will not be published. Required fields are marked *