Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/DOCFX-VERSION-PICKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ Committed as `[]` (empty array). The `docfx.yaml` workflow regenerates
the real `versions.json` at deploy time from the set of actual `v*`
tags that have versioned docs deployed (D6 derivation) and writes it
to the gh-pages site root. The empty stub is the fanout-safe default —
no DateTime-Extensions paths leak into other repos when this folder is
synced fleet-wide.
no repo-specific version paths leak into other repos when this folder
is synced fleet-wide.

**Local picker testing** requires populating `versions.json` manually
with mock entries (see "Testing locally" below). The picker
Expand Down
2 changes: 1 addition & 1 deletion examples/FSharp.DotNet8.Example/Program.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module FSharp.DotNet462.Example
module FSharp.DotNet8.Example

open System
open System.IO
Expand Down
51 changes: 33 additions & 18 deletions src/Wolfgang.TryPattern/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ namespace Wolfgang.TryPattern;


/// <summary>
/// The result of executing an <seealso cref="Action"/>. Contains properties indicating whether the operation
/// succeeded or failed. If the operation failed the <see cref="Result.ErrorMessage"/> property will contain
/// a message as to why.
/// Represents the outcome of an operation. Contains properties indicating whether the operation
/// succeeded or failed. If the operation failed the <see cref="Result.ErrorMessage"/> property will
/// contain a message as to why.
/// </summary>
/// <remarks>
/// Commonly produced by <see cref="Try.Run(Action)"/>, but also useful directly as a return type
/// from validation helpers, repository methods, and other service-layer code.
/// </remarks>
public class Result
{
/// <summary>
Expand Down Expand Up @@ -55,13 +59,14 @@ protected Result


/// <summary>
/// Creates a failed Result with the specified error Message.
/// Creates a failed <see cref="Result"/> with the specified error message.
/// </summary>
/// <param name="errorMessage">The error message indicating the reason for failure.</param>
/// <exception cref="ArgumentException">errorMessage is null, empty, or whitespace</exception>
/// <returns>A failed <see cref="Result"/> whose <see cref="ErrorMessage"/> is set to <paramref name="errorMessage"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="errorMessage"/> is null, empty, or whitespace.</exception>
public static Result Failure(string errorMessage) =>
string.IsNullOrWhiteSpace(errorMessage)
? throw new ArgumentException("errorMessage cannot be empty", nameof(errorMessage))
? throw new ArgumentException("errorMessage cannot be null, empty, or whitespace.", nameof(errorMessage))
: new Result(succeeded: false, errorMessage);


Expand All @@ -73,6 +78,7 @@ public static Result Failure(string errorMessage) =>
/// <summary>
/// Creates a successful <see cref="Result"/>.
/// </summary>
/// <returns>A successful <see cref="Result"/>.</returns>
/// <remarks>
/// Returns a cached singleton instance. <see cref="Result"/> is immutable, so reusing
/// the same instance is safe and avoids per-call allocations on hot paths. Callers must
Expand Down Expand Up @@ -102,7 +108,7 @@ public static Result Failure(string errorMessage) =>


/// <summary>
/// The error message describing why the operation failed. Otherwise, an empty string if the operation succeeded.
/// Gets the error message describing why the operation failed, or an empty string if the operation succeeded.
/// </summary>
public string? ErrorMessage { get; }

Expand Down Expand Up @@ -263,11 +269,17 @@ public static bool AllSucceeded([NotNull] params Result[]? results)


/// <summary>
/// The result of executing an <seealso cref="Func{T}"/>. Contains properties indicating whether the operation
/// <see cref="Result.Succeeded"/> or <see cref="Result.Failed"/>. If the operation failed the
/// <see cref="Result.ErrorMessage"/> property will contain a message as to why. If the operation succeeded the
/// <see cref="Result{T}.Value"/> property will contain the return value from the function.
/// Represents the outcome of an operation that produces a value of type <typeparamref name="T"/>.
/// Contains properties indicating whether the operation <see cref="Result.Succeeded"/> or
/// <see cref="Result.Failed"/>. If the operation failed the <see cref="Result.ErrorMessage"/> property
/// will contain a message as to why. If the operation succeeded the <see cref="Result{T}.Value"/>
/// property will contain the returned value.
/// </summary>
/// <typeparam name="T">The type of value returned on success.</typeparam>
/// <remarks>
/// Commonly produced by <see cref="Try.Run{T}(Func{T})"/>, but also useful directly as a return type
/// from repository, service, or validation code that wants to surface a value-or-error outcome.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"CA1000:Do not declare static members on generic types",
Expand Down Expand Up @@ -306,27 +318,30 @@ public class Result<T> : Result


/// <summary>
/// Creates a failed Result with the specified error message.
/// Creates a failed <see cref="Result{T}"/> with the specified error message.
/// </summary>
/// <param name="errorMessage">The error message indicating the reason for failure.</param>
/// <exception cref="ArgumentException">errorMessage is null, empty, or whitespace</exception>
/// <returns>A failed <see cref="Result{T}"/> whose <see cref="Result.ErrorMessage"/> is set to <paramref name="errorMessage"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="errorMessage"/> is null, empty, or whitespace.</exception>
#if NET5_0_OR_GREATER
public static new Result<T?> Failure(string errorMessage) =>
string.IsNullOrWhiteSpace(errorMessage)
? throw new ArgumentException("errorMessage cannot be empty", nameof(errorMessage))
? throw new ArgumentException("errorMessage cannot be null, empty, or whitespace.", nameof(errorMessage))
: new Result<T?>(succeeded: false, errorMessage, default!);
#else
public static new Result<T> Failure(string errorMessage) =>
string.IsNullOrWhiteSpace(errorMessage)
? throw new ArgumentException("errorMessage cannot be empty", nameof(errorMessage))
? throw new ArgumentException("errorMessage cannot be null, empty, or whitespace.", nameof(errorMessage))
: new Result<T>(succeeded: false, errorMessage, default!);
#endif



/// <summary>
/// Creates a successful <see cref="Result"/> with specified value.
/// Creates a successful <see cref="Result{T}"/> with the specified value.
/// </summary>
/// <param name="value">The value produced by the operation.</param>
/// <returns>A successful <see cref="Result{T}"/> whose <see cref="Value"/> is <paramref name="value"/>.</returns>
#if NET5_0_OR_GREATER
public static Result<T?> Success(T? value) => new(succeeded: true, string.Empty, value);
#else
Expand All @@ -336,9 +351,9 @@ public class Result<T> : Result


/// <summary>
/// The value produced by the operation if it succeeded.
/// Gets the value produced by the operation if it succeeded; throws if the operation failed.
/// </summary>
/// <exception cref="InvalidOperationException">Retrieving this property if the operation failed</exception>
/// <exception cref="InvalidOperationException">Thrown when this property is accessed after the operation has failed.</exception>
#if NET5_0_OR_GREATER
public T? Value => Failed
? throw new InvalidOperationException("Cannot access the Value of a failed Result.")
Expand Down
10 changes: 5 additions & 5 deletions src/Wolfgang.TryPattern/Try.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ public static Result Run([NotNull] Action? action)


/// <summary>
/// Executes the specified function, catching any exception that may occur.
/// Executes the specified function and returns its result, catching any exception that may occur.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="function">The function to execute.</param>
/// <returns>
/// A <see cref="Result{T}"/> indicating if the function was successful or not and the result of
/// the function if it was.
/// A successful <see cref="Result{T}"/> wrapping the function's return value, or a failed
/// <see cref="Result{T}"/> whose <see cref="Result.ErrorMessage"/> is the caught exception's message.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is null.</exception>
#if NET5_0_OR_GREATER
Expand Down Expand Up @@ -135,10 +135,10 @@ public static async Task<Result> RunAsync([NotNull] Action? action, Cancellation


/// <summary>
/// Executes the specified function, catching any exception that may occur.
/// Executes the specified asynchronous function, catching any exception that may occur.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="function">The function to execute.</param>
/// <param name="function">The asynchronous function to execute.</param>
/// <param name="token">
/// A <see cref="CancellationToken"/> that is checked before <paramref name="function"/> is
/// invoked. If cancellation is already requested, an <see cref="OperationCanceledException"/>
Expand Down
49 changes: 25 additions & 24 deletions src/Wolfgang.TryPattern/Wolfgang.TryPattern.csproj
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0;net8.0;net10.0</TargetFrameworks>
<LangVersion>14</LangVersion>
<Version>0.3.2</Version>
<!-- Pin AssemblyVersion to a fixed binding-stability baseline so consumers
do not need to recompile / add binding redirects on every minor/patch
bump. Bump only on a deliberate breaking API change. FileVersion +
InformationalVersion (the latter auto-derived from <Version>) carry
the actual release version. -->
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$([System.Text.RegularExpressions.Regex]::Replace("$(Version)", "[-+].*$", "")).0</FileVersion>
<Title>$(AssemblyName)</Title>
<Description>A .NET library demonstrating the Try pattern for structured error handling and result types.</Description>
<PackageProjectUrl>https://github.com/Chris-Wolfgang/Try-Pattern</PackageProjectUrl>
<RepositoryUrl>https://github.com/Chris-Wolfgang/Try-Pattern.git</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<SignAssembly>False</SignAssembly>
<PackageIcon>icon.png</PackageIcon>
<TargetFrameworks>net462;netstandard2.0;net8.0;net10.0</TargetFrameworks>
<LangVersion>14</LangVersion>
<Version>0.3.2</Version>
<!-- Pin AssemblyVersion to a fixed binding-stability baseline so consumers
do not need to recompile / add binding redirects on every minor/patch
bump. Bump only on a deliberate breaking API change. FileVersion +
InformationalVersion (the latter auto-derived from <Version>) carry
the actual release version. -->
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$([System.Text.RegularExpressions.Regex]::Replace("$(Version)", "[-+].*$", "")).0</FileVersion>
<Title>$(AssemblyName)</Title>
<Description>A .NET library demonstrating the Try pattern for structured error handling and result types.</Description>
<PackageTags>try;result;error-handling;exception-handling;functional;railway-oriented;dotnet</PackageTags>
<PackageProjectUrl>https://github.com/Chris-Wolfgang/Try-Pattern</PackageProjectUrl>
<RepositoryUrl>https://github.com/Chris-Wolfgang/Try-Pattern.git</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<SignAssembly>False</SignAssembly>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>

<PropertyGroup Condition="
'$(TargetFramework)' == 'net8.0' OR
'$(TargetFramework)' == 'net10.0'
">
'$(TargetFramework)' == 'net8.0' OR
'$(TargetFramework)' == 'net10.0'
">
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Expand Down
4 changes: 2 additions & 2 deletions tests/Wolfgang.TryPattern.Tests/ResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void Ctor_when_passed_true_and_empty_string_does_not_throw_Exception()
[InlineData(null)]
[InlineData(" ")]
[InlineData("Test error")]
public void Ctor_when_passed_true_and_non_empty_string_throw_InvalidOperationException(string? message)
public void Ctor_when_passed_true_and_non_empty_string_throws_ArgumentException(string? message)
{
var ex = Assert.Throws<ArgumentException>(() => new TestResult(succeeded: true, message));
Assert.Equal("errorMessage", ex.ParamName);
Expand All @@ -38,7 +38,7 @@ public void Ctor_when_passed_false_and_message_does_not_throw_Exception()
[InlineData(null)]
[InlineData(" ")]
[InlineData("")]
public void Ctor_when_passed_false_and_no_message_throw_InvalidOperationException(string? message)
public void Ctor_when_passed_false_and_no_message_throws_ArgumentException(string? message)
{
var ex = Assert.Throws<ArgumentException>(() => new TestResult(succeeded: false, message));
Assert.Equal("errorMessage", ex.ParamName);
Expand Down
16 changes: 8 additions & 8 deletions tests/Wolfgang.TryPattern.Tests/RunFuncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Wolfgang.TryPattern.Tests;
public class RunFuncTests
{
[Fact]
public void Run_Func_WithNullFunction_ThrowsArgumentNullException()
public void Run_Func_when_function_is_null_throws_ArgumentNullException()
{
// Arrange
Func<int>? nullFunction = null;
Expand All @@ -18,7 +18,7 @@ public void Run_Func_WithNullFunction_ThrowsArgumentNullException()


[Fact]
public void Run_Func_WithSuccessfulFunctionOfInt_ReturnsResult()
public void Run_Func_when_int_function_succeeds_returns_successful_Result()
{
// Arrange
const int expectedValue = 42;
Expand All @@ -37,7 +37,7 @@ public void Run_Func_WithSuccessfulFunctionOfInt_ReturnsResult()


[Fact]
public void Run_Func_WithSuccessfulFunctionOfNullableInt_ReturnsResult()
public void Run_Func_when_nullable_int_function_succeeds_returns_successful_Result()
{
// Arrange
var expectedValue = 42;
Expand All @@ -56,7 +56,7 @@ public void Run_Func_WithSuccessfulFunctionOfNullableInt_ReturnsResult()


[Fact]
public void Run_Func_WithStringFunction_ReturnsResult()
public void Run_Func_when_string_function_succeeds_returns_successful_Result()
{
// Arrange
const string expectedValue = "Hello, World!";
Expand All @@ -75,7 +75,7 @@ public void Run_Func_WithStringFunction_ReturnsResult()


[Fact]
public void Run_Func_WithNullableStringFunction_ReturnsResult()
public void Run_Func_when_nullable_string_function_succeeds_returns_successful_Result()
{
// Arrange
const string expectedValue = "Hello, World!";
Expand All @@ -94,7 +94,7 @@ public void Run_Func_WithNullableStringFunction_ReturnsResult()


[Fact]
public void Run_Func_WithObjectFunction_ReturnsResult()
public void Run_Func_when_object_function_succeeds_returns_successful_Result()
{
// Arrange
var expectedValue = new object();
Expand All @@ -113,7 +113,7 @@ public void Run_Func_WithObjectFunction_ReturnsResult()


[Fact]
public void FunctionNullableInt_WithExceptionThrowingFunction_ReturnsDefault()
public void Run_Func_when_nullable_int_function_throws_returns_failed_Result_whose_Value_access_throws()
{
// Arrange
static int? Function() => throw new InvalidOperationException("Test exception");
Expand Down Expand Up @@ -149,7 +149,7 @@ public void Run_Func_reference_type_returns_Result_with_correct_properties()


[Fact]
public void Run_Func_WithMultipleCalls_HandlesEachIndependently()
public void Run_Func_when_called_multiple_times_handles_each_independently()
{
// Arrange
var callCount = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<PropertyGroup>
<TargetFrameworks>net462;net472;net48;net481;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<Version>0.3.0</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
Expand Down Expand Up @@ -61,23 +60,6 @@
</PackageReference>
</ItemGroup>

<!-- .NET Core 3.1 specific packages -->
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.console" Version="2.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- .NET 5.0 specific packages -->
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0' ">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
Expand Down