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
14 changes: 14 additions & 0 deletions FSharp.Data.Validation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Validation.Gira
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Data.Validation.Async", "src\FSharp.Data.Validation.Async\FSharp.Data.Validation.Async.fsproj", "{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Data.Validation.Async.Tests", "tests\FSharp.Data.Validation.Async.Tests\FSharp.Data.Validation.Async.Tests.fsproj", "{0B92DFE3-181E-4713-B03F-B747D8D38187}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -97,6 +99,18 @@ Global
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|x64.Build.0 = Release|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|x86.ActiveCfg = Release|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|x86.Build.0 = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|x64.ActiveCfg = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|x64.Build.0 = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|x86.ActiveCfg = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Debug|x86.Build.0 = Debug|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|Any CPU.Build.0 = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|x64.ActiveCfg = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|x64.Build.0 = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|x86.ActiveCfg = Release|Any CPU
{0B92DFE3-181E-4713-B03F-B747D8D38187}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 4 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1069,21 +1069,10 @@ Here is an example of what it might look like.
### Validating Async Data

What if we need to validate data that is retrieved asynchronously?
There are three functions available in the `FSharp.Data.Validation.Async` package that can help with this:

- `bindToAsync`
- `bindAsync`
- `bindFromAsync`

The `bindToAsync` function is used to bind a value to an asynchronous computation.
There are multiple functions available in the `FSharp.Data.Validation.Async` package that can help with this.
For example, the `bindToAsync` function is used to bind a value to an asynchronous computation.
The value is passed to the computation and the result is returned.

The `bindAsync` function is used to bind an asynchronous computation to a value.
The computation is executed and the result is passed to the function.

The `bindFromAsync` function is used to bind an asynchronous computation to another asynchronous computation.
The first computation is executed and the result is passed to the second computation.

Let's say we have a function that retrieves a user's data from a database.
We want to validate the data before we use it.
We can use the `bindToAsync` function to bind the data to a validation computation.
Expand Down Expand Up @@ -1112,6 +1101,8 @@ The `getUserDataAndValidate` function retrieves the user data and validates it.
The `bindToAsync` function is used to bind the data to the validation computation.
The result is an asynchronous computation that returns the validated data.

See the `FSharp.Data.Validation.Async` documentation for more information on the available functions.

## Validation Operations

### `refute*` Operations
Expand Down
16 changes: 16 additions & 0 deletions src/FSharp.Data.Validation.Async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# FSharp.Data.Validation.Async

## Description

This library provides a small set of functions that extend the `FSharp.Data.Validation` library to work with asynchronous workflows.

## Functions

- `bindToAsync: ('A -> Async<VCtx<'F, 'B>>) -> VCtx<'F, 'A> -> Async<VCtx<'F, 'B>>`
- `bindAsync: ('A -> Async<VCtx<'F, 'B>>) -> Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'B>>`
- `bindFromAsync: ('A -> VCtx<'F, 'B>) -> Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'B>>`
- `combineAsync: Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'B>> -> Async<VCtx<'F, 'A * 'B>>`
- `bindAndMergeSourcesAsync: ('A -> Async<VCtx<'F, 'B>>) -> Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'A * 'B>>`
- `bindToAndMergeSourcesAsync: ('A -> Async<VCtx<'F, 'B>>) -> VCtx<'F, 'A> -> Async<VCtx<'F, 'A * 'B>>`
- `bindFromAndMergeSourcesAsync: ('A -> VCtx<'F, 'B>) -> Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'A * 'B>>`
- `mapAsync: ('A -> Async<'B>) -> VCtx<'F, 'A> -> Async<VCtx<'F, 'B>>`
133 changes: 122 additions & 11 deletions src/FSharp.Data.Validation.Async/VCtx.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ namespace FSharp.Data.Validation
[<RequireQualifiedAccess>]
module VCtx =
/// <summary>
/// Binds a function that returns an asynchronous computation to a validation context.
/// Binds a function that returns an asynchronous validation context to a validation context.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into an
/// asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c> and a validation context <c>c</c>
/// of type <c>VCtx&lt;'F, 'A&gt;</c>. It returns an asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
/// asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c> and a validation context <c>c</c>
/// of type <c>VCtx&lt;'F, 'A&gt;</c>. It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
///
/// The function handles the following cases:
/// <list type="bullet">
Expand All @@ -23,9 +23,9 @@ module VCtx =
/// </item>
/// </list>
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns an asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</param>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</param>
/// <param name="c">A validation context of type <c>VCtx&lt;'F, 'A&gt;</c>.</param>
/// <returns>An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
/// <returns>An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
let bindToAsync (fn:'A -> Async<VCtx<'F, 'B>>) (c: VCtx<'F, 'A>): Async<VCtx<'F, 'B>> =
async {
match c with
Expand All @@ -40,24 +40,24 @@ module VCtx =
}

/// <summary>
/// Binds a function that returns a validation context to an asynchronous computation.
/// Binds a function that returns an asynchronous validation context to an asynchronous validation context.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into a validation context
/// of type <c>VCtx&lt;'F, 'B&gt;</c> and an asynchronous computation <c>c</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.
/// It returns an asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
/// of type <c>VCtx&lt;'F, 'B&gt;</c> and an asynchronous validation context <c>c</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.
/// It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns a validation context of type <c>VCtx&lt;'F, 'B&gt;</c>.</param>
/// <param name="c">An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <returns>An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
/// <param name="c">An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <returns>An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
let bindAsync (fn:'A -> Async<VCtx<'F, 'B>>) (c: Async<VCtx<'F, 'A>>): Async<VCtx<'F, 'B>> =
async {
let! c' = c
return! bindToAsync fn c'
}

/// <summary>
/// Binds a function that returns a validation context to a validation context.
/// Binds a function that returns a validation context to an asynchronous validation context.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into a validation context
Expand All @@ -69,3 +69,114 @@ module VCtx =
/// <returns>A validation context of type <c>VCtx&lt;'F, 'B&gt;</c>.</returns>
let bindFromAsync (fn:'A -> VCtx<'F, 'B>) (c: Async<VCtx<'F, 'A>>): Async<VCtx<'F, 'B>> =
bindAsync (fn >> async.Return) c

/// <summary>
/// Merge sources of two asynchronous computations of validation contexts into a single asynchronous validation context.
/// </summary>
/// <remarks>
/// This function takes two asynchronous validation contexts <c>c1</c> and <c>c2</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>
/// and returns an asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c> that merges the results.
/// </remarks>
/// <param name="c1">An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <param name="c2">An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</param>
/// <returns>An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c>.</returns>
/// <seealso cref="VCtx.mergeSources"/>
let mergeSourcesAsync
(c1: Async<VCtx<'F,'A>>)
(c2: Async<VCtx<'F,'B>>)
: Async<VCtx<'F,'A * 'B>> =
async {
let! a = c1
let! b = c2
return VCtx.mergeSources a b
}

/// <summary>
/// Binds a function that returns an asynchronous validation context to an asynchronous validation context and merges the results.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into an asynchronous computation
/// of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c> and an asynchronous computation <c>c</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.
/// It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c> that merges the results.
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns an asynchronous validation computation of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</param>
/// <param name="c">An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <returns>An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c>.</returns>
/// <seealso cref="VCtx.bindAsync"/>
/// <seealso cref="VCtx.mergeSourcesAsync"/>
let bindAndMergeSourcesAsync
(fn: 'A -> Async<VCtx<'F,'B>>)
(c: Async<VCtx<'F,'A>>)
: Async<VCtx<'F,'A * 'B>> =
bindAsync fn c |> mergeSourcesAsync c

/// <summary>
/// Binds a function that returns an asynchronous validation context to a validation context and merges the results.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into an
/// asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c> and a validation context <c>c</c>
/// of type <c>VCtx&lt;'F, 'A&gt;</c>. It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</param>
/// <param name="c">A validation context of type <c>VCtx&lt;'F, 'A&gt;</c>.</param>
/// <returns>An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
/// <seealso cref="VCtx.bindToAsync"/>
/// <seealso cref="VCtx.mergeSources"/>
let bindToAndMergeSourcesAsync
(fn: 'A -> Async<VCtx<'F,'B>>)
(c: VCtx<'F,'A>)
: Async<VCtx<'F,'A * 'B>> =
async {
let! b = bindToAsync fn c
return VCtx.mergeSources c b
}

// bindFromAndMergeSourcesAsync: ('A -> VCtx<'F, 'B>) -> Async<VCtx<'F, 'A>> -> Async<VCtx<'F, 'A * 'B>>

/// <summary>
/// Binds a function that returns a validation context to an asynchronous validation context and merges the results.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into a validation context
/// of type <c>VCtx&lt;'F, 'B&gt;</c> and an asynchronous validation context <c>c</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.
/// It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c> that merges the results.
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns a validation context of type <c>VCtx&lt;'F, 'B&gt;</c>.</param>
/// <param name="c">An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <returns>An asynchronous computation of type <c>Async&lt;VCtx&lt;'F, 'A * 'B&gt;&gt;</c>.</returns>
/// <seealso cref="VCtx.bindFromAsync"/>
/// <seealso cref="VCtx.mergeSources"/>
let bindFromAndMergeSourcesAsync
(fn: 'A -> VCtx<'F, 'B>)
(c: Async<VCtx<'F, 'A>>)
: Async<VCtx<'F, 'A * 'B>> =
let b = bindFromAsync fn c
mergeSourcesAsync c b

/// <summary>
/// Maps a function over the value of a validation context. The function returns an asynchronous computation.
/// </summary>
/// <remarks>
/// This function takes a function <c>fn</c> that transforms a value of type <c>'A</c> into an asynchronous computation
/// of type <c>Async&lt;'B&gt;</c> and an asynchronous validation context <c>c</c> of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.
/// It returns an asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.
/// </remarks>
/// <param name="fn">A function that takes a value of type <c>'A</c> and returns an asynchronous computation of type <c>Async&lt;'B&gt;</c>.</param>
/// <param name="c">An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'A&gt;&gt;</c>.</param>
/// <returns>An asynchronous validation context of type <c>Async&lt;VCtx&lt;'F, 'B&gt;&gt;</c>.</returns>
let mapAsync
(fn: 'A -> Async<'B>)
(c: Async<VCtx<'F,'A>>)
: Async<VCtx<'F,'B>> =
async {
let! c' = c
match c' with
| ValidCtx a ->
let! b = fn a
return ValidCtx b
| RefutedCtx (gfs,lfs) -> return RefutedCtx (gfs,lfs)
| DisputedCtx (gfs,lfs,a) ->
let! b = fn a
return DisputedCtx (gfs,lfs, b)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="[6.0.6,)" />
<PackageReference Include="Giraffe" Version="[6,)" />
<PackageReference Include="System.Text.Json" Version="[6.0.11,)" />
</ItemGroup>
</Project>
31 changes: 23 additions & 8 deletions src/FSharp.Data.Validation/VCtx.fs
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,37 @@ module VCtx =
(gfs, Utilities.mergeFailures lfs <| Utilities.mergeFailures lfs3 lfs2)
| Global _a -> (gfs @ gfs', Utilities.mergeFailures lfs lfs')

type VCtxBuilder() =
member this.Bind(v:VCtx<'F, 'A>, fn:'A -> VCtx<'F, 'B>): VCtx<'F, 'B> =
VCtx.bind fn v

member this.MergeSources(v1: VCtx<'F, 'A>, v2: VCtx<'F, 'B>) =
/// <summary>
/// Merges two validation contexts. If one of the contexts is refuted, the result is refuted. If one of the contexts
/// is disputed, the result is disputed. Otherwise, the result is valid. The result is a tuple of the values of the
/// two input contexts.
/// </summary>
/// <param name="v1">The first validation context.</param>
/// <param name="v2">The second validation context.</param>
/// <returns>A validation context that combines the results of the two input validation contexts.</returns>
/// <remarks>
/// This function takes two validation contexts <c>v1</c> and <c>v2</c> and returns a tupled validation context.
/// Prioritizes refuted contexts over disputed contexts and disputed contexts over valid contexts.
/// </remarks>
let mergeSources (v1: VCtx<'F, 'A>) (v2: VCtx<'F, 'B>): VCtx<'F, 'A * 'B> =
match (v1, v2) with
| ValidCtx a, ValidCtx b -> ValidCtx (a, b)
| ValidCtx _, DisputedCtx (gfs', lfs', _) -> RefutedCtx (gfs', lfs')
| ValidCtx a, DisputedCtx (gfs', lfs', b) -> DisputedCtx (gfs', lfs', (a, b))
| ValidCtx _, RefutedCtx (gfs', lfs') -> RefutedCtx (gfs', lfs')
| DisputedCtx (gfs, lfs, _), ValidCtx _ -> RefutedCtx (gfs, lfs)
| DisputedCtx (gfs, lfs, _), DisputedCtx (gfs', lfs', _) -> RefutedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs')
| DisputedCtx (gfs, lfs, a), ValidCtx b -> DisputedCtx (gfs, lfs, (a, b))
| DisputedCtx (gfs, lfs, a), DisputedCtx (gfs', lfs', b) -> DisputedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs', (a, b))
| DisputedCtx (gfs, lfs, _), RefutedCtx (gfs', lfs') -> RefutedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs')
| RefutedCtx (gfs, lfs), ValidCtx _ -> RefutedCtx (gfs, lfs)
| RefutedCtx (gfs, lfs), DisputedCtx (gfs', lfs', _) -> RefutedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs')
| RefutedCtx (gfs, lfs), RefutedCtx (gfs', lfs') -> RefutedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs')

type VCtxBuilder() =
member this.Bind(v:VCtx<'F, 'A>, fn:'A -> VCtx<'F, 'B>): VCtx<'F, 'B> =
VCtx.bind fn v

member this.MergeSources(v1: VCtx<'F, 'A>, v2: VCtx<'F, 'B>) =
VCtx.mergeSources v1 v2

member this.For(v:VCtx<'F, 'A>, fn:'A -> VCtx<'F, 'B>): VCtx<'F, 'B> = this.Bind(v, fn)

member this.Return(a:'A): VCtx<'F, 'A> = ValidCtx a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Compile Include="Tests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\FSharp.Data.Validation.Async\FSharp.Data.Validation.Async.fsproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="8.0.100" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FsCheck" Version="2.16.6" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.6" />
<PackageReference Include="FsUnit.xUnit" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions tests/FSharp.Data.Validation.Async.Tests/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Program = let [<EntryPoint>] main _ = 0
Loading
Loading