diff --git a/.vscode/settings.json b/.vscode/settings.json index 76dd9a1..858341d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "dotnet.defaultSolution": "Just.Railway.sln" + "dotnet.defaultSolution": "Just.Railway.sln", + "dotnetAcquisitionExtension.enableTelemetry": false } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 93713a2..0543fbb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023 JustFixMe +Copyright (c) 2023-2024 JustFixMe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Railway.SourceGenerator/EnsureExtensionsExecutor.cs b/Railway.SourceGenerator/EnsureExtensionsExecutor.cs index 4fbe65a..e1bd7ae 100644 --- a/Railway.SourceGenerator/EnsureExtensionsExecutor.cs +++ b/Railway.SourceGenerator/EnsureExtensionsExecutor.cs @@ -1,7 +1,4 @@ -using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -45,6 +42,10 @@ public sealed class EnsureExtensionsExecutor : IGeneratorExecutor errorGenerationDefinitions.ForEach(def => GenerateSatisfiesExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); sb.AppendLine("#endregion"); + sb.AppendLine("#region Null"); + errorGenerationDefinitions.ForEach(def => GenerateNullExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + sb.AppendLine("#region NotNull"); errorGenerationDefinitions.ForEach(def => GenerateNotNullExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); sb.AppendLine("#endregion"); @@ -57,6 +58,34 @@ public sealed class EnsureExtensionsExecutor : IGeneratorExecutor errorGenerationDefinitions.ForEach(def => GenerateNotWhitespaceExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); sb.AppendLine("#endregion"); + sb.AppendLine("#region True"); + errorGenerationDefinitions.ForEach(def => GenerateTrueExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region False"); + errorGenerationDefinitions.ForEach(def => GenerateFalseExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region EqualTo"); + errorGenerationDefinitions.ForEach(def => GenerateEqualToExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region LessThan"); + errorGenerationDefinitions.ForEach(def => GenerateLessThanExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region GreaterThan"); + errorGenerationDefinitions.ForEach(def => GenerateGreaterThanExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region LessThanOrEqualTo"); + errorGenerationDefinitions.ForEach(def => GenerateLessThanOrEqualToExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + + sb.AppendLine("#region GreaterThanOrEqualTo"); + errorGenerationDefinitions.ForEach(def => GenerateGreaterThanOrEqualToExtensions(sb, def.ErrorParameterDecl, def.ErrorValueExpr)); + sb.AppendLine("#endregion"); + return sb.ToString(); } @@ -119,6 +148,44 @@ public sealed class EnsureExtensionsExecutor : IGeneratorExecutor """)); } + private void GenerateNullExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not null.\")"; + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure Null(this in Ensure ensure, {{errorParameterDecl}}) + where T : struct + { + return ensure.State switch + { + ResultState.Success => !ensure.Value.HasValue + ? new(default(T?)!, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure Null(this in Ensure ensure, {{errorParameterDecl}}) + where T : class + { + return ensure.State switch + { + ResultState.Success => ensure.Value is null + ? new(default(T?)!, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + private void GenerateNotNullExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) { string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is null.\")"; @@ -230,4 +297,183 @@ public sealed class EnsureExtensionsExecutor : IGeneratorExecutor } """); } + + private void GenerateTrueExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not true.\")"; + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure True(this in Ensure ensure, {{errorParameterDecl}}) + { + return ensure.State switch + { + ResultState.Success => ensure.Value == true + ? new(ensure.Value!, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure True(this in Ensure ensure, {{errorParameterDecl}}) + { + return ensure.State switch + { + ResultState.Success => ensure.Value == true + ? new(ensure.Value.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateFalseExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not false.\")"; + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure False(this in Ensure ensure, {{errorParameterDecl}}) + { + return ensure.State switch + { + ResultState.Success => ensure.Value == false + ? new(ensure.Value!, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure False(this in Ensure ensure, {{errorParameterDecl}}) + { + return ensure.State switch + { + ResultState.Success => ensure.Value == false + ? new(ensure.Value.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateEqualToExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not equal to requirement.\")"; + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure EqualTo(this in Ensure ensure, T requirement, {{errorParameterDecl}}) + where T : IEquatable + { + return ensure.State switch + { + ResultState.Success => ensure.Value.Equals(requirement) + ? new(ensure.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateLessThanExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not less than requirement.\")"; + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure LessThan(this in Ensure ensure, T requirement, {{errorParameterDecl}}) + where T : IComparable + { + return ensure.State switch + { + ResultState.Success => ensure.Value.CompareTo(requirement) < 0 + ? new(ensure.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateGreaterThanExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is not greater than requirement.\")"; + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure GreaterThan(this in Ensure ensure, T requirement, {{errorParameterDecl}}) + where T : IComparable + { + return ensure.State switch + { + ResultState.Success => ensure.Value.CompareTo(requirement) > 0 + ? new(ensure.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateLessThanOrEqualToExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is greater than requirement.\")"; + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure LessThanOrEqualTo(this in Ensure ensure, T requirement, {{errorParameterDecl}}) + where T : IComparable + { + return ensure.State switch + { + ResultState.Success => ensure.Value.CompareTo(requirement) <= 0 + ? new(ensure.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } + + private void GenerateGreaterThanOrEqualToExtensions(StringBuilder sb, string errorParameterDecl, string errorValueExpr) + { + string defaultErrorExpr = "?? Error.New(DefaultErrorType, $\"Value {{{ensure.ValueExpression}}} is less than requirement.\")"; + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(EnsureExtensionsExecutor)}}", "1.0.0.0")] + public static Ensure GreaterThanOrEqualTo(this in Ensure ensure, T requirement, {{errorParameterDecl}}) + where T : IComparable + { + return ensure.State switch + { + ResultState.Success => ensure.Value.CompareTo(requirement) >= 0 + ? new(ensure.Value, ensure.ValueExpression) + : new({{errorValueExpr}} {{defaultErrorExpr}}, ensure.ValueExpression), + ResultState.Error => new(ensure.Error!, ensure.ValueExpression), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + } + """); + } } diff --git a/Railway.SourceGenerator/ExtensionsMethodGenerator.cs b/Railway.SourceGenerator/ExtensionsMethodGenerator.cs index f626b06..b7298be 100644 --- a/Railway.SourceGenerator/ExtensionsMethodGenerator.cs +++ b/Railway.SourceGenerator/ExtensionsMethodGenerator.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; using Microsoft.CodeAnalysis; namespace Just.Railway.SourceGen; diff --git a/Railway.SourceGenerator/IGeneratorExecutor.cs b/Railway.SourceGenerator/IGeneratorExecutor.cs index f4c5e89..e42d884 100644 --- a/Railway.SourceGenerator/IGeneratorExecutor.cs +++ b/Railway.SourceGenerator/IGeneratorExecutor.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.CodeAnalysis; namespace Just.Railway.SourceGen; diff --git a/Railway.SourceGenerator/ResultAppendExecutor.cs b/Railway.SourceGenerator/ResultAppendExecutor.cs index 73a294b..f0364cc 100644 --- a/Railway.SourceGenerator/ResultAppendExecutor.cs +++ b/Railway.SourceGenerator/ResultAppendExecutor.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Just.Railway.SourceGen; diff --git a/Railway.SourceGenerator/ResultMatchExecutor.cs b/Railway.SourceGenerator/ResultMatchExecutor.cs index 9376454..2bfb33c 100644 --- a/Railway.SourceGenerator/ResultMatchExecutor.cs +++ b/Railway.SourceGenerator/ResultMatchExecutor.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; diff --git a/Railway.SourceGenerator/ResultTryRecoverExecutor.cs b/Railway.SourceGenerator/ResultTryRecoverExecutor.cs index 2c68676..ebcf11a 100644 --- a/Railway.SourceGenerator/ResultTryRecoverExecutor.cs +++ b/Railway.SourceGenerator/ResultTryRecoverExecutor.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; namespace Just.Railway.SourceGen; diff --git a/Railway.SourceGenerator/TryExtensionsExecutor.cs b/Railway.SourceGenerator/TryExtensionsExecutor.cs index 30ff82e..0d74b2e 100644 --- a/Railway.SourceGenerator/TryExtensionsExecutor.cs +++ b/Railway.SourceGenerator/TryExtensionsExecutor.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Immutable; using System.Linq; using System.Text; diff --git a/Railway/Ensure.cs b/Railway/Ensure.cs index 5c725b8..6110367 100644 --- a/Railway/Ensure.cs +++ b/Railway/Ensure.cs @@ -8,32 +8,9 @@ public static partial class Ensure [Pure] public static Ensure That(T value, [CallerArgumentExpression(nameof(value))]string valueExpression = "") => new(value, valueExpression); - [Pure] public static Result Result(this in Ensure ensure) => ensure.State switch - { - ResultState.Success => new(ensure.Value), - ResultState.Error => new(ensure.Error!), - _ => throw new EnsureNotInitializedException(nameof(ensure)) - }; - [Pure] public static async Task> Result(this Task> ensureTask) - { - var ensure = await ensureTask.ConfigureAwait(false); - return ensure.State switch - { - ResultState.Success => new(ensure.Value), - ResultState.Error => new(ensure.Error!), - _ => throw new EnsureNotInitializedException(nameof(ensureTask)) - }; - } - [Pure] public static async ValueTask> Result(this ValueTask> ensureTask) - { - var ensure = await ensureTask.ConfigureAwait(false); - return ensure.State switch - { - ResultState.Success => new(ensure.Value), - ResultState.Error => new(ensure.Error!), - _ => throw new EnsureNotInitializedException(nameof(ensureTask)) - }; - } + [Pure] public static Result Result(this in Ensure ensure) => ensure; + [Pure] public static async Task> Result(this Task> ensureTask) => await ensureTask.ConfigureAwait(false); + [Pure] public static async ValueTask> Result(this ValueTask> ensureTask) => await ensureTask.ConfigureAwait(false); } public readonly struct Ensure @@ -58,6 +35,22 @@ public readonly struct Ensure Value = default!; State = ResultState.Error; } + + [Pure] + public static implicit operator Result(in Ensure ensure) => ensure.State switch + { + ResultState.Success => new(ensure.Value), + ResultState.Error => new(ensure.Error!), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; + + [Pure] + public static explicit operator Result(in Ensure ensure) => ensure.State switch + { + ResultState.Success => new(null), + ResultState.Error => new(ensure.Error!), + _ => throw new EnsureNotInitializedException(nameof(ensure)) + }; } [Serializable] diff --git a/Railway/ErrorJsonConverter.cs b/Railway/ErrorJsonConverter.cs index 76b3271..b6d8b27 100644 --- a/Railway/ErrorJsonConverter.cs +++ b/Railway/ErrorJsonConverter.cs @@ -49,7 +49,7 @@ public sealed class ErrorJsonConverter : JsonConverter => new(errorInfo.Type, errorInfo.Message) { ExtensionData = errorInfo.ExtensionData }; internal static (string Type, string Message, ImmutableDictionary ExtensionData) ReadOne(ref Utf8JsonReader reader) { - List>? extensionData = null; + ImmutableDictionary.Builder? extensionData = null; string type = "error"; string message = ""; while (reader.Read()) @@ -83,8 +83,8 @@ public sealed class ErrorJsonConverter : JsonConverter } else if (!string.IsNullOrEmpty(propname)) { - extensionData ??= new(4); - extensionData.Add(new(propname, propvalue)); + extensionData ??= ImmutableDictionary.CreateBuilder(); + extensionData.Add(propname, propvalue); } break; @@ -95,7 +95,7 @@ public sealed class ErrorJsonConverter : JsonConverter } } endLoop: - return (type, message, extensionData?.ToImmutableDictionary() ?? ImmutableDictionary.Empty); + return (type, message, extensionData?.ToImmutable() ?? ImmutableDictionary.Empty); } internal static void WriteOne(Utf8JsonWriter writer, Error value) { diff --git a/Railway/Railway.csproj b/Railway/Railway.csproj index 5692e96..a4767e1 100644 --- a/Railway/Railway.csproj +++ b/Railway/Railway.csproj @@ -11,7 +11,7 @@ Base for railway-oriented programming in .NET. Package includes Result object, Error class and most of the common extensions. railway-oriented;functional;result-pattern;result-object;error-handling JustFixMe - Copyright (c) 2023 JustFixMe + Copyright (c) 2023-2024 JustFixMe LICENSE README.md https://github.com/JustFixMe/Just.Railway/ diff --git a/Railway/ReflectionHelper.cs b/Railway/ReflectionHelper.cs index e269e09..1db6ef0 100644 --- a/Railway/ReflectionHelper.cs +++ b/Railway/ReflectionHelper.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; namespace Just.Railway; diff --git a/Railway/Result.cs b/Railway/Result.cs index 7dbcb4e..16d32fb 100644 --- a/Railway/Result.cs +++ b/Railway/Result.cs @@ -1,4 +1,3 @@ - namespace Just.Railway; internal enum ResultState : byte diff --git a/Raliway.Tests/EnsureExtensions/Satisfy.cs b/Raliway.Tests/EnsureExtensions/Satisfy.cs index 588f2af..4c84a1d 100644 --- a/Raliway.Tests/EnsureExtensions/Satisfy.cs +++ b/Raliway.Tests/EnsureExtensions/Satisfy.cs @@ -7,8 +7,9 @@ public class Satisfy { var result = Ensure.That(69) .Satisfies(i => i < 100) + .LessThan(100) .Result(); - + Assert.True(result.IsSuccess); Assert.Equal(69, result.Value); } @@ -18,8 +19,9 @@ public class Satisfy var error = Error.New(Ensure.DefaultErrorType, "Value {69} does not satisfy the requirement."); var result = Ensure.That(69) .Satisfies(i => i > 100) + .GreaterThan(100) .Result(); - + Assert.True(result.IsFailure); Assert.Equal(error, result.Error); } @@ -32,8 +34,9 @@ public class Satisfy .NotEmpty() .NotWhitespace() .Satisfies(s => s == "69") + .EqualTo("69") .Result(); - + Assert.True(result.IsSuccess); Assert.Equal("69", result.Value); } @@ -47,8 +50,9 @@ public class Satisfy .NotEmpty() .NotWhitespace() .Satisfies(s => s == "69") + .EqualTo("69") .Result(); - + Assert.True(result.IsFailure); Assert.Equal(error, result.Error); }