diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..cd967fc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..446b951
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..74f048e
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,70 @@
+name: .NET
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ build:
+ name: Build and analyze
+ runs-on: windows-latest
+ env:
+ ProjectName: 'AStar.Dev.Functional.Extensions'
+ RepositoryName: 'astar-dev-functional-extensions'
+ steps:
+ - name: Set up JDK
+ uses: actions/setup-java@v4.4.0
+ with:
+ java-version: 17
+ distribution: 'zulu'
+
+ - name: Checkout
+ uses: actions/checkout@v4.2.1
+ with:
+ fetch-depth: 0
+
+ - name: Cache SonarCloud packages
+ uses: actions/cache@v4.2.3
+ with:
+ path: ~\sonar\cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+
+ - name: Cache SonarCloud scanner
+ id: cache-sonar-scanner
+ uses: actions/cache@v4.2.3
+ with:
+ path: .\.sonar\scanner
+ key: ${{ runner.os }}-sonar-scanner
+ restore-keys: ${{ runner.os }}-sonar-scanner
+
+ - name: Install SonarCloud scanner
+ if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
+ shell: powershell
+ run: |
+ 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: Pack NuGet package
+ if: github.ref == 'refs/heads/main'
+ run: dotnet pack .\src\${{ env.ProjectName }}\${{ env.ProjectName }}.csproj
+
+ - name: Push to NuGet
+ if: github.ref == 'refs/heads/main'
+ run: dotnet nuget push "**\${{ env.ProjectName }}.*.nupkg" --api-key ${{secrets.nuget_api_key}} --skip-duplicate --source https://api.nuget.org/v3/index.json
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..2182218
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/contentModel.xml
+/projectSettingsUpdater.xml
+/.idea.astar-dev-functional-extensions.iml
+/modules.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore
new file mode 100644
index 0000000..de8bd97
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/contentModel.xml
+/.idea.AStar.Dev.Functional.Extensions.iml
+/projectSettingsUpdater.xml
+/modules.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name
new file mode 100644
index 0000000..be440be
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name
@@ -0,0 +1 @@
+AStar.Dev.Functional.Extensions
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/indexLayout.xml b/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AStar.Dev.Functional.Extensions.sln b/AStar.Dev.Functional.Extensions.sln
new file mode 100644
index 0000000..2d11f8b
--- /dev/null
+++ b/AStar.Dev.Functional.Extensions.sln
@@ -0,0 +1,47 @@
+
+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}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3C168EA0-2CFB-490A-8E3A-BE08F456DD3F}"
+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}"
+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}"
+ ProjectSection(SolutionItems) = preProject
+ .github\dependabot.yml = .github\dependabot.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
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ 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
+ EndGlobalSection
+EndGlobal
diff --git a/AStar.ico b/AStar.ico
new file mode 100644
index 0000000..0b9aea4
Binary files /dev/null and b/AStar.ico differ
diff --git a/AStar.png b/AStar.png
new file mode 100644
index 0000000..8cac0a1
Binary files /dev/null and b/AStar.png differ
diff --git a/build-and-test.ps1 b/build-and-test.ps1
new file mode 100644
index 0000000..aad68a7
--- /dev/null
+++ b/build-and-test.ps1
@@ -0,0 +1,3 @@
+dotnet build
+dotnet test
+dotnet stryker --open-report --break-at 99
\ No newline at end of file
diff --git a/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj
new file mode 100644
index 0000000..1ea161b
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj
@@ -0,0 +1,51 @@
+
+
+
+ latest-recommended
+ AStar Developement, Jason Barden
+ AStar Development
+ AStar Developement, 2025
+ AStar.Dev.Functional.Extensions package contains some basic implementations of Option, Some and None. This list may expand over time.
+ $(AssemblyName).xml
+ True
+ True
+ true
+ enable
+ True
+ true
+ enable
+ AStar.png
+ LICENSE
+ https://github.com/astar-development/astar-dev-functional-extensions/
+ Readme.md
+ Update.
+ True
+ git
+ https://github.com/astar-development/astar-dev-functional-extensions.git
+ snupkg
+ net9.0
+ AStar.Dev.Functional.Extensions
+ 0.1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ 1701;1702;
+
+
+
+ True
+ 1701;1702;
+
+
+
diff --git a/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml
new file mode 100644
index 0000000..034a31a
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml
@@ -0,0 +1,148 @@
+
+
+
+ AStar.Dev.Functional.Extensions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The class that exists for when we have no object
+
+
+
+
+ The Value will always return an instance of , it can never return an actual value
+
+
+
+
+ The Of{T} method can be used to return an instance of the generic when there is no actual value to return
+
+ The type of the original object that this instance of is replacing
+ The appropriate type of
+
+
+
+ The class replaces the object when there is no object available
+
+ The type of the original object
+
+
+
+ The ToString method is overridden to always return "None"
+
+ "None" no matter what the original type was
+
+
+
+ The class that contains the original object
+
+
+
+
+ The Optional method will convert the 'raw' object to an
+
+ The type of the original object
+ The object to return as an option
+ The original object as an option (and implemented as an instance of
+
+
+
+ The class contains a basic set of extension methods to help map, filter, etc. the original object
+
+
+
+
+ The Map{T,TResult} method will either map the Some{T} to a new Some or return a new None of the specified result type
+
+ The type of the source object
+ The type of object expected from the map
+ The object to map
+ The Map function
+ Either a new Some or a new None of the specified type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The generic class contains the result for a successful method
+
+ The type of the return object
+
+
+ The content (actual result) object
+
+
+
+ The generic class contains the result for a successful method
+
+ The type of the return object
+
+
+ The content (actual result) object
+
+
+
+ The content (actual result) object
+
+
+
+
+ Overrides the default ToString to return the object type or <null>
+
+
+ Once the ToJson method (in AStar.Dev.Utilities) respects the 'Mask', 'Ignore' etc. attributes, we can reconsider whether this method returns the actual object
+
+ The object type name or null.
+
+
+
diff --git a/src/AStar.Dev.Functional.Extensions/AStar.ico b/src/AStar.Dev.Functional.Extensions/AStar.ico
new file mode 100644
index 0000000..0b9aea4
Binary files /dev/null and b/src/AStar.Dev.Functional.Extensions/AStar.ico differ
diff --git a/src/AStar.Dev.Functional.Extensions/AStar.png b/src/AStar.Dev.Functional.Extensions/AStar.png
new file mode 100644
index 0000000..8cac0a1
Binary files /dev/null and b/src/AStar.Dev.Functional.Extensions/AStar.png differ
diff --git a/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs b/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs
new file mode 100644
index 0000000..9c97003
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs
@@ -0,0 +1,20 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+///
+public static class EnumerableExtensions
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Option FirstOrNone(this IEnumerable sequence, Func predicate)
+ {
+ return sequence.Where(predicate)
+ .Select>(x => x)
+ .DefaultIfEmpty(None.Value)
+ .First();
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/LICENSE b/src/AStar.Dev.Functional.Extensions/LICENSE
new file mode 100644
index 0000000..0b1b024
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 AStar Development, Jason Barden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/AStar.Dev.Functional.Extensions/None.cs b/src/AStar.Dev.Functional.Extensions/None.cs
new file mode 100644
index 0000000..ae933a4
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/None.cs
@@ -0,0 +1,22 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+/// The class that exists for when we have no object
+///
+public sealed class None
+{
+ ///
+ /// The Value will always return an instance of , it can never return an actual value
+ ///
+ public static None Value { get; } = new();
+
+ ///
+ /// The Of{T} method can be used to return an instance of the generic when there is no actual value to return
+ ///
+ /// The type of the original object that this instance of is replacing
+ /// The appropriate type of
+ public static None Of()
+ {
+ return new();
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/None{T}.cs b/src/AStar.Dev.Functional.Extensions/None{T}.cs
new file mode 100644
index 0000000..120e54d
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/None{T}.cs
@@ -0,0 +1,17 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+/// The class replaces the object when there is no object available
+///
+/// The type of the original object
+public sealed class None : Option
+{
+ ///
+ /// The ToString method is overridden to always return "None"
+ ///
+ /// "None" no matter what the original type was
+ public override string ToString()
+ {
+ return "None";
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/Option.cs b/src/AStar.Dev.Functional.Extensions/Option.cs
new file mode 100644
index 0000000..b3cbb4b
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/Option.cs
@@ -0,0 +1,20 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+/// The class that contains the original object
+///
+#pragma warning disable CA1716 // Rename type Option so that it no longer conflicts with the reserved language keyword 'Option'
+public static class Option
+#pragma warning restore CA1716
+{
+ ///
+ /// The Optional method will convert the 'raw' object to an
+ ///
+ /// The type of the original object
+ /// The object to return as an option
+ /// The original object as an option (and implemented as an instance of
+ public static Option Optional(this T obj)
+ {
+ return new Some(obj);
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs b/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs
new file mode 100644
index 0000000..5c3b401
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs
@@ -0,0 +1,53 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+/// The class contains a basic set of extension methods to help map, filter, etc. the original object
+///
+public static class OptionExtensions
+{
+ ///
+ /// The Map{T,TResult} method will either map the Some{T} to a new Some or return a new None of the specified result type
+ ///
+ /// The type of the source object
+ /// The type of object expected from the map
+ /// The object to map
+ /// The Map function
+ /// Either a new Some or a new None of the specified type
+ public static Option Map(this Option obj, Func map)
+ {
+ return obj is Some some ? new Some(map(some.Content)) : new None();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Option Filter(this Option obj, Func predicate)
+ {
+ return obj is Some some && !predicate(some.Content) ? new None() : obj;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static T Reduce(this Option obj, T substitute)
+ {
+ return obj is Some some ? some.Content : substitute;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static T Reduce(this Option obj, Func substitute)
+ {
+ return obj is Some some ? some.Content : substitute();
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/Option{T}.cs b/src/AStar.Dev.Functional.Extensions/Option{T}.cs
new file mode 100644
index 0000000..794828d
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/Option{T}.cs
@@ -0,0 +1,25 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+///
+///
+#pragma warning disable CA1716 // Rename type Option so that it no longer conflicts with the reserved language keyword 'Option'
+public abstract class Option
+#pragma warning restore CA1716
+{
+ ///
+ ///
+ ///
+ public static implicit operator Option(None _)
+ {
+ return new None();
+ }
+
+ ///
+ ///
+ ///
+ public static implicit operator Option(T value)
+ {
+ return new Some(value);
+ }
+}
diff --git a/src/AStar.Dev.Functional.Extensions/Readme.md b/src/AStar.Dev.Functional.Extensions/Readme.md
new file mode 100644
index 0000000..16dacd1
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/Readme.md
@@ -0,0 +1 @@
+Update
\ No newline at end of file
diff --git a/src/AStar.Dev.Functional.Extensions/Some.cs b/src/AStar.Dev.Functional.Extensions/Some.cs
new file mode 100644
index 0000000..53540f6
--- /dev/null
+++ b/src/AStar.Dev.Functional.Extensions/Some.cs
@@ -0,0 +1,28 @@
+namespace AStar.Dev.Functional.Extensions;
+
+///
+/// The generic class contains the result for a successful method
+///
+/// The type of the return object
+///
+///
+/// The content (actual result) object
+public sealed class Some(T content) : Option
+{
+ ///
+ /// The content (actual result) object
+ ///
+ public T Content { get; } = content;
+
+ ///
+ /// Overrides the default ToString to return the object type or <null>
+ ///
+ ///
+ /// Once the ToJson method (in AStar.Dev.Utilities) respects the 'Mask', 'Ignore' etc. attributes, we can reconsider whether this method returns the actual object
+ ///
+ /// The object type name or null.
+ public override string ToString()
+ {
+ return Content?.ToString() ?? "";
+ }
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj b/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj
new file mode 100644
index 0000000..5556a7d
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj
@@ -0,0 +1,51 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+ AStar.Dev.Functional.Extensions
+ True
+ latest-recommended
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ 1701;1702;IDE0058;
+
+
+
+ True
+ 1701;1702;IDE0058;
+
+
+
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs
new file mode 100644
index 0000000..162c82b
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Functional.Extensions;
+
+[TestSubject(typeof(EnumerableExtensions))]
+public class EnumerableExtensionsShould
+{
+ [Fact]
+ public void ReturnTheExpectedFirstObject()
+ {
+ var list = new List { 1, 2, 3 };
+
+ list.FirstOrNone(i => i == 2).ShouldBeAssignableTo>();
+ }
+
+ [Fact]
+ public void ReturnTheExpectedNoneObject()
+ {
+ var list = new List { 1, 2, 3 };
+
+ list.FirstOrNone(i => i > 3).ShouldBeAssignableTo>();
+ }
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs
new file mode 100644
index 0000000..484b63c
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs
@@ -0,0 +1,13 @@
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Functional.Extensions;
+
+[TestSubject(typeof(None<>))]
+public class GenericNoneShould
+{
+ [Fact]
+ public void OverrideToStringAsExpected()
+ {
+ None.Of().ToString().ShouldBe("None");
+ }
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs
new file mode 100644
index 0000000..0ed5353
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs
@@ -0,0 +1,16 @@
+namespace AStar.Dev.Functional.Extensions;
+
+internal sealed class MockClass
+{
+ public string SomeText { get; set; } = "SomeText";
+
+ public static MockClass Create()
+ {
+ return new() { SomeText = "MockClassCreate" };
+ }
+}
+
+internal sealed class MockClass2
+{
+ public string SomeText { get; set; } = "SomeText";
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs
new file mode 100644
index 0000000..0e97bd1
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs
@@ -0,0 +1,19 @@
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Functional.Extensions;
+
+[TestSubject(typeof(None))]
+public class NoneShould
+{
+ [Fact]
+ public void ReturnNone()
+ {
+ None.Value.ShouldBeAssignableTo();
+ }
+
+ [Fact]
+ public void ReturnNoneOf()
+ {
+ None.Of().ShouldBeAssignableTo>();
+ }
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs
new file mode 100644
index 0000000..fd0a3a9
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs
@@ -0,0 +1,87 @@
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Functional.Extensions;
+
+[TestSubject(typeof(OptionExtensions))]
+public class OptionExtensionsShould
+{
+ [Fact]
+ public void MapTheSomeObjectToTheNewSomeObject()
+ {
+ var obj = new Some(new ());
+
+ var result = obj.Map(mockClass => new MockClass2 { SomeText = mockClass.SomeText });
+
+ result.ShouldBeAssignableTo>();
+ }
+
+ [Fact]
+ public void MapTheNoneObjectToTheNewNone()
+ {
+ var obj = new None();
+
+ var result = obj.Map(mockClass => new MockClass2 { SomeText = mockClass.SomeText });
+
+ result.ShouldBeAssignableTo>();
+ }
+
+ [Fact]
+ public void FilterTheSomeOptionBasedOnThePredicate()
+ {
+ var list = new List { 1, 2, 3, 4, 5 }.Optional();
+
+ var result = list.Filter(item => item.Contains(1));
+
+ result.ShouldBeAssignableTo>>();
+ }
+
+ [Fact]
+ public void FilterTheSomeOptionBasedOnThePredicateToNone()
+ {
+ var list = new List { 1, 2, 3, 4, 5 }.Optional();
+
+ var result = list.Filter(item => item.Contains(1234));
+
+ result.ShouldBeAssignableTo>>();
+ }
+
+ [Fact]
+ public void ReduceTheSomeContentToContainingType()
+ {
+ var obj = new Some(new () { SomeText = "SomeText" });
+
+ var result = obj.Reduce(new MockClass { SomeText = "SubstituteSomeText" });
+
+ result.SomeText.ShouldBe("SomeText");
+ }
+
+ [Fact]
+ public void ReduceTheSomeContentToContainingTypeUsingTheSubstituteObject()
+ {
+ var obj = new None();
+
+ var result = obj.Reduce(new MockClass { SomeText = "SubstituteSomeText" });
+
+ result.SomeText.ShouldBe("SubstituteSomeText");
+ }
+
+ [Fact]
+ public void ReduceTheSomeContentToTheContainingType()
+ {
+ var obj = new Some(new () { SomeText = "SomeText" });
+
+ var result = obj.Reduce(MockClass.Create);
+
+ result.SomeText.ShouldBe("SomeText");
+ }
+
+ [Fact]
+ public void ReduceTheSomeContentToContainingTypeUsingTheSubstituteFunction()
+ {
+ var obj = new None();
+
+ var result = obj.Reduce(MockClass.Create);
+
+ result.SomeText.ShouldBe("MockClassCreate");
+ }
+}
diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs
new file mode 100644
index 0000000..5d79499
--- /dev/null
+++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs
@@ -0,0 +1,19 @@
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Functional.Extensions;
+
+[TestSubject(typeof(Option))]
+public class OptionShould
+{
+ [Fact]
+ public void ReturnTheSpecifiedObjectAsAnOptionalObject()
+ {
+ new MockClass().Optional().ShouldBeAssignableTo