Skip to content
Open
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
21 changes: 16 additions & 5 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: .NET
name: .NET Build and Test

on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read
checks: write
pull-requests: write

concurrency:
group: dotnet-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 10.0.x

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Test
run: dotnet test --no-build --verbosity normal
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,46 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="12.0.3" />
<PackageReference Include="Avalonia.Desktop" Version="12.0.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3" />
<PackageReference Include="Lucide.Avalonia" Version="0.2.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
<PackageReference Include="ReactiveUI.Avalonia" Version="12.0.1" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Identity.Client" Version="4.84.1" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.84.1" />
<PackageReference Include="Microsoft.Graph" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Testably.Abstractions" Version="10.2.0" />
</ItemGroup>

<ItemGroup>
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../AStar.Dev.FunctionalParadigm/AStar.Dev.FunctionalParadigm.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/AStar.Dev.CloudSyncFunctional/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
44 changes: 44 additions & 0 deletions src/AStar.Dev.FunctionalParadigm/Option.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace AStar.Dev.FunctionalParadigm;

public abstract record Option<TResult>
{
public static implicit operator TResult(Option<TResult> option) =>
option switch
{
Some<TResult> some => some.Value,
_ => default!
};

public static implicit operator string(Option<TResult> option) =>
option switch
{
None<TResult> => "missing",
Option<TResult>.None => "missing",
_ => string.Empty
};

/// <summary>
/// Represents the absence of a value.
/// </summary>
public sealed record None : Option<TResult>
{
/// <summary>
/// A helper method to create an instance of <see cref="Option{T}.None" />
/// </summary>
public static readonly None Instance = new();

private None()
{
}

/// <summary>
/// Overrides the ToString method to return the type as a simple string.
/// </summary>
/// <returns>The overridden ToString</returns>
public override string ToString() => "None";
}
}

public record Some<TResult>(TResult Value) : Option<TResult>;

public record None<TResult>() : Option<TResult>;
72 changes: 72 additions & 0 deletions src/AStar.Dev.FunctionalParadigm/OptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace AStar.Dev.FunctionalParadigm;

public static class OptionExtensions
{
public static Option<TResult> Tap<TResult>(this Option<TResult> option, Action<TResult> onSome, Action<string>? onNone = null)
{
if (option is Some<TResult> some)
{
onSome(some.Value);
return some;
}

if (option is None<TResult> none)
{
onNone?.Invoke(none);
return none;
}

throw new InvalidOperationException("Unexpected option type.");
}

public static Option<TMapped> Map<TResult, TMapped>(this Option<TResult> option, Func<TResult, TMapped> selector)
=> option switch
{
Some<TResult> some => new Some<TMapped>(selector(some.Value)),
None<TResult> => new None<TMapped>(),
_ => throw new InvalidOperationException("Unexpected option type.")
};

public static Option<TMapped> Bind<TResult, TMapped>(this Option<TResult> option, Func<TResult, Option<TMapped>> binder)
=> option switch
{
Some<TResult> some => binder(some.Value),
None<TResult> => new None<TMapped>(),
_ => throw new InvalidOperationException("Unexpected option type.")
};

public static TOut Match<TResult, TOut>(this Option<TResult> option, Func<TResult, TOut> onSome, Func<string, TOut> onNone)
=> option switch
{
Some<TResult> some => onSome(some.Value),
None<TResult> none => onNone(none),
_ => throw new InvalidOperationException("Unexpected option type.")
};

public static TOut Match<TResult, TOut>(this Option<TResult> option, Func<TResult, TOut> onSome)
=> option switch
{
Some<TResult> some => onSome(some.Value),
None<TResult> none => new None<TOut>(),
_ => throw new InvalidOperationException("Unexpected option type.")
};

public static async Task<TOut> MatchAsync<TResult, TOut>(this Task<Option<TResult>> task, Func<TResult, TOut> onSome, Func<string, TOut> onNone)
{
var option = await task.ConfigureAwait(false);

return option.Match(onSome, onNone);
}

public static async Task<TOut> MatchAsync<TResult, TOut>(this Task<Option<TResult>> task, Func<TResult, Task<TOut>> onSome, Func<string, Task<TOut>> onNone)
{
var option = await task.ConfigureAwait(false);

return option switch
{
Some<TResult> some => await onSome(some.Value).ConfigureAwait(false),
None<TResult> none => await onNone(none).ConfigureAwait(false),
_ => throw new InvalidOperationException("Unexpected option type.")
};
}
}
2 changes: 0 additions & 2 deletions src/AStar.Dev.FunctionalParadigm/Program.cs

This file was deleted.

24 changes: 24 additions & 0 deletions src/AStar.Dev.FunctionalParadigm/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace AStar.Dev.FunctionalParadigm;

public abstract record Result<TResult, TError>
{
public static implicit operator Result<TResult, TError>(TResult value) => new Ok<TResult, TError>(value);
public static implicit operator Result<TResult, TError>(TError error) => new Fail<TResult, TError>(error);

public static implicit operator TResult(Result<TResult, TError> result) =>
result switch
{
Ok<TResult, TError> ok => ok.Value,
_ => default!
};

public static implicit operator TError(Result<TResult, TError> result) =>
result switch
{
Fail<TResult, TError> fail => fail.Error,
_ => default!
};
}

public record Ok<TResult, TError>(TResult Value) : Result<TResult, TError>;
public record Fail<TResult, TError>(TError Error) : Result<TResult, TError>;
Loading
Loading