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
19 changes: 19 additions & 0 deletions .github/workflows/Remaining Setup for Result class.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
🔐 Setup Instructions
Tag your release Push a tag like v1.0.0 to trigger the workflow:

bash
git tag v1.0.0
git push origin v1.0.0
Add your NuGet API key

Go to your GitHub repo → Settings → Secrets and variables → Actions

Add a new secret named NUGET_API_KEY with your key from nuget.org

Ensure your .csproj includes:

xml
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
You now have a fully automated, tag-driven release pipeline that builds, tests, packs, and publishes your NuGet package with full Source Link and XML doc support. Want to add prerelease support or auto-versioning from Git tags next? I’ve got templates for that too.
23 changes: 12 additions & 11 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@ jobs:
New-Item -Path .\.sonar\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner

- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
dotnet tool install --global dotnet-coverage
.\.sonar\scanner\dotnet-sonarscanner begin /k:"astar-development_${{ env.RepositoryName }}" /o:"astar-development" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.scanner.scanAll=false
dotnet build --configuration Release
dotnet-coverage collect 'dotnet test --filter "FullyQualifiedName!~Acceptance.Tests"' -f xml -o 'coverage.xml'
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
# - name: Build and analyze
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# shell: powershell
# run: |
# dotnet tool install --global dotnet-coverage
# .\.sonar\scanner\dotnet-sonarscanner begin /k:"astar-development_${{ env.RepositoryName }}" /o:"astar-development" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https:#sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.scanner.scanAll=false
# dotnet-coverage collect 'dotnet test --filter "FullyQualifiedName!~Acceptance.Tests"' -f xml -o 'coverage.xml'
# dotnet build --configuration Release
# dotnet-coverage collect 'dotnet test --filter "FullyQualifiedName!~Acceptance.Tests"' -f xml -o 'coverage.xml'
# .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"

- name: Pack NuGet package
if: github.ref == 'refs/heads/main'
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish NuGet Package

on:
push:
tags:
- 'v*' # Triggers on version tags like v1.0.0

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: 🧾 Checkout code
uses: actions/checkout@v4

- name: 🛠 Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x'

- name: 🔍 Restore dependencies
run: dotnet restore

- name: 🧪 Run tests
run: dotnet test --configuration Release --no-build

- name: 📦 Pack NuGet package
run: dotnet pack --configuration Release --no-build --output ./nupkg

- name: 🚀 Publish to NuGet.org
run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

72 changes: 43 additions & 29 deletions AStar.Dev.Functional.Extensions.sln
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8456212E-F453-483A-8E27-7494722AE10F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions", "src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj", "{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3C168EA0-2CFB-490A-8E3A-BE08F456DD3F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions.Tests.Unit", "test\AStar.Dev.Functional.Extensions.Tests.Unit\AStar.Dev.Functional.Extensions.Tests.Unit.csproj", "{CD5D55C2-C6D1-4E28-93FD-1A60118403CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions", "src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj", "{3FC5E2DB-6C2C-4D72-8498-39A121722334}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{267BE110-D583-4557-80D5-0D9E80A05542}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions.Tests.Unit", "test\AStar.Dev.Functional.Extensions.Tests.Unit\AStar.Dev.Functional.Extensions.Tests.Unit.csproj", "{F61660A7-160E-4654-94A5-F8D5D75FFBD6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{419B9C55-6F41-4ED8-B87C-855B23E01C41}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{2C3EC910-77D3-475C-A9EE-E8F9F9C8C43C}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yml = .github\dependabot.yml
.github\workflows\publish.yml = .github\workflows\publish.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{2BB3AD42-1BED-4792-8E37-E87783AE949C}"
ProjectSection(SolutionItems) = preProject
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
EndProjectSection
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.ConsoleSample", "samples\AStar.Dev.ConsoleSample\AStar.Dev.ConsoleSample.csproj", "{16513013-C9A4-4707-9D79-87A2F5BBCF5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.SampleApi", "samples\AStar.Dev.SampleApi\AStar.Dev.SampleApi.csproj", "{2C6E2B2F-2312-499D-A885-405FB25CFD85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.SampleBlazor", "samples\AStar.Dev.SampleBlazor\AStar.Dev.SampleBlazor.csproj", "{B4A9B8A7-7548-4C0E-AA7E-36299B21240F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E8A4624A-D322-4CF1-A3FB-EC7E013CEC89}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D1A87C44-FB04-49D8-8C34-C6CD33944612}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0A5D570A-ACDC-429C-A9EA-9E8D9DA7774C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23}.Release|Any CPU.Build.0 = Release|Any CPU
{CD5D55C2-C6D1-4E28-93FD-1A60118403CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD5D55C2-C6D1-4E28-93FD-1A60118403CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD5D55C2-C6D1-4E28-93FD-1A60118403CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD5D55C2-C6D1-4E28-93FD-1A60118403CE}.Release|Any CPU.Build.0 = Release|Any CPU
{16513013-C9A4-4707-9D79-87A2F5BBCF5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16513013-C9A4-4707-9D79-87A2F5BBCF5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16513013-C9A4-4707-9D79-87A2F5BBCF5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16513013-C9A4-4707-9D79-87A2F5BBCF5C}.Release|Any CPU.Build.0 = Release|Any CPU
{2C6E2B2F-2312-499D-A885-405FB25CFD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C6E2B2F-2312-499D-A885-405FB25CFD85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C6E2B2F-2312-499D-A885-405FB25CFD85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C6E2B2F-2312-499D-A885-405FB25CFD85}.Release|Any CPU.Build.0 = Release|Any CPU
{B4A9B8A7-7548-4C0E-AA7E-36299B21240F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4A9B8A7-7548-4C0E-AA7E-36299B21240F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4A9B8A7-7548-4C0E-AA7E-36299B21240F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4A9B8A7-7548-4C0E-AA7E-36299B21240F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3FC5E2DB-6C2C-4D72-8498-39A121722334} = {8456212E-F453-483A-8E27-7494722AE10F}
{F61660A7-160E-4654-94A5-F8D5D75FFBD6} = {3C168EA0-2CFB-490A-8E3A-BE08F456DD3F}
{2BB3AD42-1BED-4792-8E37-E87783AE949C} = {419B9C55-6F41-4ED8-B87C-855B23E01C41}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3FC5E2DB-6C2C-4D72-8498-39A121722334}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FC5E2DB-6C2C-4D72-8498-39A121722334}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FC5E2DB-6C2C-4D72-8498-39A121722334}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FC5E2DB-6C2C-4D72-8498-39A121722334}.Release|Any CPU.Build.0 = Release|Any CPU
{F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Release|Any CPU.Build.0 = Release|Any CPU
{2C3EC910-77D3-475C-A9EE-E8F9F9C8C43C} = {267BE110-D583-4557-80D5-0D9E80A05542}
{16513013-C9A4-4707-9D79-87A2F5BBCF5C} = {E8A4624A-D322-4CF1-A3FB-EC7E013CEC89}
{2C6E2B2F-2312-499D-A885-405FB25CFD85} = {E8A4624A-D322-4CF1-A3FB-EC7E013CEC89}
{B4A9B8A7-7548-4C0E-AA7E-36299B21240F} = {E8A4624A-D322-4CF1-A3FB-EC7E013CEC89}
{46743FC5-9AAD-46A0-BD96-B2EEA6CE9E23} = {D1A87C44-FB04-49D8-8C34-C6CD33944612}
{CD5D55C2-C6D1-4E28-93FD-1A60118403CE} = {0A5D570A-ACDC-429C-A9EA-9E8D9DA7774C}
EndGlobalSection
EndGlobal
15 changes: 15 additions & 0 deletions samples/AStar.Dev.ConsoleSample/AStar.Dev.ConsoleSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj" />
</ItemGroup>

</Project>
80 changes: 80 additions & 0 deletions samples/AStar.Dev.ConsoleSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using AStar.Dev.Functional.Extensions;

Console.WriteLine("🔎 FunctionalResults Sample");

// Example: Option<T>
var maybeName = GetUserInput();

var greeting = maybeName.Match(
name => $"Hello, {name}!",
() => "Hello, mysterious stranger.");

Console.WriteLine(greeting);

// Example: Result<T, E>
var result = Divide(10, 0);

var message = result.Match(
val => $"Quotient: {val}",
err => $"Error: {err}");

Console.WriteLine(message);

// Example: Try<T>
var risky = Try<int>.Run(() => int.Parse("not-an-int"));

var parsed = risky.Match(
ok => $"Parsed int: {ok}",
ex => $"Caught exception: {ex.Message}");

Console.WriteLine(parsed);

var numbers = new List<int> { 1, 2, 3, 4, 5 };

var found = numbers.FirstOrNone(n => n == 3); // returns Option<int>.Some
var notFound = numbers.FirstOrNone(n => n == 10); // returns Option<int>.None

Console.WriteLine(found); // Output: Some(3)
Console.WriteLine(notFound); // Output: None

var result2 = await TryFetchUsernameAsync()
.MapAsync(name => name.Trim())
.BindAsync(ValidateAsync)
.MatchAsync(
valid => Task.FromResult($"Welcome, {valid}!"),
() => Task.FromResult("No valid user found.")
);

Console.WriteLine(result2); // Output: Welcome, Jason!
Option.Some("Jason");
Option.None<string>();
var (isSome, value) = Option.Some("hello");

return;

Option<string> GetUserInput()
{
Console.Write("Enter your name (or leave blank): ");
var input = Console.ReadLine();

return string.IsNullOrWhiteSpace(input) ? Option.None<string>() : input;
}

Result<int, string> Divide(int numerator, int denominator)
{
return denominator == 0
? new Result<int, string>.Error("Division by zero")
: new Result<int, string>.Ok(numerator / denominator);
}

static Task<Option<string>> TryFetchUsernameAsync()
{
return Task.FromResult<Option<string>>(new Option<string>.Some(" Jason "));
}

static Task<Option<string>> ValidateAsync(string name)
{
return Task.FromResult(name.Trim() == "Jason"
? new Option<string>.Some(name.Trim())
: Option.None<string>());
}
18 changes: 18 additions & 0 deletions samples/AStar.Dev.SampleApi/AStar.Dev.SampleApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions samples/AStar.Dev.SampleApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using AStar.Dev.Functional.Extensions;

var app = WebApplication.Create();
app.MapGet("/", () => "Sample API up and running!");

app.MapGet("/greet", (string? name) =>
{
#pragma warning disable CS8604 // Possible null reference argument. That is the point...
Option<string> maybeName = name;
#pragma warning restore CS8604 // Possible null reference argument.

return maybeName.Match(
some => $"Hello, {some} 👋",
() => "Hello, anonymous 👻");
});

app.MapGet("/divide", (int a, int b) =>
{
Result<int, string> result = b == 0
? new Result<int, string>.Error("Division by zero")
: new Result<int, string>.Ok(a / b);

return result.Match(
ok => Results.Ok(new { Result = ok }),
err => Results.BadRequest(new { Error = err }));
});

app.MapGet("/parse", (string input) =>
{
var parsed = Try<int>.Run(() => int.Parse(input));

return parsed.Match(
ok => Results.Ok(new { Parsed = ok }),
ex => Results.BadRequest(new { Error = ex.Message }));
});

app.Run();
23 changes: 23 additions & 0 deletions samples/AStar.Dev.SampleApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5195",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7127;http://localhost:5195",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
6 changes: 6 additions & 0 deletions samples/AStar.Dev.SampleApi/SampleApi.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@SampleApi_HostAddress = http://localhost:5195

GET {{SampleApi_HostAddress}}/weatherforecast/
Accept: application/json

###
8 changes: 8 additions & 0 deletions samples/AStar.Dev.SampleApi/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/AStar.Dev.SampleApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
14 changes: 14 additions & 0 deletions samples/AStar.Dev.SampleBlazor/AStar.Dev.SampleBlazor.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj" />
</ItemGroup>

</Project>
Loading