Skip to content

Commit

Permalink
Merge pull request #22 from mta-solutions/async-extensions
Browse files Browse the repository at this point in the history
Remove internal scoping and add Async extension methods for validation contexts
  • Loading branch information
RJSonnenberg authored Dec 10, 2024
2 parents 718458c + 22742c9 commit ab803c7
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 5 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2
Expand All @@ -18,7 +18,7 @@ jobs:
- name: Set VERSION variable from tag
run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV

- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
Expand All @@ -37,7 +37,9 @@ jobs:
run: dotnet pack --configuration Release /p:NuGetVersion=${VERSION} /p:PackageVersion=${VERSION} --include-symbols --output .

- name: Push
run: dotnet nuget push FSharp.Data.Validation.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${MTA_NUGET_KEY} --symbol-source https://symbols.nuget.org/download/symbols --symbol-api-key ${MTA_NUGET_KEY}
run: |
dotnet nuget push FSharp.Data.Validation.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${MTA_NUGET_KEY} --symbol-source https://symbols.nuget.org/download/symbols --symbol-api-key ${MTA_NUGET_KEY}
dotnet nuget push FSharp.Data.Validation.Async.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${MTA_NUGET_KEY} --symbol-source https://symbols.nuget.org/download/symbols --symbol-api-key ${MTA_NUGET_KEY}
env:
MTA_NUGET_KEY: ${{ secrets.MTA_NUGET_KEY }}
14 changes: 14 additions & 0 deletions FSharp.Data.Validation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Validation.Test
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Validation.Giraffe", "src\FSharp.Data.Validation.Giraffe\FSharp.Data.Validation.Giraffe.fsproj", "{99EA41F7-1E77-4DD2-9D2E-9F7BE20441FB}"
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -83,6 +85,18 @@ Global
{99EA41F7-1E77-4DD2-9D2E-9F7BE20441FB}.Release|x64.Build.0 = Release|Any CPU
{99EA41F7-1E77-4DD2-9D2E-9F7BE20441FB}.Release|x86.ActiveCfg = Release|Any CPU
{99EA41F7-1E77-4DD2-9D2E-9F7BE20441FB}.Release|x86.Build.0 = Release|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|x64.ActiveCfg = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|x64.Build.0 = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|x86.ActiveCfg = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Debug|x86.Build.0 = Debug|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|Any CPU.Build.0 = Release|Any CPU
{CF59CFDC-CE24-4F02-A454-2FE0CC1A9FA9}.Release|x64.ActiveCfg = Release|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [Validating Nested Types](#validating-nested-types)
- [Validating Collections](#validating-collections)
- [Serializing The Proof Type](#serializing-the-proof-type)
- [Validating Async Data](#validating-async-data)
- [Validation Operations](#validation-operations)
- [`refute*` Operations](#refute-operations)
- [`refute`](#refute)
Expand Down Expand Up @@ -1065,6 +1066,52 @@ 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.
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.

```fsharp
module Example
open FSharp.Data.Validation
open FSharp.Data.Validation.Async
let getUserData (id:int): Async<UserData> =
// get user data from database
let validateUserData (data:UserData): Proof<UserDataFailure, UserData> =
validation {
withValue data
// validate data
qed
} |> fromVCtx
let getUserDataAndValidate (id:int): Async<Proof<UserDataFailure, UserData>> =
getUserData id |> bindToAsync validateUserData
```

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.

## Validation Operations

### `refute*` Operations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<Compile Include="VCtx.fs" />
</ItemGroup>

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

</Project>
71 changes: 71 additions & 0 deletions src/FSharp.Data.Validation.Async/VCtx.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace FSharp.Data.Validation

[<RequireQualifiedAccess>]
module VCtx =
/// <summary>
/// Binds a function that returns an asynchronous computation 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>.
///
/// The function handles the following cases:
/// <list type="bullet">
/// <item>
/// <description><c>ValidCtx a</c>: Applies the function <c>fn</c> to <c>a</c> and returns the result.</description>
/// </item>
/// <item>
/// <description><c>RefutedCtx (gfs, lfs)</c>: Returns the same <c>RefutedCtx</c> without applying the function.</description>
/// </item>
/// <item>
/// <description><c>DisputedCtx (gfs, lfs, a)</c>: Applies the function <c>fn</c> to <c>a</c> and merges the results accordingly.</description>
/// </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="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>
let bindToAsync (fn:'A -> Async<VCtx<'F, 'B>>) (c: VCtx<'F, 'A>): Async<VCtx<'F, 'B>> =
async {
match c with
| ValidCtx a -> return! fn a
| RefutedCtx (gfs,lfs) -> return RefutedCtx (gfs,lfs)
| DisputedCtx (gfs,lfs,a) ->
let! b = fn a
match b with
| ValidCtx b -> return DisputedCtx (gfs,lfs,b)
| DisputedCtx (gfs',lfs',b) -> return DisputedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs', b)
| RefutedCtx (gfs',lfs') -> return RefutedCtx (gfs @ gfs', Utilities.mergeFailures lfs lfs')
}

/// <summary>
/// Binds a function that returns a validation context to an asynchronous computation.
/// </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>.
/// </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>
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.
/// </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 a validation context <c>c</c> of type <c>VCtx&lt;'F, 'A&gt;</c>.
/// It returns a validation context of type <c>VCtx&lt;'F, 'B&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">A validation context of type <c>VCtx&lt;'F, 'A&gt;</c>.</param>
/// <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
2 changes: 1 addition & 1 deletion src/FSharp.Data.Validation/Utilities.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module internal FSharp.Data.Validation.Utilities
module FSharp.Data.Validation.Utilities

// Given a sequence of options, return list of Some
let catOptions l = Seq.choose id l
Expand Down
1 change: 0 additions & 1 deletion src/FSharp.Data.Validation/VCtx.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ open System.Linq.Expressions
open FSharpPlus.Data

type VCtx<'F, 'A> =
internal
| ValidCtx of 'A
| DisputedCtx of 'F list * FailureMap<'F> * 'A
| RefutedCtx of 'F list * FailureMap<'F>
Expand Down

0 comments on commit ab803c7

Please sign in to comment.