Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/fabioluz/result-pattern
Usage of pattern matching for stronger validation and control flow
https://github.com/fabioluz/result-pattern
csharp pattern-matching validation
Last synced: 1 day ago
JSON representation
Usage of pattern matching for stronger validation and control flow
- Host: GitHub
- URL: https://github.com/fabioluz/result-pattern
- Owner: fabioluz
- Created: 2024-04-06T17:07:37.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2024-04-06T18:21:50.000Z (9 months ago)
- Last Synced: 2024-11-08T23:39:41.426Z (about 2 months ago)
- Topics: csharp, pattern-matching, validation
- Language: C#
- Homepage:
- Size: 7.81 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Result Pattern
This project demonstrates how to use pattern matching and stronger typing to avoid the unsafe `if (isValid)` calls.
## The Problem
A common way of validating inputs, objects and business logic in imperative languages is by using if conditions to check validation results. Then, early returning the method if necessary. E.g.
```
obj.Validate();
if (!obj.IsValid)
{
return obj.Errors;
}
``````
if (!obj.IsValid())
{
return obj.Errors;
}
```The problem with this approach is that a programmer can easily forget to call `if (obj.IsValid)`, thus allowing unvalidated information to proceed in the flow. This can also easily pass through code reviews, where people are usually focused on finding problems in the code they are currently reviewing rather than looking for missing code in the pull request.
You may argue that this would be the programmer's or reviewer's fault because inputs should always be validated before anything else. However, sometimes that is not obvious. In a large codebase, a programmer who has just started working on it may not know that they have to call if (obj.SomethingIsNotTrue). Sometimes, it is not even clear where that information is coming from, which makes it harder to determine which validations or adjustments must be performed before saving that information in the database or sending it to another system.
## Solution
Use a class to represent the validated information. For example, consider the following input model:
```
public record CreateBook(string Name, string Author, int Year);
```Use another class to represent the input after validation:
```
public record ValidCreateBook(string Name, string Author, int Year);
```Use a class to represent the Result of an operation:
```
public abstract class Result
{
}public class Success(T value) : Result
{
public T Value { get; init; } = value;
}public class Failure(IEnumerable error) : Result
{
public IEnumerable Errors { get; init; } = error;
public Result Cast()
{
return new Failure(Errors);
}
}public readonly record struct Error(string Code, string Message);
```Instead of checking for a `Bool` flag for validation, always use `Result` to force the consumer to pattern match against `Success` or `Failure`.
In the `ValidCreateBook` hide the default constructor and use a smart-constructor to enforce input validation. You can see an example at https://github.com/fabioluz/result-pattern/blob/main/Books/Create/Validation.cs
Now, you can use the Result interface to check if the `ValidCreateBook` exists. E.g.
```
if (result is Failure failure)
{
return Results.UnprocessableEntity(failure.Errors);
}if (result is Success success)
{
return Results.Created($"/books/{success.Value.ID}", success.Value);
}
```If you forget to match against the `Success` or `Failure` you won't be able to access the result `ValidCreateBook`.