├── .editorconfig ├── .gitignore ├── .paket └── Paket.Restore.targets ├── CHANGELOG.md ├── Directory.Build.props ├── LICENSE ├── README.md ├── SharpX.sln ├── assets └── icon.png ├── paket.dependencies ├── paket.lock ├── src ├── .editorconfig └── SharpX │ ├── Extensions │ ├── EnumerableExtensions.cs │ ├── ExceptionExtensions.cs │ ├── FSharpResultExtensions.cs │ ├── LoggerExtensions.cs │ ├── ObjectExtensions.cs │ ├── StringExtensions.cs │ └── UnitExtensions.cs │ ├── FsCheck │ ├── ArbitraryIntegerSeq.cs │ ├── ArbitraryString.cs │ ├── ArbitraryStringSeq.cs │ └── ArbitraryValue.cs │ ├── Primitives.cs │ ├── SharpX.csproj │ ├── Strings.cs │ ├── Types │ ├── Either │ │ ├── Either.cs │ │ ├── EitherExtensions.cs │ │ ├── EitherOfT.cs │ │ └── EitherType.cs │ ├── Maybe │ │ ├── Maybe.cs │ │ ├── MaybeExtensions.cs │ │ ├── MaybeOfT.cs │ │ └── MaybeType.cs │ ├── Result │ │ ├── Bad.cs │ │ ├── Ok.cs │ │ ├── ResultExtensions.cs │ │ ├── ResultOfT.cs │ │ ├── ResultType.cs │ │ └── Trial.cs │ └── Unit.cs │ ├── Utilities │ ├── CryptoRandom.cs │ └── Guard.cs │ └── paket.references └── tests ├── .editorconfig └── SharpX.Tests ├── Outcomes ├── EitherSpecs.cs ├── EnumerableExtensionsSpecs.cs ├── FSharpResultExtensionsSpecs.cs ├── LoggerExtensionsSpecs.cs ├── MaybeSpecs.cs ├── ObjectExtensionsSpecs.cs ├── PrimitivesSpecs.cs ├── ResultSpecs.cs ├── StringExtensionsSpecs.cs ├── StringsSpecs.cs ├── TrailSpecs.cs └── UnitSpecs.cs ├── SharpX.Tests.csproj ├── Tests ├── NightClubsValidation.cs └── SimpleValidation.cs └── paket.references /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [] 4 | end_of_line = crlf 5 | insert_final_newline = true 6 | 7 | [*.xml] 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.{json,yml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | .vs 4 | tools/ 5 | [Oo]bj/ 6 | [Bb]in/ 7 | .nuget/ 8 | _ReSharper.* 9 | packages/ 10 | artifacts/ 11 | *.user 12 | *.suo 13 | *.userprefs 14 | *DS_Store 15 | *.sln.ide 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [8.3.6] - 2025-04-06 9 | 10 | - Added agressive inlining for `Maybe` primitives. 11 | - Set `ToMaybe` parameter as nullable. 12 | 13 | ## [8.3.4] - 2025-03-29 14 | 15 | - Added `Primitives::SafeInvoke` overload. 16 | 17 | ## [8.3.2] - 2025-03-29 18 | 19 | - Implemented `Primitives::SafeInvoke` method. 20 | 21 | ## [8.3.0] - 2025-02-23 22 | 23 | - Removed `Outcome` type from class library. 24 | - Reorganized result types source files. 25 | - Fixed compiler warnings in Maybe implementation. 26 | - Fixed compiler warnings in Either implementation. 27 | - Set Unit type as readonly struct. 28 | - Fixed compiler warnings in Result implementation. 29 | 30 | ## [8.1.0] - 2025-02-09 31 | 32 | - Improved `LoggerExtensions` methods. 33 | - Implemented `LoggerExtensions::WarnWith` method. 34 | - Updated `LoggerExtensions` methods signature and parameters validation. 35 | - Added `LoggerExtensions` methods for handling booleans. 36 | 37 | ## [8.0.0] - 2025-02-05 38 | 39 | - Updated .NET targets and dependencies. 40 | - Implemented `Guard::DisallowNothing` method. 41 | 42 | ## [6.4.6] - 2024-06-01 43 | 44 | - Added `LoggerExtensions` class. 45 | - Added `EnumerableExtensions::Batch` method from MoreLINQ. 46 | - Implemented `UnitExtensions::And` method. 47 | 48 | ## [6.4.2] - 2024-01-14 49 | 50 | - Implemented string utility methods to remove diacritics. 51 | 52 | ## [6.4.0] - 2024-01-14 53 | 54 | - Renamed method `DisallowAnyEmptyWhitespace` as `DisallowEmptyWhitespace`. 55 | - Used `ArgumentOutOfRangeException` in `Guard` when appropriate. 56 | - Updated `Guard::DisallowMalformedGuid` exception message. 57 | - Target set to .NET Standard 2.0. 58 | - Fixed int genenation in `Primitives::GenerateSeq` method. 59 | 60 | ## [6.3.6] - 2023-12-18 61 | 62 | - Implemented `EnumerableExtensions::DistinctCount` method. 63 | - Fixed missing `this` operator in `ObjectExtensions::IsNumber` method. 64 | - Implemented `StringExtensions::ToUri` method. 65 | - Updated `EqualsIgnoreCase` to campare null values when safe mode is on. 66 | - Made parameter nullable in `StringExtensions::IsEmpty` method. 67 | - Implemented `Strings::ReverseCase` method and relative extension method. 68 | - Implemented `Strings::RandomizeCase` method and relative extension method. 69 | - Made parameter nullable in `StringExtensions::EqualsIgnoreCase` method. 70 | 71 | ## [6.3.2] - 2023-09-28 72 | 73 | - Implemented `NormalizeToEmpty` method and extension method. 74 | - Implemented `IsNumber` method and extension method. 75 | - Fix: `Arbitrary*` types made public. 76 | - Fixed a test. 77 | 78 | ## [6.3.0] - 2023-08-20 79 | 80 | - Improved implementation of `Primitives::ChanceOf`. 81 | - Improved `EnumerableExtensions::Intersperse` to support randomness. 82 | - Improved `EnumerableExtensions::Intersperse` to support nulls. 83 | - Moved FsCheck generators to main project. 84 | - Implemented `Primitives::GenerateSeq` method. 85 | - Refactored FsCheck generators for version 3.0.0-alpha4. 86 | - Updated `Strings::Generate` to function without length parameter. 87 | - Implemented `Primitives::GenerateSeq` overload for int, double and string. 88 | - Fixed `EnumerableExtensions::Choice` extension method. 89 | - Refactored `ArbitraryValue` to generate default values. 90 | - Updated `ArbitraryString` generator to include null values. 91 | - Updated `ArbitraryStringSeq` generator to include null values. 92 | - Updated `ArbitraryIntegerSeq` generator implementation. 93 | 94 | ## [6.2.1] - 2023-05-26 95 | 96 | - Implemented `DisallowEmptyEnumerable` guard method. 97 | 98 | ## [6.2.0] - 2023-05-26 99 | 100 | - Removed UnitExtensions class. 101 | - Implemented `StringExtensions::ToGuid` extension method. 102 | - Reimplemented `SafeSubstring` as `Substring` overload. 103 | 104 | ## [6.1.0] - 2023-04-25 105 | 106 | - Restored target `netcoreapp3.1`. 107 | 108 | ## [6.0.3] - 2023-04-20 109 | 110 | - Implemented `Primitives::ChanceOf` method. 111 | 112 | ## [6.0.2] - 2023-04-16 113 | 114 | - Implemented `StringExtensions::IsEmpty` extension method. 115 | - Implemented `EnumerableExtensions::IsEmpty` extension method. 116 | 117 | ## [6.0.1] - 2023-04-15 118 | 119 | - Set Maybe type as readonly struct. 120 | - Added `Maybe::DoAsync` extension overloads. 121 | - Added `UnitExtensions::ToUnit` extension method. 122 | 123 | - Implemented `ObjectExtensions::ToUnit`. 124 | 125 | ## [6.0.0-preview.1] - 2022-03-29 126 | 127 | - Set target to .NET 6.0 only. 128 | - `CryptoRandom` marked obsolete. 129 | - Renamed `Guard::RestrictArraySize` to `DisallowArraySize`. 130 | - Renamed `Guard::AllowGuidOnly` to `DisallowMalformedGuid`. 131 | 132 | ## [1.1.11] - 2022-03-20 133 | 134 | - Implemented `StringExtensions::StartsWithIgnoreCase`. 135 | 136 | ## [1.1.10] - 2022-03-20 137 | 138 | - Implemented `StringExtensions::EqualsIgnoreCase`. 139 | - Implemented `StringExtensions::ContainsIgnoreCase`. 140 | 141 | ## [1.1.8] - 2022-01-30 142 | 143 | - Implemented `EnumerableExtensions::Shuffle` method. 144 | - Fixed and improved `Strings::Generate` method. 145 | 146 | ## [1.1.7] - 2022-01-30 147 | 148 | - Added `Strings::SafeSubstring` method (and relative extension method). 149 | - Fixed `Strings::Generate` method. 150 | 151 | ## [1.1.6] - 2022-01-22 152 | 153 | - Added `Strings::IsEmptyWhitespace` method (and relative extension method). 154 | - Updated `WhiteSpace` to `Whitespace` in `Guard` class. 155 | - Updated `Strings::Mangle` to use all special characters. 156 | - Allowed `Strings::Generate` customization options and prefix input. 157 | 158 | ## [1.1.5] - 2022-01-16 159 | 160 | - Fixed name of `StringExtensions::IsWhitespace` to `ContainsWhitespace`. 161 | 162 | ## [1.1.4] - 2021-01-12 163 | 164 | - Added `Primitives::ToEnumerable` method (and relative extension method). 165 | - Added non-extension version of `ExceptionExtensions::Format` to `Primitives` class. 166 | - Moved `ToEnumerable` method to `EnumerableExtensions`. 167 | - Added `Unit::DoAsync` method. 168 | - Added `EnumerableExtensions::ForEachAsync` method. 169 | 170 | ## [1.1.2] - 2021-12-19 171 | 172 | - Updated `Strings::ContainsSpecialChar`. 173 | 174 | ## [1.1.1] - 2021-12-19 175 | 176 | - Added `IsSpecialChar` method to `Strings` and `StringsExtensions` classes. 177 | - Added `ContainsSpecialChar` method to `Strings` and `StringsExtensions` classes. 178 | 179 | ## [1.1.1] - 2021-12-18 180 | 181 | - Renamed `IsWhitespace` to a more semantically correct `ContainsWhitespace`. 182 | 183 | ## [1.1.0] - 2021-12-04 184 | 185 | - Moved `ToMaybe` `FirstOrNothing`, `LastOrNothing`, `SingleOrNothing`, `ElementAtOrNothing` to `MaybeExtensions` in root namespace. 186 | - Updated `ToUpperFirstLetter` and `ToLowerFirstLetter` and renamed as `ToUpperFirst` and `ToLowerFirst` (`Strings` class). 187 | - Renamed `Strings::IsWhiteSpace` as `IsWhitespace`. 188 | - Added `MaybeExtensions::ToJust` method. 189 | - Updated `Maybe::Equals(object)` implementation. 190 | - Updated `Guard::DisallowDefault`. 191 | 192 | ## [1.0.5] - 2021-11-28 193 | 194 | - Inverted oreder of members in `ResultType`. 195 | 196 | ## [1.0.4] - 2021-11-28 197 | 198 | - Moved classes with extension methods to `SharpX.Extensions` namespace. 199 | - Moved `StringUtil::Generate` to new `Strings` class. 200 | - Moved `CharExtensions::Replicate` as `Strings::ReplicateChar` 201 | - Moved `CharExtensions::Replicate` to `StringExtensions`. 202 | - Removed `CharExtensions` class. 203 | - All `StringExtensions` methods are implemented in `Strings` class. 204 | - Added `Guard` class. 205 | - Consumed `Guard` class extensively in the project. 206 | 207 | ## [1.0.3] - 2021-11-20 208 | 209 | - Renamed `StringExtensions::StripTags` as `StripTag`. 210 | 211 | ## [1.0.2] - 2021-11-19 212 | 213 | - Renamed `StringExtensions::StripML` as `StripTags`. 214 | - Updated implemetation of `Maybe::GetHashCode`. 215 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory) 4 | false 5 | 6 | 7 | $(DefineConstants);NETFRAMEWORK 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers 13 | all 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 - 2025 Giacomo Stelluti Scala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpX 2 | 3 | ![SharpX Logo](/assets/icon.png) 4 | 5 | SharpX is derived from [CSharpx](https://github.com/gsscoder/csharpx) 2.8.0-rc.2 (_which was practically a stable_) and [RailwaySharp](https://github.com/gsscoder/railwaysharp) 1.2.2. 6 | 7 | ![CSharpX Downloads](https://img.shields.io/nuget/dt/CSharpX.svg) 8 | 9 | While both projects were meant mainly for source inclusion, SharpX is designed to be pulled from [NuGet](https://www.nuget.org/). 10 | 11 | The library contains **functional types** and other utilities, including **test oriented tools**. It follows the _don't reinvent the wheel_ philosophy. This project was originally inspired by [Real-World Functional Programming](https://www.amazon.com/Real-World-Functional-Programming-Tomas-Petricek/dp/1933988924/ref=sr_1_1?keywords=Real-World+Functional+Programming&qid=1580118924&s=books&sr=1-1) and includes code from [MoreLINQ](https://github.com/morelinq/MoreLINQ). 12 | 13 | ![SharpX Downloads](https://img.shields.io/nuget/dt/SharpX.svg) 14 | 15 | ## Targets 16 | 17 | - .NET Standard 2.1 18 | - .NET 8.0 19 | 20 | ## Install via NuGet 21 | 22 | You can install it via NuGet: 23 | 24 | ```sh 25 | $ dotnet add package SharpX --version 8.3.6 26 | Determining projects to restore... 27 | ... 28 | ``` 29 | 30 | ## Overview 31 | 32 | All types are available in the root namespace. Extension methods (except the very specific ones) are in `SharpX.Extensions`. 33 | `SharpX.FsCheck` contains [FsCheck](https://github.com/fscheck/FsCheck) generators for property-based testing. 34 | 35 | ## Maybe 36 | 37 | - Encapsulates an optional value that can contain a value or being empty. 38 | - Similar to F# `'T option` / Haskell `data Maybe a = Just a | Nothing` type. 39 | 40 | ```csharp 41 | var greet = true; 42 | var value = greet ? "world".ToMaybe() : Maybe.Nothing(); 43 | value.Match( 44 | who => Console.WriteLine($"hello {who}!"), 45 | () => Environment.Exit(1)); 46 | ``` 47 | 48 | - Supports LINQ syntax: 49 | 50 | ```csharp 51 | var result1 = (30).ToJust(); 52 | var result2 = (10).ToJust(); 53 | var result3 = (2).ToJust(); 54 | 55 | var sum = from r1 in result1 56 | from r2 in result2 57 | where r1 > 0 58 | select r1 - r2 into temp 59 | from r3 in result3 60 | select temp * r3; 61 | 62 | var value = sum.FromJust(); // outcome: 40 63 | ``` 64 | 65 | - Features sequence extensions: 66 | 67 | ```csharp 68 | var maybeFirst = new int[] {0, 1, 2}.FirstOrNothing(x => x == 1) 69 | // outcome: Just(1) 70 | ``` 71 | 72 | ## Either 73 | 74 | - Represents a value that can contain either a value or an error. 75 | - Similar to Haskell `data Either a b = Left a | Right b` type. 76 | - Similar also to F# `Choice<'T, 'U>`. 77 | - Like in Haskell the convention is to let `Right` case hold the value and `Left` keep track of error or similar data. 78 | - If you want a more complete implementation of this kind of types, consider using `Result`. 79 | 80 | ## Result 81 | 82 | This type was originally present in RailwaySharp. Check the test project to see a more complete usage example. 83 | 84 | ``` csharp 85 | public static Result ValidateInput(Request input) 86 | { 87 | if (input.Name == string.Empty) { 88 | return Result.FailWith("Name must not be blank"); 89 | } 90 | if (input.EMail == string.Empty) { 91 | return Result.FailWith("Email must not be blank"); 92 | } 93 | return Result.Succeed(input); 94 | } 95 | 96 | var request = new Request { Name = "Giacomo", EMail = "gsscoder@gmail.com" }; 97 | var result = Validation.ValidateInput(request); 98 | result.Match( 99 | (x, msgs) => { Logic.SendMail(x.EMail); }, 100 | msgs => { Logic.HandleFailure(msgs) }); 101 | ``` 102 | 103 | ## Unit 104 | 105 | - `Unit` is similar to `void` but, since it's a **real** type. `void` is not, in fact you can't declare a variable of that type. `Unit` allows the use functions without a result in a computation (**functional style**). It's essentially **F#** `unit` and **Haskell** `Unit`. 106 | 107 | ```csharp 108 | // prints each word and returns 0 to the shell 109 | static int Main(string[] args) 110 | { 111 | var sentence = "this is a sentence"; 112 | return (from _ in 113 | from word in sentence.Split() 114 | select Unit.Do(() => Console.WriteLine(word)) 115 | select 0).Distinct().Single(); 116 | } 117 | ``` 118 | 119 | ## FSharpResultExtensions 120 | 121 | - Convenient extension methods to consume `FSharpResult` in simple and functional for other **.NET** languages. 122 | 123 | ```csharp 124 | // pattern match like 125 | var result = Query.GetStockQuote("ORCL"); 126 | result.Match( 127 | quote => Console.WriteLine($"Price: {quote.Price}"), 128 | error => Console.WriteLine($"Trouble: {error}")); 129 | // mapping 130 | var result = Query.GetIndex(".DJI"); 131 | result.Map( 132 | quote => CurrencyConverter.Change(quote.Price, "$", "€")); 133 | ``` 134 | 135 | - Blog [post](https://gsscoder.github.io/consuming-fsharp-results-in-c/) about it. 136 | 137 | ## StringExtensions 138 | 139 | - General purpose and randomness string manipulation extensions. Few more methods and non-extension version can be found in `Strings` class. 140 | 141 | ```csharp 142 | Console.WriteLine( 143 | "\t[hello\world@\t".Sanitize(normalizeWhiteSpace: true)); 144 | // outcome: ' hello world ' 145 | 146 | Console.WriteLine( 147 | "I want to change a word".ApplyAt(4, word => word.Mangle())); 148 | // outcome like: 'I want to change &a word' 149 | ``` 150 | 151 | ## EnumerableExtensions 152 | 153 | - Most useful extension methods from [MoreLINQ](https://github.com/morelinq/MoreLINQ). 154 | - Some of these reimplemnted (e.g. `Choose` using `Maybe`): 155 | 156 | ```csharp 157 | var numbers = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 158 | var evens = numbers.Choose(x => x % 2 == 0 159 | ? x.ToJust() 160 | : Maybe.Nothing()); 161 | // outcome: {0, 2, 4, 6, 8} 162 | ``` 163 | 164 | - With other useful methods too: 165 | 166 | ```CSharp 167 | var sequence = new int[] {0, 1, 2, 3, 4}.Intersperse(5); 168 | // outcome: {0, 5, 1, 5, 2, 5, 3, 5, 4} 169 | var element = sequence.Choice(); 170 | // will choose a random element 171 | var sequence = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.ChunkBySize(3); 172 | // outcome: { [0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10] } 173 | ``` 174 | 175 | ## Icon 176 | 177 | [Tool](https://thenounproject.com/search/?q=tool&i=3902696) icon designed by Cattaleeya Thongsriphong from [The Noun Project](https://thenounproject.com/) 178 | -------------------------------------------------------------------------------- /SharpX.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33829.357 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpX", "src\SharpX\SharpX.csproj", "{2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpX.Tests", "tests\SharpX.Tests\SharpX.Tests.csproj", "{BBDB5904-5A75-49E7-A57E-EB92E32A7527}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{434B6A60-0D64-4958-991F-17DBC9DC0F18}" 11 | ProjectSection(SolutionItems) = preProject 12 | paket.dependencies = paket.dependencies 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F51FF1E6-EC52-4C95-B0FE-DD02F2B43D30}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{2FB8C816-AE08-40AE-A744-9FF6A78CB396}" 18 | ProjectSection(SolutionItems) = preProject 19 | CHANGELOG.md = CHANGELOG.md 20 | LICENSE = LICENSE 21 | README.md = README.md 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527} = {F51FF1E6-EC52-4C95-B0FE-DD02F2B43D30} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {FD40382B-FB43-49AC-A527-50B4015C7E8E} 47 | SolutionGuid = {A02DD375-4885-4373-B792-B2541B428719} 48 | SolutionGuid = {9EDE47D4-D8AA-406C-A4FC-DE8735C3B386} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsscoder/sharpx/d091e179a3eecbbb009f4c75255529e3dfbe949c/assets/icon.png -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | group main 2 | source https://www.nuget.org/api/v2 3 | framework: netstandard2.1, net8.0 4 | nuget FSharp.Core 9.0.101 5 | nuget FsCheck 3.1.0 6 | nuget Microsoft.NETCore.Platforms 7.0.4 7 | nuget Microsoft.Extensions.Logging.Abstractions 9.0.1 8 | 9 | group tests 10 | source https://www.nuget.org/api/v2 11 | framework: net8.0 12 | nuget Microsoft.NET.Test.Sdk 17.12.0 13 | nuget coverlet.collector 6.0.4 14 | nuget xunit 2.9.3 15 | nuget xunit.runner.visualstudio 3.0.1 16 | nuget FsCheck 3.1.0 17 | nuget FsCheck.Xunit 3.1.0 18 | nuget FluentAssertions 8.0.1 19 | nuget Bogus 35.6.1 20 | nuget WaffleGenerator 4.2.2 21 | nuget WaffleGenerator.Bogus 4.2.2 22 | nuget Microsoft.NETCore.Platforms 7.0.4 23 | nuget Moq 4.20.72 24 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | RESTRICTION: || (== net8.0) (== netstandard2.1) 2 | NUGET 3 | remote: https://www.nuget.org/api/v2 4 | FsCheck (3.1) 5 | FSharp.Core (>= 5.0.2) 6 | FSharp.Core (9.0.101) 7 | Microsoft.Extensions.DependencyInjection.Abstractions (9.0.1) 8 | Microsoft.Extensions.Logging.Abstractions (9.0.1) 9 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.1) 10 | System.Buffers (>= 4.5.1) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 11 | System.Diagnostics.DiagnosticSource (>= 9.0.1) 12 | System.Memory (>= 4.5.5) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 13 | Microsoft.NETCore.Platforms (7.0.4) 14 | System.Buffers (4.6) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 15 | System.Diagnostics.DiagnosticSource (9.0.1) 16 | System.Memory (>= 4.5.5) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 17 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 18 | System.Memory (4.6) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 19 | System.Buffers (>= 4.6) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.1)) (== netstandard2.1) 20 | System.Numerics.Vectors (>= 4.6) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.1)) (== netstandard2.1) 21 | System.Runtime.CompilerServices.Unsafe (>= 6.1) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netcoreapp2.1)) (== netstandard2.1) 22 | System.Numerics.Vectors (4.6) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 23 | System.Runtime.CompilerServices.Unsafe (6.1) - restriction: || (&& (== net8.0) (>= net462)) (== netstandard2.1) 24 | 25 | GROUP tests 26 | RESTRICTION: == net8.0 27 | NUGET 28 | remote: https://www.nuget.org/api/v2 29 | Bogus (35.6.1) 30 | Castle.Core (5.2.1) 31 | System.Diagnostics.EventLog (>= 6.0) 32 | coverlet.collector (6.0.4) 33 | FluentAssertions (8.0.1) 34 | FsCheck (3.1) 35 | FSharp.Core (>= 5.0.2) 36 | FsCheck.Xunit (3.1) 37 | FsCheck (3.1) 38 | FSharp.Core (>= 5.0.2) 39 | xunit.extensibility.execution (>= 2.4.1 < 3.0) 40 | FSharp.Core (9.0.201) 41 | Microsoft.CodeCoverage (17.13) 42 | Microsoft.NET.Test.Sdk (17.12) 43 | Microsoft.CodeCoverage (>= 17.12) 44 | Microsoft.TestPlatform.TestHost (>= 17.12) 45 | Microsoft.NETCore.Platforms (7.0.4) 46 | Microsoft.TestPlatform.ObjectModel (17.13) 47 | System.Reflection.Metadata (>= 1.6) 48 | Microsoft.TestPlatform.TestHost (17.13) 49 | Microsoft.TestPlatform.ObjectModel (>= 17.13) 50 | Newtonsoft.Json (>= 13.0.1) 51 | Moq (4.20.72) 52 | Castle.Core (>= 5.1.1) 53 | Newtonsoft.Json (13.0.3) 54 | System.Collections.Immutable (9.0.3) 55 | System.Diagnostics.EventLog (9.0.3) 56 | System.Reflection.Metadata (9.0.3) 57 | System.Collections.Immutable (>= 9.0.3) 58 | WaffleGenerator (4.2.2) 59 | WaffleGenerator.Bogus (4.2.2) 60 | Bogus (>= 35.6.1) 61 | WaffleGenerator (>= 4.2.2) 62 | xunit (2.9.3) 63 | xunit.analyzers (>= 1.18) 64 | xunit.assert (>= 2.9.3) 65 | xunit.core (2.9.3) 66 | xunit.abstractions (2.0.3) 67 | xunit.analyzers (1.21) 68 | xunit.assert (2.9.3) 69 | xunit.core (2.9.3) 70 | xunit.extensibility.core (2.9.3) 71 | xunit.extensibility.execution (2.9.3) 72 | xunit.extensibility.core (2.9.3) 73 | xunit.abstractions (>= 2.0.3) 74 | xunit.extensibility.execution (2.9.3) 75 | xunit.extensibility.core (2.9.3) 76 | xunit.runner.visualstudio (3.0.1) 77 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{csproj,config}] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | # C# files 6 | [*.cs] 7 | 8 | #### Core EditorConfig Options #### 9 | 10 | # Indentation and spacing 11 | indent_size = 4 12 | indent_style = space[] 13 | tab_width = 4 14 | 15 | # New line preferences 16 | end_of_line = crlf 17 | insert_final_newline = true 18 | 19 | #### .NET Coding Conventions #### 20 | 21 | # Organize usings 22 | dotnet_separate_import_directive_groups = false 23 | dotnet_sort_system_directives_first = true 24 | file_header_template = unset 25 | 26 | # this. and Me. preferences 27 | dotnet_style_qualification_for_event = false 28 | dotnet_style_qualification_for_field = false 29 | dotnet_style_qualification_for_method = false 30 | dotnet_style_qualification_for_property = false 31 | 32 | # Language keywords vs BCL types preferences 33 | dotnet_style_predefined_type_for_locals_parameters_members = true 34 | dotnet_style_predefined_type_for_member_access = true 35 | 36 | # Parentheses preferences 37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 38 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 39 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 40 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 41 | 42 | # Modifier preferences 43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 44 | 45 | # Expression-level preferences 46 | dotnet_style_coalesce_expression = true 47 | dotnet_style_collection_initializer = true 48 | dotnet_style_explicit_tuple_names = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_compound_assignment = true 54 | dotnet_style_prefer_conditional_expression_over_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_return = true 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 57 | dotnet_style_prefer_inferred_tuple_names = true 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 59 | dotnet_style_prefer_simplified_boolean_expressions = true 60 | dotnet_style_prefer_simplified_interpolation = true 61 | 62 | # Field preferences 63 | dotnet_style_readonly_field = true 64 | 65 | # Parameter preferences 66 | dotnet_code_quality_unused_parameters = all 67 | 68 | # Suppression preferences 69 | dotnet_remove_unnecessary_suppression_exclusions = none 70 | 71 | #### C# Coding Conventions #### 72 | 73 | # var preferences 74 | csharp_style_var_elsewhere = false 75 | csharp_style_var_for_built_in_types = false 76 | csharp_style_var_when_type_is_apparent = false 77 | 78 | # Expression-bodied members 79 | csharp_style_expression_bodied_accessors = true 80 | csharp_style_expression_bodied_constructors = false 81 | csharp_style_expression_bodied_indexers = true 82 | csharp_style_expression_bodied_lambdas = true 83 | csharp_style_expression_bodied_local_functions = false 84 | csharp_style_expression_bodied_methods = false 85 | csharp_style_expression_bodied_operators = false 86 | csharp_style_expression_bodied_properties = true 87 | 88 | # Pattern matching preferences 89 | csharp_style_pattern_matching_over_as_with_null_check = true 90 | csharp_style_pattern_matching_over_is_with_cast_check = true 91 | csharp_style_prefer_not_pattern = true 92 | csharp_style_prefer_pattern_matching = true 93 | csharp_style_prefer_switch_expression = true 94 | 95 | # Null-checking preferences 96 | csharp_style_conditional_delegate_call = true 97 | 98 | # Modifier preferences 99 | csharp_prefer_static_local_function = true 100 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 101 | 102 | # Code-block preferences 103 | csharp_prefer_braces = true 104 | csharp_prefer_simple_using_statement = true 105 | 106 | # Expression-level preferences 107 | csharp_prefer_simple_default_expression = true 108 | csharp_style_deconstructed_variable_declaration = true 109 | csharp_style_implicit_object_creation_when_type_is_apparent = true 110 | csharp_style_inlined_variable_declaration = true 111 | csharp_style_pattern_local_over_anonymous_function = true 112 | csharp_style_prefer_index_operator = true 113 | csharp_style_prefer_range_operator = true 114 | csharp_style_throw_expression = true 115 | csharp_style_unused_value_assignment_preference = discard_variable 116 | csharp_style_unused_value_expression_statement_preference = discard_variable 117 | 118 | # 'using' directive preferences 119 | csharp_using_directive_placement = outside_namespace 120 | 121 | #### C# Formatting Rules #### 122 | 123 | # New line preferences 124 | csharp_new_line_before_catch = true 125 | csharp_new_line_before_else = true 126 | csharp_new_line_before_finally = true 127 | csharp_new_line_before_members_in_anonymous_types = true 128 | csharp_new_line_before_members_in_object_initializers = true 129 | csharp_new_line_before_open_brace = anonymous_methods,anonymous_types,lambdas,methods,object_collection_array_initializers,properties,types 130 | csharp_new_line_between_query_expression_clauses = true 131 | 132 | # Indentation preferences 133 | csharp_indent_block_contents = true 134 | csharp_indent_braces = false 135 | csharp_indent_case_contents = true 136 | csharp_indent_case_contents_when_block = true 137 | csharp_indent_labels = one_less_than_current 138 | csharp_indent_switch_labels = true 139 | 140 | # Space preferences 141 | csharp_space_after_cast = false 142 | csharp_space_after_colon_in_inheritance_clause = true 143 | csharp_space_after_comma = true 144 | csharp_space_after_dot = false 145 | csharp_space_after_keywords_in_control_flow_statements = true 146 | csharp_space_after_semicolon_in_for_statement = true 147 | csharp_space_around_binary_operators = before_and_after 148 | csharp_space_around_declaration_statements = false 149 | csharp_space_before_colon_in_inheritance_clause = true 150 | csharp_space_before_comma = false 151 | csharp_space_before_dot = false 152 | csharp_space_before_open_square_brackets = false 153 | csharp_space_before_semicolon_in_for_statement = false 154 | csharp_space_between_empty_square_brackets = false 155 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 156 | csharp_space_between_method_call_name_and_opening_parenthesis = false 157 | csharp_space_between_method_call_parameter_list_parentheses = false 158 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 159 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 160 | csharp_space_between_method_declaration_parameter_list_parentheses = false 161 | csharp_space_between_parentheses = false 162 | csharp_space_between_square_brackets = false 163 | 164 | # Wrapping preferences 165 | csharp_preserve_single_line_blocks = true 166 | csharp_preserve_single_line_statements = true 167 | 168 | #### Naming styles #### 169 | 170 | # Naming rules 171 | 172 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 173 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 174 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 175 | 176 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 177 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 178 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 179 | 180 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 181 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 182 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 183 | 184 | # Symbol specifications 185 | 186 | dotnet_naming_symbols.interface.applicable_kinds = interface 187 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 188 | dotnet_naming_symbols.interface.required_modifiers = 189 | 190 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 191 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 192 | dotnet_naming_symbols.types.required_modifiers = 193 | 194 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 195 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 196 | dotnet_naming_symbols.non_field_members.required_modifiers = 197 | 198 | # Naming styles 199 | 200 | dotnet_naming_style.pascal_case.required_prefix = 201 | dotnet_naming_style.pascal_case.required_suffix = 202 | dotnet_naming_style.pascal_case.word_separator = 203 | dotnet_naming_style.pascal_case.capitalization = pascal_case 204 | 205 | dotnet_naming_style.begins_with_i.required_prefix = I 206 | dotnet_naming_style.begins_with_i.required_suffix = 207 | dotnet_naming_style.begins_with_i.word_separator = 208 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 209 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX.Extensions; 2 | 3 | public static class ExceptionExtensions 4 | { 5 | public static string Format(this Exception exception) => Primitives.FormatException(exception); 6 | } 7 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/FSharpResultExtensions.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 8602 2 | 3 | using System.Runtime.CompilerServices; 4 | using Microsoft.FSharp.Core; 5 | 6 | namespace SharpX.Extensions; 7 | 8 | public static class FSharpResultExtensions 9 | { 10 | /// Allows pattern matching on FSharpResult. 11 | public static void Match(this FSharpResult result, 12 | Action onOk, 13 | Action onError) 14 | { 15 | Guard.DisallowNull(nameof(onOk), onOk); 16 | Guard.DisallowNull(nameof(onError), onError); 17 | 18 | if (result.IsOk) { 19 | onOk(result.ResultValue); 20 | return; 21 | } 22 | onError(result.ErrorValue); 23 | } 24 | 25 | /// Allows pattern matching on FSharpResult. 26 | public static TResult Either(this FSharpResult result, 27 | Func onOk, 28 | Func onError) 29 | { 30 | Guard.DisallowNull(nameof(onOk), onOk); 31 | Guard.DisallowNull(nameof(onError), onError); 32 | 33 | return Either(onOk, onError, result); 34 | } 35 | 36 | /// Lifts a Func into an FSharpResult and applies it on the given 37 | /// result. 38 | public static FSharpResult Map( 39 | this FSharpResult result, 40 | Func func) 41 | { 42 | Guard.DisallowNull(nameof(func), func); 43 | 44 | return Lift(func, result); 45 | } 46 | 47 | /// If the wrapped function is a success and the given result is a success the 48 | /// function is applied on the value. Otherwise the exisiting error is returned. 49 | public static FSharpResult Bind( 50 | this FSharpResult result, 51 | Func> func) 52 | { 53 | Guard.DisallowNull(nameof(func), func); 54 | 55 | return Bind(func, result); 56 | } 57 | 58 | /// If the given result is a success the wrapped value will be returned. Otherwise 59 | /// the function throws an exception with the string representation of the error. 60 | public static T ReturnOrFail(this FSharpResult result) 61 | { 62 | Guard.DisallowNull(nameof(result), result); 63 | 64 | Func raiseExn = err => throw new Exception(err.ToString()); 65 | 66 | return Either(value => value, raiseExn, result); 67 | } 68 | 69 | /// Unwraps a value applying a function o returns another value on fail. 70 | public static TResult Return( 71 | this FSharpResult result, 72 | Func func, TResult noneValue) => Either(func, value => noneValue, result); 73 | 74 | /// Builds a Maybe discarding error type. 75 | public static Maybe ToMaybe(this FSharpResult result) => 76 | result.IsOk ? Maybe.Just(result.ResultValue) : Maybe.Nothing(); 77 | 78 | public static Either ToEither(this FSharpResult result) => 79 | result.IsOk 80 | ? SharpX.Either.Right(result.ResultValue) 81 | : SharpX.Either.Left(result.ErrorValue); 82 | 83 | /// Takes a result and maps it with okFunc if it is a success, otherwise it maps it with 84 | /// errorFunc. 85 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 86 | public static TResult Either( 87 | Func okFunc, 88 | Func errorFunc, 89 | FSharpResult result) 90 | { 91 | Guard.DisallowNull(nameof(okFunc), okFunc); 92 | Guard.DisallowNull(nameof(errorFunc), errorFunc); 93 | 94 | if (result.IsOk) { 95 | return okFunc(result.ResultValue); 96 | } 97 | return errorFunc(result.ErrorValue); 98 | } 99 | 100 | /// If the result is a success it executes the given function on the value. Otherwise the 101 | /// exisiting error is returned. 102 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 103 | public static FSharpResult Bind( 104 | Func> func, 105 | FSharpResult result) 106 | { 107 | Guard.DisallowNull(nameof(func), func); 108 | Guard.DisallowNull(nameof(result), result); 109 | 110 | Func> okFunc = 111 | value => func(value); 112 | Func> errorFunc = 113 | error => FSharpResult.NewError(error); 114 | return Either(okFunc, errorFunc, result); 115 | } 116 | 117 | /// If the wrapped function is a success and the given result is a success the function is 118 | /// applied on the value. Otherwise the exisiting error is returned. 119 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 120 | public static FSharpResult Apply( 121 | FSharpResult, TError> wrappedFunc, 122 | FSharpResult result 123 | ) 124 | { 125 | Guard.DisallowNull(nameof(wrappedFunc), wrappedFunc); 126 | Guard.DisallowNull(nameof(result), result); 127 | 128 | if (wrappedFunc.IsOk && result.IsOk) { 129 | return FSharpResult.NewOk( 130 | wrappedFunc.ResultValue(result.ResultValue)); 131 | } 132 | return FSharpResult.NewError(result.ErrorValue); 133 | } 134 | 135 | /// Lifts a function into a result container and applies it on the given result. 136 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 137 | public static FSharpResult Lift( 138 | Func func, 139 | FSharpResult result) => 140 | Apply(FSharpResult, TError>.NewOk(func), result); 141 | } 142 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace SharpX.Extensions; 4 | 5 | public static class LoggerExtensions 6 | { 7 | public static bool WarnWith( 8 | this ILogger logger, string message, bool returnValue = false, params object[] args) => 9 | WarnWith(logger, message, returnValue, args); 10 | 11 | public static T? WarnWith( 12 | this ILogger logger, string message, T returnValue, params object[] args) 13 | { 14 | Guard.DisallowNull(nameof(logger), logger); 15 | Guard.DisallowNull(nameof(message), message); 16 | if (!typeof(T).IsValueType) Guard.DisallowNull(nameof(returnValue), returnValue); 17 | 18 | logger.LogWarning(message, args); 19 | 20 | return returnValue; 21 | } 22 | 23 | public static bool FailWith( 24 | this ILogger logger, string message, bool returnValue = false, params object[] args) => 25 | FailWith(logger, message, returnValue, args); 26 | 27 | public static T? FailWith( 28 | this ILogger logger, string message, T returnValue, params object[] args) 29 | { 30 | Guard.DisallowNull(nameof(logger), logger); 31 | Guard.DisallowNull(nameof(message), message); 32 | if (!typeof(T).IsValueType) Guard.DisallowNull(nameof(returnValue), returnValue); 33 | 34 | logger.LogError(message, args); 35 | 36 | return returnValue; 37 | } 38 | 39 | public static bool PanicWith( 40 | this ILogger logger, string message, bool returnValue = false, Exception? ex = null, params object[] args) => 41 | PanicWith(logger, message, returnValue, ex, args); 42 | 43 | public static T? PanicWith( 44 | this ILogger logger, string message, T returnValue, Exception? ex = null, params object[] args) 45 | { 46 | Guard.DisallowNull(nameof(logger), logger); 47 | Guard.DisallowNull(nameof(message), message); 48 | if (!typeof(T).IsValueType) Guard.DisallowNull(nameof(returnValue), returnValue); 49 | 50 | logger.LogCritical(ex, message, args); 51 | 52 | return returnValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX.Extensions; 2 | 3 | public static class ObjectExtensions 4 | { 5 | /// Discards a value and return Unit. 6 | public static Unit ToUnit(this T value) => Unit.Default; 7 | 8 | /// Returns true in case of a numeric type value, otherwise false. 9 | public static bool IsNumber(this T? value) => Primitives.IsNumber(value); 10 | } 11 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace SharpX.Extensions; 4 | 5 | public static class StringExtensions 6 | { 7 | static Regex _stripTagRegEx = new Regex(@"<[^>]*>", RegexOptions.Compiled | RegexOptions.Multiline); 8 | 9 | /// Determines whether the beginning of this string instance matches the specified string in a case insensitive way. 10 | public static bool StartsWithIgnoreCase(this string source, string value) => 11 | source.StartsWith(value, StringComparison.OrdinalIgnoreCase); 12 | 13 | /// Determines whether two String objects have the same value in a case insensitive way. 14 | public static bool EqualsIgnoreCase(this string? source, string value, bool safe = false) 15 | { 16 | if (!safe) Guard.DisallowNull(nameof(source), source); 17 | else if (safe && source == null && value != null) return false; 18 | else if (safe && source == null && value == null) return true; 19 | 20 | return source.Equals(value, StringComparison.OrdinalIgnoreCase); 21 | } 22 | 23 | /// Determines whether a specified substring occurs within this string in a case insensitive way. 24 | public static bool ContainsIgnoreCase(this string source, string value) => 25 | source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; 26 | 27 | /// Replicates a character for a given number of times using a seperator. 28 | public static string Replicate(this char value, int count, string separator = "") => 29 | Strings.ReplicateChar(value, count, separator); 30 | 31 | /// Determines if a character is special op not. 32 | public static bool IsSpecialChar(this char value) => Strings.IsSpecialChar(value); 33 | 34 | /// Determines if a string is composed only by letter characters. 35 | public static bool IsAlpha(this string value) => Strings.IsAlpha(value); 36 | 37 | /// Determines if a string is composed only by alphanumeric characters. 38 | public static bool IsAlphanumeric(this string value) => Strings.IsAlphanumeric(value); 39 | 40 | /// Determines if a string is null, empty or composed only by white space. 41 | public static bool IsEmpty(this string? value) => String.IsNullOrWhiteSpace(value); 42 | 43 | /// Determines if a string is empty or composed only by white spaces. 44 | public static bool IsEmptyWhitespace(this string value) => Strings.IsEmptyWhitespace(value); 45 | 46 | /// Determines if a string is contains any kind of white spaces. 47 | public static bool ContainsWhitespace(this string value) => Strings.ContainsWhitespace(value); 48 | 49 | /// Returns a copy of this string with first letter converted to uppercase. 50 | public static string ToUpperFirst(this string value) => 51 | Strings.ToUpperFirst(value); 52 | 53 | /// Returns a copy of this string with first letter converted to lowercase. 54 | public static string ToLowerFirst(this string value) => 55 | Strings.ToLowerFirst(value); 56 | 57 | /// Creates a Guid from a given string or default if safe is set. 58 | public static Guid ToGuid(this string value, bool safe = false) 59 | { 60 | if (!safe) return new(value); 61 | return Guid.TryParse(value, out var result) 62 | ? result 63 | : default; 64 | } 65 | 66 | /// Replicates a string for a given number of times using a seperator. 67 | public static string Replicate(this string value, int count, string separator = "") => 68 | Strings.Replicate(value, count, separator); 69 | 70 | /// Applies a given function to nth-word of string. 71 | public static string ApplyAt(this string value, int index, Func modifier) => 72 | Strings.ApplyAt(value, index, modifier); 73 | 74 | /// Selects a random index of a word that optionally satisfies a function. 75 | public static int ChoiceOfIndex(this string value, Func? validator = null) => 76 | Strings.ChoiceOfIndex(value, validator); 77 | 78 | /// Mangles a string with a given number of non alphanumeric character in 79 | /// random positions. 80 | public static string Mangle(this string value, int times = 1, int maxLength = 1) => 81 | Strings.Mangle(value, times, maxLength); 82 | 83 | /// Takes a value and a string and `intersperses' that value between its words. 84 | public static string Intersperse(this string value, params object[] values) => 85 | Strings.Intersperse(value, values); 86 | 87 | /// Sanitizes a string removing non alphanumeric characters and optionally normalizing 88 | /// white spaces. 89 | public static string Sanitize(this string value, bool normalizeWhiteSpace = true) => 90 | Strings.Sanitize(value, normalizeWhiteSpace); 91 | 92 | /// Normalizes any white space character to a single white space. 93 | public static string NormalizeWhiteSpace(this string value) => 94 | Strings.NormalizeWhiteSpace(value); 95 | 96 | // Normalizes a null or white space string to empty. 97 | public static string NormalizeToEmpty(string value) => 98 | Strings.NormalizeToEmpty(value); 99 | 100 | /// Removes tags from a string. 101 | public static string StripTag(this string value) => 102 | _stripTagRegEx.Replace(value, ""); 103 | 104 | /// Removes words of given length. 105 | public static string StripByLength(this string value, int length) => 106 | Strings.StripByLength(value, length); 107 | 108 | /// Retrieves a substring from this instance. The substring starts at a specified 109 | /// character position and has a specified length. No exception is raised if limit is exceeded. 110 | public static string Substring(this string value, int startIndex, int length, bool safe = false) => 111 | Strings.Substring(value, startIndex, length, safe); 112 | 113 | /// Reduces a sequence of strings to a sequence of parts, splitted by space, 114 | /// of each original string. 115 | public static IEnumerable FlattenOnce(this IEnumerable source) => 116 | Strings.FlattenOnce(source); 117 | 118 | /// Convenience extension method to create a new Uri from a string. 119 | public static Uri? ToUri(this string value, bool safe = false) 120 | { 121 | if (!safe) return new(value); 122 | try { 123 | return new(value); 124 | } 125 | catch { 126 | return default; 127 | } 128 | } 129 | 130 | /// Reverses tha case of a character. 131 | public static char ReverseCase(this char value) => Strings.ReverseCase(value); 132 | 133 | /// Randomizes the case of a string characters. 134 | public static string RandomizeCase(this string value) => Strings.RandomizeCase(value); 135 | 136 | /// Normalize a diacritics with an ordinary character. 137 | public static char RemoveDiacritics(this char value) => Strings.RemoveDiacritics(value); 138 | 139 | /// Normalize a string with diacritics with ordinary characters. 140 | public static string RemoveDiacritics(this string value) => Strings.RemoveDiacritics(value); 141 | } 142 | -------------------------------------------------------------------------------- /src/SharpX/Extensions/UnitExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX.Extensions; 2 | 3 | public static class UnitExtensions 4 | { 5 | /// Joins more Unit instances. 6 | public static Unit And(this Unit _, Unit second) => second; 7 | } 8 | -------------------------------------------------------------------------------- /src/SharpX/FsCheck/ArbitraryIntegerSeq.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | 4 | namespace SharpX.FsCheck; 5 | 6 | public static class ArbitraryIntegerSeq 7 | { 8 | public static Arbitrary Generator() 9 | { 10 | var seq = Primitives.GenerateSeq(count: 100); 11 | 12 | return Gen.Shuffle(seq).ToArbitrary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SharpX/FsCheck/ArbitraryString.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | 4 | namespace SharpX.FsCheck; 5 | 6 | public static class ArbitraryString 7 | { 8 | public static Arbitrary Generator() => Gen.OneOf(Gen.Constant(null), 9 | Gen.Constant(Strings.Generate(9))).ToArbitrary(); 10 | } 11 | -------------------------------------------------------------------------------- /src/SharpX/FsCheck/ArbitraryStringSeq.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | using SharpX.Extensions; 4 | 5 | namespace SharpX.FsCheck; 6 | 7 | public static class ArbitraryStringSeq 8 | { 9 | public static Arbitrary Generator() 10 | { 11 | var seq = Primitives.GenerateSeq(() => Strings.Generate(9), count: 20) 12 | .Concat(Enumerable.Range(0, 9).Select(x => x.ToString())) 13 | .Intersperse(null) 14 | .Intersperse(string.Empty); 15 | 16 | return Gen.Shuffle(seq).ToArbitrary(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SharpX/FsCheck/ArbitraryValue.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using FsCheck; 3 | using FsCheck.Fluent; 4 | using SharpX.Extensions; 5 | 6 | namespace SharpX.FsCheck; 7 | 8 | public static class ArbitraryValue 9 | { 10 | public static Arbitrary Generator() 11 | { 12 | var seq = (new object[] { false, true } 13 | .Concat(Enumerable.Repeat(default, 5).Cast()) 14 | .Concat(Primitives.GenerateSeq(count: 19).Cast()) 15 | .Concat(Primitives.GenerateSeq(count: 19).Cast()) 16 | .Concat(Enumerable.Range(0, 19).Select(x => (object)DateTime.Now.AddDays(x))) 17 | .Concat(Enumerable.Repeat(null, 5).Cast()) 18 | .Concat(Primitives.GenerateSeq(count: 10).Cast()) 19 | .Concat(Enumerable.Range(0, 19).Select(x => new { Foo = x })) 20 | ).Shuffle(); 21 | 22 | return Gen.OneOf(from x in seq select Gen.Constant(x)).ToArbitrary(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SharpX/Primitives.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using RandomNumberGenerator = SharpX._RandomNumberGeneratorCompatibility; 3 | 4 | namespace SharpX; 5 | 6 | public static class Primitives 7 | { 8 | /// Converts a value to an enumerable. 9 | public static IEnumerable ToEnumerable(T value) 10 | { 11 | Guard.DisallowNull(nameof(value), value); 12 | 13 | return [value]; 14 | } 15 | 16 | /// Formats an exception to human readable text. 17 | public static string FormatException(Exception exception) 18 | { 19 | Guard.DisallowNull(nameof(exception), exception); 20 | 21 | var builder = new StringBuilder(capacity: 256) 22 | .AppendLine(exception.Message); 23 | if (exception.StackTrace != null) { 24 | builder.AppendLine("--- Stack trace:") 25 | .AppendLine(exception.StackTrace); 26 | } 27 | if (exception.InnerException != null) { 28 | builder.AppendLine("--- Inner exception:") 29 | .AppendLine(exception.InnerException.Message); 30 | if(exception.InnerException.StackTrace != null) { 31 | builder.AppendLine("--- Inner exception stack trace:") 32 | .AppendLine(exception.InnerException.StackTrace); 33 | } 34 | } 35 | return builder.ToString(); 36 | } 37 | 38 | /// Returns true if the chance randomly occurred. 39 | public static bool ChanceOf(int thresold) 40 | { 41 | Guard.DisallowNegative(nameof(thresold), thresold); 42 | 43 | return thresold > 0 44 | ? RandomNumberGenerator.GetInt32(0, 100) <= thresold 45 | : false; 46 | } 47 | 48 | /// Generates a random sequence from a generator function. 49 | public static IEnumerable GenerateSeq(Func generator, int? count = null) 50 | { 51 | if (count != null) Guard.DisallowNegative(nameof(count), count.Value); 52 | 53 | var count_ = count ?? 0; 54 | 55 | while (count == null || count_-- > 0) { 56 | yield return generator(); 57 | } 58 | } 59 | 60 | /// Generates a random sequence of int, double or string types. 61 | public static IEnumerable GenerateSeq(int? count = null) 62 | { 63 | if (count != null) Guard.DisallowNegative(nameof(count), count.Value); 64 | 65 | Func generator = typeof(T) switch { 66 | var t when t == typeof(int) => () => RandomNumberGenerator.GetInt32((int)(int.MinValue / 1.3), (int)(int.MaxValue / 1.3)), 67 | var t when t == typeof(double) => () => BitConverter.ToDouble(RandomNumberGenerator.GetBytes(8), 0), 68 | var t when t == typeof(string) => () => Strings.Generate(length: 16), 69 | _ => throw new ArgumentException($"{typeof(T).Name} is not supported"), 70 | }; 71 | 72 | var count_ = count ?? 0; 73 | 74 | while (count == null || count_-- > 0) { 75 | yield return (T)generator(); 76 | } 77 | } 78 | 79 | /// Returns true in case of a numeric type value, otherwise false. 80 | public static bool IsNumber(T? value) 81 | { 82 | if (value == null) return false; 83 | 84 | return value is sbyte || 85 | value is byte || 86 | value is short || 87 | value is ushort || 88 | value is int || 89 | value is uint || 90 | value is long || 91 | value is ulong || 92 | value is float || 93 | value is double || 94 | value is decimal; 95 | } 96 | 97 | /// Executes code and forgets the eventual exception. 98 | public static Unit SafeInvoke(Action action) 99 | { 100 | try { 101 | action(); 102 | } 103 | catch { 104 | } 105 | 106 | return Unit.Default; 107 | } 108 | 109 | /// Executes code and forgets the eventual exception. 110 | public static Unit SafeInvoke(Func func) 111 | { 112 | try { 113 | return func(); 114 | } 115 | catch { 116 | } 117 | 118 | return Unit.Default; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/SharpX/SharpX.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netstandard2.1;net8.0 6 | 13.0 7 | .NET functional programming and other utilities 8 | .NET functional programming and other utilities 9 | 8.3.6 10 | gsscoder 11 | Copyright (C) Giacomo Stelluti Scala, 2015-2025 12 | https://github.com/gsscoder/sharpx 13 | https://github.com/gsscoder/sharpx 14 | MIT 15 | functional;errors;strings;utility;api;library 16 | icon.png 17 | README.md 18 | enable 19 | enable 20 | 21 | 22 | ../../artifacts/SharpX/Debug 23 | 24 | 25 | ../../artifacts/SharpX/Release 26 | 27 | 28 | 29 | <_Parameter1>$(MSBuildProjectName).Tests 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/SharpX/Types/Either/Either.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpX; 3 | 4 | public static class Either 5 | { 6 | #region Value case constructors 7 | /// Builds the Left case of an Either value. 8 | public static Either Left(TLeft value) => 9 | new Either(value); 10 | 11 | /// Builds the Right case of an Either value. 12 | public static Either Right(TRight value) => 13 | new Either(value); 14 | #endregion 15 | 16 | #region Monad 17 | /// Inject a value into the Either type, returning Right case. 18 | public static Either Return(TRight value) => 19 | Either.Right(value); 20 | 21 | /// Monadic bind. 22 | public static Either Bind( 23 | Either either, Func> func) 24 | { 25 | Guard.DisallowNull(nameof(either), either); 26 | Guard.DisallowNull(nameof(func), func); 27 | 28 | if (either.MatchRight(out TRight? right)) { 29 | return func(right!); 30 | } 31 | return Either.Left(either.GetLeft()!); 32 | } 33 | #endregion 34 | 35 | #region Functor 36 | /// Transforms a Either right value by using a specified mapping function. 37 | public static Either Map(Either either, 38 | Func func) 39 | { 40 | Guard.DisallowNull(nameof(either), either); 41 | Guard.DisallowNull(nameof(func), func); 42 | 43 | if (either.MatchRight(out TRight? right)) { 44 | return Either.Right(func(right!)); 45 | } 46 | return Either.Left(either.GetLeft()!); 47 | } 48 | #endregion 49 | 50 | #region Bifunctor 51 | /// Maps both parts of a Either type. Applies the first function if Either 52 | /// is Left. Otherwise applies the second function. 53 | public static Either Bimap(Either either, 54 | Func mapLeft, Func mapRight) 55 | { 56 | Guard.DisallowNull(nameof(either), either); 57 | Guard.DisallowNull(nameof(mapLeft), mapLeft); 58 | Guard.DisallowNull(nameof(mapRight), mapRight); 59 | 60 | if (either.MatchRight(out TRight? right)) { 61 | return Either.Right(mapRight(right!)); 62 | } 63 | return Either.Left(mapLeft(either.GetLeft()!)); 64 | } 65 | #endregion 66 | 67 | /// Fail with a message. Not part of mathematical definition of a monad. 68 | public static Either Fail(string message) => throw new Exception(message); 69 | 70 | /// Wraps a function, encapsulates any exception thrown within to a Either. 71 | public static Either Try(Func func) 72 | { 73 | Guard.DisallowNull(nameof(func), func); 74 | 75 | try { 76 | return new Either(func()); 77 | } 78 | catch (Exception ex) { 79 | return new Either(ex); 80 | } 81 | } 82 | 83 | /// Attempts to cast an object. Stores the cast value in Right if successful, otherwise 84 | /// stores the exception in Left. 85 | public static Either Cast(object obj) => Either.Try(() => (TRight)obj); 86 | 87 | /// Converts a Just value to a Right and a Nothing value to a 88 | /// Left. 89 | public static Either FromMaybe(Maybe maybe, TLeft left) 90 | { 91 | Guard.DisallowNull(nameof(maybe), maybe); 92 | Guard.DisallowNull(nameof(left), left); 93 | 94 | if (maybe.Tag == MaybeType.Just) { 95 | return Either.Right(maybe.FromJust()!); 96 | } 97 | return Either.Left(left); 98 | } 99 | 100 | private static TLeft? GetLeft(this Either either) => either.FromLeft(); 101 | } 102 | -------------------------------------------------------------------------------- /src/SharpX/Types/Either/EitherExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpX; 3 | 4 | public static class EitherExtensions 5 | { 6 | #region LINQ operators 7 | /// Map operation compatible with LINQ. 8 | public static Either Select( 9 | this Either either, 10 | Func selector) => Either.Map(either, selector); 11 | 12 | /// Map operation compatible with LINQ. 13 | public static Either SelectMany(this Either result, 14 | Func> func) => Either.Bind(result, func); 15 | #endregion 16 | 17 | #region Alternative match extensions 18 | public static Unit Match(this Either either, 19 | Func onLeft, Func onRight) 20 | { 21 | Guard.DisallowNull(nameof(either), either); 22 | Guard.DisallowNull(nameof(onLeft), onLeft); 23 | Guard.DisallowNull(nameof(onRight), onRight); 24 | 25 | return either.MatchRight(out TRight? right) switch { 26 | true => onRight(right!), 27 | _ => onLeft(either.FromLeft()) 28 | }; 29 | } 30 | #endregion 31 | 32 | /// Equivalent to monadic Return operation. Builds a Right value 33 | /// by default. 34 | public static Either ToEither(this TRight value) => Either.Return(value); 35 | 36 | /// Equivalent to monadic Bind. 37 | public static Either Bind( 38 | this Either either, 39 | Func> func) => Either.Bind(either, func); 40 | 41 | /// Equivalent to monadic Map. 42 | public static Either Map( 43 | this Either either, 44 | Func func) => Either.Map(either, func); 45 | 46 | /// Eviqualent to monadic Bimap. 47 | public static Either Bimap( 48 | this Either either, 49 | Func mapLeft, 50 | Func mapRight) => Either.Bimap(either, mapLeft, mapRight); 51 | 52 | /// Returns true if it is in form of Left. 53 | public static bool IsLeft(this Either either) => 54 | either.Tag == EitherType.Left; 55 | 56 | /// Returns true if it is in form of Right. 57 | public static bool IsRight(this Either either) => 58 | either.Tag == EitherType.Right; 59 | 60 | /// Extracts the element out of Left and returns a default value (or noneValue 61 | /// when given) if it is in form of Right. 62 | public static TLeft FromLeft(this Either either, 63 | TLeft? noneValue = default) => either.MatchLeft(out TLeft? value) ? value! : noneValue!; 64 | 65 | /// Extracts the element out of Left and throws an exception if it is form of 66 | /// Right. 67 | public static TLeft FromLeftOrFail(this Either either, 68 | Exception? exceptionToThrow = null) 69 | { 70 | Guard.DisallowNull(nameof(either), either); 71 | 72 | if (either.MatchLeft(out TLeft? value)) { 73 | return value!; 74 | } 75 | throw exceptionToThrow ?? new Exception("The value is empty."); 76 | } 77 | 78 | /// Extracts the element out of Left and returns a default (or noneValue 79 | /// when given) value if it is in form ofRight. 80 | public static TRight? FromRight(this Either either, 81 | TRight? noneValue = default) => either.MatchRight(out TRight? value) ? value : noneValue; 82 | 83 | /// Extracts the element out of Left and throws an exception if it is form of 84 | /// Right. 85 | public static TRight? FromRightOrFail(this Either either, 86 | Exception? exceptionToThrow = null) 87 | { 88 | Guard.DisallowNull(nameof(either), either); 89 | 90 | if (either.MatchRight(out TRight? value)) { 91 | return value; 92 | } 93 | throw exceptionToThrow ?? new Exception("The value is empty."); 94 | } 95 | 96 | #region Sequences 97 | /// Extracts from a sequence of Either all the Left elements. All the 98 | /// Left elements are extracted in order. 99 | public static IEnumerable Lefts(this IEnumerable> source) 100 | { 101 | Guard.DisallowNull(nameof(source), source); 102 | 103 | return _(); IEnumerable _() 104 | { 105 | foreach (var either in source) { 106 | if (either.Tag == EitherType.Left) yield return either.FromLeft(); 107 | } 108 | } 109 | } 110 | 111 | /// Extracts from a sequence of Either all the Right elements. All the 112 | /// Rights elements are extracted in order. 113 | public static IEnumerable Rights(this IEnumerable> source) 114 | { 115 | Guard.DisallowNull(nameof(source), source); 116 | 117 | return _(); IEnumerable _() 118 | { 119 | foreach (var either in source) { 120 | if (either.Tag == EitherType.Right) yield return either.FromRight()!; 121 | } 122 | } 123 | } 124 | 125 | /// Partitions a sequence of Either into two sequences. All the Left 126 | /// elements are extracted, in order, to the first component of the pair. Similarly the Right 127 | /// elements are extracted to the second component of the pair. 128 | public static (IEnumerable, IEnumerable) Partition( 129 | this IEnumerable> source) 130 | { 131 | Guard.DisallowNull(nameof(source), source); 132 | 133 | var lefts = new List(); 134 | var rights = new List(); 135 | 136 | foreach (var either in source) { 137 | if (either.Tag == EitherType.Left) lefts.Add(either.FromLeft()); 138 | else rights.Add(either.FromRight()!); 139 | } 140 | return (lefts, rights); 141 | } 142 | #endregion 143 | } 144 | -------------------------------------------------------------------------------- /src/SharpX/Types/Either/EitherOfT.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpX; 3 | 4 | /// The Either type represents values with two possibilities: a value of type 5 | /// Either T U is either Left T or Right U. The Either type is 6 | /// sometimes used to represent a value which is either correct or an error; by convention, the 7 | /// Left constructor is used to hold an error value and the Right constructor is 8 | /// used to hold a correct value (mnemonic: "right" also means "correct"). 9 | public struct Either 10 | { 11 | private readonly TLeft? _leftValue; 12 | private readonly TRight? _rightValue; 13 | 14 | internal Either(TLeft value) 15 | { 16 | _leftValue = value; 17 | _rightValue = default; 18 | Tag = EitherType.Left; 19 | } 20 | 21 | internal Either(TRight value) 22 | { 23 | _leftValue = default; 24 | _rightValue = value; 25 | Tag = EitherType.Right; 26 | } 27 | 28 | public EitherType Tag { get; private set; } 29 | 30 | #region Basic match methods 31 | /// Matches a Left value returning true and value itself via an output 32 | /// parameter. 33 | public bool MatchLeft(out TLeft? value) 34 | { 35 | value = Tag == EitherType.Left ? _leftValue : default; 36 | return Tag == EitherType.Left; 37 | } 38 | 39 | /// Matches a Right value returning true and value itself via an output 40 | /// parameter. 41 | public bool MatchRight(out TRight? value) 42 | { 43 | value = Tag == EitherType.Right ? _rightValue : default; 44 | return Tag == EitherType.Right; 45 | } 46 | #endregion 47 | } 48 | -------------------------------------------------------------------------------- /src/SharpX/Types/Either/EitherType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Discriminator for Either. 4 | public enum EitherType 5 | { 6 | /// Failed computation case. 7 | Left, 8 | /// Sccessful computation case. 9 | Right 10 | } 11 | -------------------------------------------------------------------------------- /src/SharpX/Types/Maybe/Maybe.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace SharpX; 4 | 5 | /// Provides static methods for manipulating Maybe. 6 | public static class Maybe 7 | { 8 | #region Value case constructors 9 | /// Builds the empty case of Maybe. 10 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 11 | public static Maybe Nothing() => new(); 12 | 13 | /// Builds the case when Maybe contains a value. 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static Maybe Just(T value) 16 | { 17 | Guard.DisallowNull(nameof(value), value); 18 | 19 | return new Maybe(value); 20 | } 21 | #endregion 22 | 23 | #region Monad 24 | /// Injects a value into the monadic Maybe type. 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static Maybe Return(T? value) => Equals(value, default(T)) ? Nothing() : Just(value!); 27 | 28 | /// Sequentially compose two actions, passing any value produced by the first as 29 | /// an argument to the second. 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static Maybe Bind(Maybe maybe, Func> onJust) 32 | { 33 | Guard.DisallowNull(nameof(maybe), maybe); 34 | Guard.DisallowNull(nameof(onJust), onJust); 35 | 36 | return maybe.MatchJust(out T1? value) ? onJust(value) : Nothing(); 37 | } 38 | #endregion 39 | 40 | #region Functor 41 | /// Transforms a Maybe value by using a specified mapping function. 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public static Maybe Map(Maybe maybe, Func onJust) 44 | { 45 | Guard.DisallowNull(nameof(maybe), maybe); 46 | Guard.DisallowNull(nameof(onJust), onJust); 47 | 48 | return maybe.MatchJust(out T1? value) ? Just(onJust(value!)) : Nothing(); 49 | } 50 | #endregion 51 | 52 | /// If both Maybe values contain a value, it merges them into a Maybe 53 | /// with a tupled value. 54 | public static Maybe<(T1, T2)> Merge(Maybe first, Maybe second) 55 | { 56 | Guard.DisallowNull(nameof(first), first); 57 | Guard.DisallowNull(nameof(second), second); 58 | 59 | T2? value2 = default; 60 | return (first.MatchJust(out T1? value1) && 61 | second.MatchJust(out value2)) switch { 62 | true => Just((value1!, value2!)), 63 | _ => Nothing<(T1, T2)>() 64 | }; 65 | } 66 | 67 | /// Executes the given function on a Just success or returns a Nothing. 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public static Maybe Try(Func func) 70 | { 71 | Guard.DisallowNull(nameof(func), func); 72 | 73 | try { 74 | return Just(func()); 75 | } 76 | catch { 77 | return Nothing(); 78 | } 79 | } 80 | 81 | /// Maps Either right value to Just, otherwise returns 82 | /// Nothing. 83 | public static Maybe FromEither(Either either) 84 | { 85 | Guard.DisallowNull(nameof(either), either); 86 | 87 | return (either.Tag == EitherType.Right) switch 88 | { 89 | true => Just(either.FromRight()!), 90 | _ => Nothing() 91 | }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SharpX/Types/Maybe/MaybeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Provides convenience extension methods for Maybe. 4 | public static class MaybeExtensions 5 | { 6 | #region Match extensions 7 | /// Returns true if it is in form of Nothing. 8 | public static bool IsNothing(this Maybe maybe) 9 | { 10 | Guard.DisallowNull(nameof(maybe), maybe); 11 | 12 | return maybe.Tag == MaybeType.Nothing; 13 | } 14 | 15 | /// Returns true if it is in form of Just. 16 | public static bool IsJust(this Maybe maybe) 17 | { 18 | Guard.DisallowNull(nameof(maybe), maybe); 19 | 20 | return maybe.Tag == MaybeType.Just; 21 | } 22 | 23 | /// Provides pattern matching using System.Func delegates. 24 | public static Unit Match(this Maybe maybe, 25 | Func onJust, Func onNothing) 26 | { 27 | Guard.DisallowNull(nameof(maybe), maybe); 28 | Guard.DisallowNull(nameof(onJust), onJust); 29 | Guard.DisallowNull(nameof(onNothing), onNothing); 30 | 31 | return maybe.MatchJust(out T? value) switch { 32 | true => onJust(value!), 33 | _ => onNothing() 34 | }; 35 | } 36 | 37 | /// Provides pattern matching using System.Func delegates over a Maybe 38 | /// with tupled wrapped value. 39 | public static Unit Match(this Maybe<(T1, T2)> maybe, 40 | Func onJust, Func onNothing) 41 | { 42 | Guard.DisallowNull(nameof(maybe), maybe); 43 | Guard.DisallowNull(nameof(onJust), onJust); 44 | Guard.DisallowNull(nameof(onNothing), onNothing); 45 | 46 | return maybe.MatchJust(out T1 value1, out T2 value2) switch { 47 | true => onJust(value1, value2), 48 | _ => onNothing() 49 | }; 50 | } 51 | 52 | /// Matches a value returning true and the tupled value itself via two output 53 | /// parameters. 54 | public static bool MatchJust(this Maybe<(T1, T2)> maybeTuple, 55 | out T1 value1, out T2 value2) 56 | { 57 | Guard.DisallowNull(nameof(maybeTuple), maybeTuple); 58 | 59 | if (maybeTuple.MatchJust(out (T1, T2) value)) { 60 | value1 = value.Item1; 61 | value2 = value.Item2; 62 | return true; 63 | } 64 | value1 = default!; 65 | value2 = default!; 66 | return false; 67 | } 68 | #endregion 69 | 70 | #region Monad 71 | /// Equivalent to monadic Return operation. Builds a Just value in case 72 | /// value is different from its default. 73 | /// 74 | public static Maybe ToMaybe(this T? value) => Maybe.Return(value); 75 | 76 | /// Invokes a function on this maybe value that itself yields a maybe. 77 | public static Maybe Bind(this Maybe maybe, Func> onJust) => 78 | Maybe.Bind(maybe, onJust); 79 | 80 | /// Transforms a maybe value by using a specified mapping function. 81 | public static Maybe Map(this Maybe maybe, Func onJust) => 82 | Maybe.Map(maybe, onJust); 83 | 84 | /// Unwraps a value applying a function o returns another value on fail. 85 | public static T2 Return(this Maybe maybe, Func onJust, T2 @default) => 86 | maybe.MatchJust(out T1? value) ? onJust(value!) : @default; 87 | #endregion 88 | 89 | /// This is a version of map which can throw out the value. If contains a Just 90 | /// executes a mapping function over it, in case of Nothing returns @default. 91 | public static T2? Map(this Maybe maybe, Func onJust, T2? @default = default) 92 | { 93 | Guard.DisallowNull(nameof(maybe), maybe); 94 | Guard.DisallowNull(nameof(onJust), onJust); 95 | 96 | return maybe.MatchJust(out T1? value) ? onJust(value!) : @default; 97 | } 98 | 99 | /// Lazy version of Map. If contains a Just executes a mapping function 100 | /// over it, in case of Nothing returns a value built by @default function. 101 | public static T2 Map(this Maybe maybe, Func onJust, Func @default) 102 | { 103 | Guard.DisallowNull(nameof(maybe), maybe); 104 | Guard.DisallowNull(nameof(onJust), onJust); 105 | 106 | return maybe.MatchJust(out T1? value) ? onJust(value!) : @default(); 107 | } 108 | 109 | #region LINQ operators 110 | /// Map operation compatible with LINQ. 111 | public static Maybe Select(this Maybe maybe, 112 | Func selector) => Maybe.Map(maybe, selector); 113 | 114 | /// Bind operation compatible with LINQ. 115 | public static Maybe SelectMany(this Maybe maybe, 116 | Func> valueSelector, Func resultSelector) => 117 | maybe 118 | .Bind(sourceValue => 119 | valueSelector(sourceValue!) 120 | .Map(resultValue => resultSelector(sourceValue!, resultValue))); 121 | 122 | /// Returns the same Maybe value if the predicate is true, otherwise 123 | /// Nothing. 124 | public static Maybe Where(this Maybe maybe, 125 | Func predicate) 126 | { 127 | Guard.DisallowNull(nameof(maybe), maybe); 128 | Guard.DisallowNull(nameof(predicate), predicate); 129 | 130 | if (maybe.MatchJust(out TSource? value)) { 131 | if (predicate(value!)) return maybe; 132 | } 133 | return Maybe.Nothing(); 134 | } 135 | #endregion 136 | 137 | #region Do semantic 138 | /// If contains a value executes a System.Func delegate over it. 139 | public static Unit Do(this Maybe maybe, Func func) 140 | { 141 | Guard.DisallowNull(nameof(maybe), maybe); 142 | Guard.DisallowNull(nameof(func), func); 143 | 144 | return maybe.MatchJust(out T? value) switch { 145 | true => func(value!), 146 | _ => Unit.Default 147 | }; 148 | } 149 | 150 | /// If contains a value executes an async System.Func delegate over it. 151 | public static async Task DoAsync(this Maybe maybe, Func> func) 152 | { 153 | Guard.DisallowNull(nameof(maybe), maybe); 154 | Guard.DisallowNull(nameof(func), func); 155 | 156 | return maybe.MatchJust(out T? value) switch { 157 | true => await func(value!), 158 | _ => Unit.Default 159 | }; 160 | } 161 | 162 | /// If contains a tuple value executes a System.Func delegate over it. 163 | public static Unit Do(this Maybe<(T1, T2)> maybe, Func func) 164 | { 165 | Guard.DisallowNull(nameof(maybe), maybe); 166 | Guard.DisallowNull(nameof(func), func); 167 | 168 | return maybe.MatchJust(out T1 value1, out T2 value2) switch { 169 | true => func(value1, value2), 170 | _ => Unit.Default 171 | }; 172 | } 173 | 174 | /// If contans a tuple value executes an async System.Func delegate over it. 175 | public static async Task DoAsync(this Maybe<(T1, T2)> maybe, Func> func) 176 | { 177 | Guard.DisallowNull(nameof(maybe), maybe); 178 | Guard.DisallowNull(nameof(func), func); 179 | 180 | return maybe.MatchJust(out T1 value1, out T2 value2) switch { 181 | true => await func(value1, value2), 182 | _ => Unit.Default 183 | }; 184 | } 185 | #endregion 186 | 187 | /// Returns a Just of the given value. 188 | public static Maybe ToJust(this T value) => Maybe.Just(value); 189 | 190 | 191 | /// Extracts the element out of Just and returns a default value (or @default 192 | /// when given) if it is in form of Nothing. 193 | public static T? FromJust(this Maybe maybe, T? @default = default) 194 | { 195 | Guard.DisallowNull(nameof(maybe), maybe); 196 | 197 | return maybe.MatchJust(out T? value) ? value : @default; 198 | } 199 | 200 | /// Lazy version of FromJust. Extracts the element out of Just and returns 201 | /// a value built by @default function if it is in form of Nothing. 202 | public static T? FromJust(this Maybe maybe, Func @default) 203 | { 204 | Guard.DisallowNull(nameof(maybe), maybe); 205 | 206 | return maybe.MatchJust(out T? value) ? value : @default(); 207 | } 208 | 209 | /// Extracts the element out of Just or throws an exception if it is form of 210 | /// Nothing. 211 | public static T? FromJustOrFail(this Maybe maybe, Exception? exceptionToThrow = null) 212 | { 213 | Guard.DisallowNull(nameof(maybe), maybe); 214 | 215 | return maybe.MatchJust(out T? value) switch 216 | { 217 | true => value, 218 | _ => throw exceptionToThrow ?? new Exception("The value is empty.") 219 | }; 220 | } 221 | 222 | #region Sequences 223 | /// Returns an empty sequence when given Nothing or a singleton sequence in 224 | /// case of Just. 225 | public static IEnumerable ToEnumerable(this Maybe maybe) 226 | { 227 | Guard.DisallowNull(nameof(maybe), maybe); 228 | 229 | return _(); IEnumerable _() 230 | { 231 | if (maybe.MatchJust(out T? value)) yield return value!; 232 | } 233 | } 234 | 235 | /// Turns an empty sequence to Nothing, otherwise Just(sequence). 236 | public static Maybe> ToMaybe(this IEnumerable source) 237 | { 238 | Guard.DisallowNull(nameof(source), source); 239 | 240 | using var e = source.GetEnumerator(); 241 | return e.MoveNext() 242 | ? Maybe.Just(source) 243 | : Maybe.Nothing>(); 244 | } 245 | 246 | /// Takes a sequence of Maybe and counts all the Nothing values. 247 | public static int Nothings(this IEnumerable> source) 248 | { 249 | Guard.DisallowNull(nameof(source), source); 250 | 251 | var count = 0; 252 | foreach (var maybe in source) { 253 | if (maybe.Tag == MaybeType.Just) count++; 254 | } 255 | return count; 256 | } 257 | 258 | /// Takes a sequence of Maybe and returns a sequence of all the Just 259 | /// values. 260 | public static IEnumerable Justs(this IEnumerable> source) 261 | { 262 | Guard.DisallowNull(nameof(source), source); 263 | 264 | return _(); IEnumerable _() 265 | { 266 | foreach (var maybe in source) { 267 | if (maybe.Tag == MaybeType.Just) yield return maybe.FromJust()!; 268 | } 269 | } 270 | } 271 | 272 | /// This is a version of map which can throw out elements. In particular, the functional 273 | /// argument returns something of type Maybe<T2>. If this is Nothing, no element is 274 | /// added on to the result sequence. If it is Just<T2>, then T2 is included 275 | /// in the result sequence. 276 | public static IEnumerable Map(this IEnumerable source, Func> onElement) 277 | { 278 | Guard.DisallowNull(nameof(source), source); 279 | Guard.DisallowNull(nameof(onElement), onElement); 280 | 281 | return _(); IEnumerable _() 282 | { 283 | foreach (var element in source) { 284 | if (onElement(element).MatchJust(out T2? value)) yield return value!; 285 | } 286 | } 287 | } 288 | 289 | /// Returns the first element of a sequence as Just, or Nothing if the sequence 290 | /// contains no elements. 291 | public static Maybe FirstOrNothing(this IEnumerable source) 292 | { 293 | Guard.DisallowNull(nameof(source), source); 294 | 295 | if (source is IList list) { 296 | if (list.Count > 0) return Maybe.Just(list[0]); 297 | } 298 | else { 299 | using IEnumerator e = source.GetEnumerator(); 300 | if (e.MoveNext()) return Maybe.Just(e.Current); 301 | } 302 | return Maybe.Nothing(); 303 | } 304 | 305 | /// Returns the first element of the sequence as Just that satisfies a condition or 306 | /// Nothing if no such element is found. 307 | public static Maybe FirstOrNothing(this IEnumerable source, 308 | Func predicate) 309 | { 310 | Guard.DisallowNull(nameof(source), source); 311 | Guard.DisallowNull(nameof(predicate), predicate); 312 | 313 | foreach (TSource element in source) { 314 | if (predicate(element)) return Maybe.Just(element); 315 | } 316 | return Maybe.Nothing(); 317 | } 318 | 319 | /// Returns the last element of a sequence as Just, or Nothing if the sequence 320 | /// contains no elements. 321 | public static Maybe LastOrNothing(this IEnumerable source) 322 | { 323 | Guard.DisallowNull(nameof(source), source); 324 | 325 | if (source is IList list) { 326 | int count = list.Count; 327 | if (count > 0) return Maybe.Just(list[count - 1]); 328 | } 329 | else { 330 | using IEnumerator e = source.GetEnumerator(); 331 | if (e.MoveNext()) { 332 | TSource result; 333 | do { 334 | result = e.Current; 335 | } while (e.MoveNext()); 336 | return Maybe.Just(result); 337 | } 338 | } 339 | return Maybe.Nothing(); 340 | } 341 | 342 | /// Returns the last element of a sequence as Just that satisfies a condition or Nothing if 343 | /// no such element is found. 344 | public static Maybe LastOrNothing( 345 | this IEnumerable source, Func predicate) 346 | { 347 | Guard.DisallowNull(nameof(source), source); 348 | Guard.DisallowNull(nameof(predicate), predicate); 349 | 350 | var result = Maybe.Nothing(); 351 | foreach (var element in source) { 352 | if (predicate(element)) { 353 | result = Maybe.Just(element); 354 | } 355 | } 356 | return result; 357 | } 358 | 359 | /// Returns the only element of a sequence as Just, or Nothing if the sequence is 360 | /// empty. 361 | public static Maybe SingleOrNothing(this IEnumerable source) 362 | { 363 | Guard.DisallowNull(nameof(source), source); 364 | 365 | if (source is IList list) { 366 | switch (list.Count) { 367 | case 0: return Maybe.Nothing(); 368 | case 1: return Maybe.Just(list[0]); 369 | } 370 | } 371 | else { 372 | using IEnumerator e = source.GetEnumerator(); 373 | if (!e.MoveNext()) return Maybe.Nothing(); 374 | TSource result = e.Current; 375 | if (!e.MoveNext()) return Maybe.Just(result); 376 | } 377 | return Maybe.Nothing(); 378 | } 379 | 380 | /// Returns the only element of a sequence that satisfies a specified condition as 381 | /// Just or a Nothing if no such element exists; this method throws an exception if more than 382 | /// one element satisfies the condition. 383 | public static Maybe SingleOrNothing( 384 | this IEnumerable source, Func predicate) 385 | { 386 | Guard.DisallowNull(nameof(source), source); 387 | Guard.DisallowNull(nameof(predicate), predicate); 388 | 389 | var result = Maybe.Nothing(); 390 | var count = 0L; 391 | foreach (var element in source) { 392 | if (predicate(element)) { 393 | result = Maybe.Just(element); 394 | checked { count++; } 395 | } 396 | } 397 | return count switch 398 | { 399 | 0 => Maybe.Nothing(), 400 | 1 => result, 401 | _ => throw new InvalidOperationException("Sequence contains more than one element"), 402 | }; 403 | } 404 | 405 | /// Returns the element at a specified index in a sequence as Just or Nothing 406 | /// if the index is out of range. 407 | public static Maybe ElementAtOrNothing(this IEnumerable source, int index) 408 | { 409 | Guard.DisallowNull(nameof(source), source); 410 | Guard.DisallowNegative(nameof(index), index); 411 | 412 | if (index >= 0) { 413 | if (source is IList list) { 414 | if (index < list.Count) return Maybe.Just(list[index]); 415 | } 416 | else { 417 | using IEnumerator e = source.GetEnumerator(); 418 | while (true) { 419 | if (!e.MoveNext()) break; 420 | if (index == 0) return Maybe.Just(e.Current); 421 | index--; 422 | } 423 | } 424 | } 425 | return Maybe.Nothing(); 426 | } 427 | #endregion 428 | } 429 | -------------------------------------------------------------------------------- /src/SharpX/Types/Maybe/MaybeOfT.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Text; 3 | 4 | namespace SharpX; 5 | 6 | /// The Maybe type models an optional value. A value of type Maybe either 7 | /// contains a value (represented as Just a), or it is empty (represented as 8 | /// Nothing). 9 | public readonly struct Maybe : IEquatable> 10 | { 11 | #if DEBUG 12 | internal readonly T? _value; 13 | #else 14 | private readonly T? _value; 15 | #endif 16 | private readonly MaybeType _tag; 17 | 18 | internal Maybe(T value) 19 | { 20 | _value = value; 21 | _tag = MaybeType.Just; 22 | } 23 | 24 | /// Type discriminator. 25 | public readonly MaybeType Tag { get => _tag; } 26 | 27 | /// Determines whether this instance and another specified Maybe object have the same value. 28 | public override bool Equals(object? other) 29 | { 30 | if (other is null) return false; 31 | if (other is not Maybe maybe) return false; 32 | 33 | var casted = (Maybe)other; 34 | 35 | return Equals(casted); 36 | } 37 | 38 | /// Determines whether this instance and another specified Maybe object have the same value. 39 | public bool Equals(Maybe other) => 40 | other.Tag != MaybeType.Just || _value!.Equals(other._value); 41 | 42 | public static bool operator ==(Maybe left, Maybe right) => left.Equals(right); 43 | 44 | public static bool operator !=(Maybe left, Maybe right) => !left.Equals(right); 45 | 46 | /// Serves as the default hash function. 47 | public override int GetHashCode() 48 | { 49 | unchecked { 50 | var hashCode = 2; 51 | hashCode = hashCode * 3 * typeof(Maybe).GetHashCode(); 52 | if (Tag == MaybeType.Just) 53 | hashCode = hashCode * 3 + _value!.GetHashCode(); 54 | return hashCode; 55 | } 56 | } 57 | 58 | public override string ToString() => 59 | Tag switch { 60 | MaybeType.Just => new StringBuilder("Just(") 61 | .Append(_value) 62 | .Append(")") 63 | .ToString(), 64 | _ => "" 65 | }; 66 | 67 | #region Basic match methods 68 | /// Matches a value returning true and value itself via an output 69 | /// parameter. 70 | public bool MatchJust(out T? value) 71 | { 72 | value = Tag == MaybeType.Just ? _value : default; 73 | return Tag == MaybeType.Just; 74 | } 75 | 76 | /// Matches an empty value returning true. 77 | public bool MatchNothing() => Tag == MaybeType.Nothing; 78 | #endregion 79 | } 80 | -------------------------------------------------------------------------------- /src/SharpX/Types/Maybe/MaybeType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Discriminator for Maybe. 4 | public enum MaybeType 5 | { 6 | /// Computation case without a value. 7 | Nothing, 8 | /// Computation case with a value. 9 | Just 10 | } 11 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/Bad.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Represents the result of a failed computation. 4 | public sealed class Bad : Result 5 | { 6 | private readonly IEnumerable _messages; 7 | 8 | public Bad(IEnumerable messages) 9 | : base(ResultType.Bad) 10 | { 11 | Guard.DisallowNull(nameof(messages), messages); 12 | 13 | _messages = messages; 14 | } 15 | 16 | public IEnumerable Messages => _messages; 17 | } 18 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/Ok.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Represents the result of a successful computation. 4 | public sealed class Ok : Result 5 | { 6 | private readonly TSuccess _success; 7 | private readonly IEnumerable _messages; 8 | 9 | public Ok(TSuccess success, IEnumerable messages) 10 | : base(ResultType.Ok) 11 | { 12 | Guard.DisallowNull(nameof(success), success); 13 | Guard.DisallowNull(nameof(messages), messages); 14 | 15 | _success = success; 16 | _messages = messages; 17 | } 18 | 19 | public TSuccess Success => _success; 20 | 21 | public IEnumerable Messages => _messages; 22 | } 23 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace SharpX; 4 | 5 | /// Extensions methods for easier usage. 6 | public static class ResultExtensions 7 | { 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | /// Builds a Maybe type instance from a Result one. 10 | public static Maybe ToMaybe(this Result result) 11 | { 12 | Guard.DisallowNull(nameof(result), result); 13 | 14 | return result.Tag == ResultType.Ok 15 | ? Maybe.Just(((Ok)result).Success) 16 | : Maybe.Nothing(); 17 | } 18 | 19 | /// Allows pattern matching on Results. 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public static void Match(this Result result, 22 | Action> ifSuccess, 23 | Action> ifFailure) 24 | { 25 | Guard.DisallowNull(nameof(result), result); 26 | Guard.DisallowNull(nameof(ifSuccess), ifSuccess); 27 | Guard.DisallowNull(nameof(ifFailure), ifFailure); 28 | 29 | if (result is Ok ok) { 30 | ifSuccess(ok.Success, ok.Messages); 31 | return; 32 | } 33 | var bad = (Bad)result; 34 | ifFailure(bad.Messages); 35 | } 36 | 37 | /// Allows pattern matching on Results. 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public static TResult Either(this Result result, 40 | Func, TResult> ifSuccess, 41 | Func, TResult> ifFailure) => Trial.Either(result, ifSuccess, ifFailure); 42 | 43 | /// Lifts a Func into a Result and applies it on the given result. 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static Result Map( 46 | this Result result, Func func) => 47 | Trial.Lift(func, result); 48 | 49 | /// Collects a sequence of Results and accumulates their values. If the sequence 50 | /// contains an error the error will be propagated. 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static Result, TMessage> Collect( 53 | this IEnumerable> values) => Trial.Collect(values); 54 | 55 | /// Collects a sequence of Results and accumulates their values. If the sequence 56 | /// contains an error the error will be propagated. 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public static Result, TMessage> Flatten( 59 | this Result>, TMessage> result) 60 | { 61 | Guard.DisallowNull(nameof(result), result); 62 | 63 | if (result.Tag == ResultType.Ok) { 64 | var ok = (Ok>, TMessage>)result; 65 | var values = ok.Success; 66 | var result1 = Collect(values); 67 | if (result1.Tag == ResultType.Ok) { 68 | var ok1 = (Ok, TMessage>)result1; 69 | return new Ok, TMessage>(ok1.Success, ok1.Messages); 70 | } 71 | var bad1 = (Bad, TMessage>)result1; 72 | return new Bad, TMessage>(bad1.Messages); 73 | } 74 | var bad = (Bad>, TMessage>)result; 75 | return new Bad, TMessage>(bad.Messages); 76 | } 77 | 78 | /// If the result is a Success it executes the given Func on the value. Otherwise 79 | /// the exisiting failure is propagated. 80 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 81 | public static Result SelectMany(this Result result, 82 | Func> func) => Trial.Bind(result, func); 83 | 84 | /// If the result is a Success it executes the given Func on the value. If the result 85 | /// of the Func is a Success it maps it using the given Func. Otherwise the exisiting failure 86 | /// is propagated. 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | public static Result SelectMany( 89 | this Result result, 90 | Func> func, 91 | Func mapperFunc) 92 | { 93 | Guard.DisallowNull(nameof(result), result); 94 | Guard.DisallowNull(nameof(func), func); 95 | Guard.DisallowNull(nameof(mapperFunc), mapperFunc); 96 | 97 | Func> curriedMapper = suc => val => mapperFunc(suc, val); 98 | Func< 99 | Result, 100 | Result, 101 | Result 102 | > liftedMapper = (a, b) => Trial.Lift2(curriedMapper, a, b); 103 | var v = Trial.Bind(result, func); 104 | return liftedMapper(result, v); 105 | } 106 | 107 | /// Lifts a Func into a Result and applies it on the given result. 108 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 109 | public static Result Select(this Result result, 110 | Func func) => Trial.Lift(func, result); 111 | 112 | /// Returns the error messages or fails if the result was a success. 113 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 114 | public static IEnumerable FailedWith(this Result result) 115 | { 116 | Guard.DisallowNull(nameof(result), result); 117 | 118 | if (result.Tag == ResultType.Ok) { 119 | var ok = (Ok)result; 120 | throw new Exception( 121 | string.Format("Result was a success: {0} - {1}", 122 | ok.Success, 123 | string.Join(Environment.NewLine, ok.Messages.Select(m => m!.ToString())))); 124 | } 125 | var bad = (Bad)result; 126 | return bad.Messages; 127 | } 128 | 129 | /// Returns the result or fails if the result was an error. 130 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 131 | public static TSuccess SucceededWith(this Result result) 132 | { 133 | Guard.DisallowNull(nameof(result), result); 134 | 135 | if (result.Tag == ResultType.Ok) { 136 | var ok = (Ok)result; 137 | return ok.Success; 138 | } 139 | var bad = (Bad)result; 140 | throw new Exception( 141 | string.Format("Result was an error: {0}", 142 | string.Join(Environment.NewLine, bad.Messages.Select(m => m!.ToString())))); 143 | } 144 | 145 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 146 | /// Returns messages in case of success, otherwise an empty sequence. 147 | public static IEnumerable SuccessMessages(this Result result) 148 | { 149 | Guard.DisallowNull(nameof(result), result); 150 | 151 | return result.Tag == ResultType.Ok 152 | ? ((Ok)result).Messages 153 | : Enumerable.Empty(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/ResultOfT.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// Represents the result of a computation. 4 | public abstract class Result 5 | { 6 | private readonly ResultType _tag; 7 | 8 | protected Result(ResultType tag) 9 | { 10 | _tag = tag; 11 | } 12 | 13 | public ResultType Tag { get => _tag; } 14 | 15 | /// Creates a Failure result with the given messages. 16 | public static Result FailWith(IEnumerable messages) 17 | { 18 | Guard.DisallowNull(nameof(messages), messages); 19 | 20 | return new Bad(messages); 21 | } 22 | 23 | /// Creates a Failure result with the given message. 24 | public static Result FailWith(TMessage message) 25 | { 26 | Guard.DisallowNull(nameof(message), message); 27 | 28 | return new Bad(new[] { message }); 29 | } 30 | 31 | /// Creates a Success result with the given value. 32 | public static Result Succeed(TSuccess value) 33 | { 34 | Guard.DisallowNull(nameof(value), value); 35 | 36 | return new Ok(value, Enumerable.Empty()); 37 | } 38 | 39 | /// Creates a Success result with the given value and the given message. 40 | public static Result Succeed(TSuccess value, TMessage message) 41 | { 42 | Guard.DisallowNull(nameof(value), value); 43 | Guard.DisallowNull(nameof(message), message); 44 | 45 | return new Ok(value, new[] { message }); 46 | } 47 | 48 | /// Creates a Success result with the given value and the given messages. 49 | public static Result Succeed(TSuccess value, IEnumerable messages) 50 | { 51 | Guard.DisallowNull(nameof(value), value); 52 | Guard.DisallowNull(nameof(messages), messages); 53 | 54 | return new Ok(value, messages); 55 | } 56 | 57 | /// Executes the given function on a given success or captures the failure. 58 | public static Result Try(Func func) 59 | { 60 | Guard.DisallowNull(nameof(func), func); 61 | 62 | try { 63 | return new Ok( 64 | func(), Enumerable.Empty()); 65 | } 66 | catch (Exception ex) { 67 | return new Bad( 68 | new[] { ex }); 69 | } 70 | } 71 | 72 | public override string ToString() 73 | { 74 | switch (Tag) { 75 | default: 76 | var ok = (Ok)this; 77 | return string.Format( 78 | "OK: {0} - {1}", 79 | ok.Success, 80 | string.Join(Environment.NewLine, ok.Messages.Select(v => v!.ToString()))); 81 | case ResultType.Bad: 82 | var bad = (Bad)this; 83 | return string.Format( 84 | "Error: {0}", 85 | string.Join(Environment.NewLine, bad.Messages.Select(v => v!.ToString()))); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/ResultType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | public enum ResultType 4 | { 5 | Bad, 6 | Ok 7 | } 8 | -------------------------------------------------------------------------------- /src/SharpX/Types/Result/Trial.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Runtime.CompilerServices; 3 | 4 | namespace SharpX; 5 | 6 | public static class Trial 7 | { 8 | /// Wraps a value in a Success. 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static Result Ok(TSuccess value) => 11 | new Ok(value, Enumerable.Empty()); 12 | 13 | /// Wraps a value in a Success. 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static Result Pass(TSuccess value) => 16 | new Ok(value, Enumerable.Empty()); 17 | 18 | /// Wraps a value in a Success and adds a message. 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static Result Warn( 21 | TMessage message, TSuccess value) 22 | { 23 | Guard.DisallowNull(nameof(message), message); 24 | Guard.DisallowNull(nameof(value), value); 25 | 26 | return new Ok(value, new[] { message }); 27 | } 28 | 29 | /// Wraps a message in a Failure. 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static Result Fail(TMessage message) 32 | { 33 | Guard.DisallowNull(nameof(message), message); 34 | 35 | return new Bad(new[] { message }); 36 | } 37 | 38 | /// Returns true if the result was not successful. 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static bool Failed(Result result) 41 | { 42 | Guard.DisallowNull(nameof(result), result); 43 | 44 | return result.Tag == ResultType.Bad; 45 | } 46 | 47 | /// Takes a Result and maps it with successFunc if it is a Success otherwise it maps 48 | /// it with failureFunc. 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public static TResult Either( 51 | Result result, 52 | Func, TResult> successFunc, 53 | Func, TResult> failureFunc) 54 | { 55 | Guard.DisallowNull(nameof(result), result); 56 | Guard.DisallowNull(nameof(successFunc), successFunc); 57 | Guard.DisallowNull(nameof(failureFunc), failureFunc); 58 | 59 | if (result is Ok ok) { 60 | return successFunc(ok.Success, ok.Messages); 61 | } 62 | var bad = (Bad)result; 63 | return failureFunc(bad.Messages); 64 | } 65 | 66 | /// If the given result is a Success the wrapped value will be returned. Otherwise 67 | /// the function throws an exception with Failure message of the result. 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public static TSuccess ReturnOrFail(Result result) 70 | { 71 | Guard.DisallowNull(nameof(result), result); 72 | 73 | return Either(result, (succ, _) => succ, msgs => 74 | throw new Exception( 75 | string.Join( 76 | Environment.NewLine, msgs.Select(m => m.ToString()))) 77 | ); 78 | } 79 | 80 | /// Appends the given messages with the messages in the given result. 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | public static Result MergeMessages( 83 | Result result, 84 | IEnumerable messages) 85 | { 86 | Guard.DisallowNull(nameof(result), result); 87 | Guard.DisallowNull(nameof(messages), messages); 88 | 89 | return Either>(result, 90 | (succ, msgs) => new Ok(succ, messages.Concat(msgs)), 91 | errors => new Bad(errors.Concat(messages))); 92 | } 93 | 94 | /// If the result is a Success it executes the given function on the value. 95 | /// Otherwise the exisiting failure is propagated. 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public static Result Bind( 98 | Result result, 99 | Func> func) 100 | { 101 | Guard.DisallowNull(nameof(result), result); 102 | Guard.DisallowNull(nameof(func), func); 103 | 104 | return Either>(result, 105 | (succ, msgs) => MergeMessages(func(succ), msgs), 106 | messages => new Bad(messages)); 107 | } 108 | 109 | /// Flattens a nested result given the Failure types are equal. 110 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 111 | public static Result Flatten( 112 | Result, TMessage> result) => Bind(result, x => x); 113 | 114 | /// If the wrapped function is a success and the given result is a success the function 115 | /// is applied on the value. Otherwise the exisiting error messages are propagated. 116 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 117 | public static Result Apply( 118 | Result result, 119 | Result, TMessage> wrappedFunction) 120 | { 121 | Guard.DisallowNull(nameof(result), result); 122 | Guard.DisallowNull(nameof(wrappedFunction), wrappedFunction); 123 | 124 | if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Ok) { 125 | var ok1 = (Ok, TMessage>)wrappedFunction; 126 | var ok2 = (Ok)result; 127 | 128 | return new Ok( 129 | ok1.Success(ok2.Success), ok1.Messages.Concat(ok2.Messages)); 130 | } 131 | if (wrappedFunction.Tag == ResultType.Bad && result.Tag == ResultType.Ok) { 132 | return new Bad(((Bad)result).Messages); 133 | } 134 | if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Bad) { 135 | return new Bad( 136 | ((Bad)result).Messages); 137 | } 138 | 139 | var bad1 = (Bad, TMessage>)wrappedFunction; 140 | var bad2 = (Bad)result; 141 | return new Bad(bad1.Messages.Concat(bad2.Messages)); 142 | } 143 | 144 | /// Lifts a function into a Result container and applies it on the given 145 | /// result. 146 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 147 | public static Result Lift( 148 | Func func, 149 | Result result) => Apply(result, Ok, TMessage>(func)); 150 | 151 | /// Promotes a function to a monad/applicative, scanning the monadic/applicative 152 | /// arguments from left to right. 153 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 154 | public static Result Lift2( 155 | Func> func, 156 | Result first, 157 | Result second) => Apply(second, Lift(func, first)); 158 | 159 | /// Collects a sequence of Results and accumulates their values. If the sequence 160 | /// contains an error the error will be propagated. 161 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 162 | public static Result, TMessage> Collect( 163 | IEnumerable> results) 164 | { 165 | Guard.DisallowNull(nameof(results), results); 166 | 167 | return Lift(Enumerable.Reverse, 168 | results.Aggregate, Result, TMessage>, Result, TMessage>>( 169 | new Ok, TMessage>(Enumerable.Empty(), Enumerable.Empty()), 170 | (result, next) => 171 | { 172 | if (result.Tag == ResultType.Ok && next.Tag == ResultType.Ok) { 173 | var ok1 = (Ok, TMessage>)result; 174 | var ok2 = (Ok)next; 175 | return 176 | new Ok, TMessage>( 177 | Enumerable.Empty().Concat(new[] { ok2.Success }).Concat(ok1.Success), 178 | ok1.Messages.Concat(ok2.Messages)); 179 | } 180 | if (result.Tag == ResultType.Ok && next.Tag == ResultType.Bad) { 181 | return new Bad, TMessage>( 182 | ((Ok, TMessage>)result).Messages.Concat( 183 | ((Bad)next).Messages)); 184 | } 185 | if (result.Tag == ResultType.Bad && next.Tag == ResultType.Ok) { 186 | return new Bad, TMessage>( 187 | ((Bad, TMessage>)result).Messages.Concat( 188 | ((Ok)next).Messages)); 189 | } 190 | var bad1 = (Bad, TMessage>)result; 191 | var bad2 = (Bad)next; 192 | return new Bad, TMessage>(bad1.Messages.Concat(bad2.Messages)); 193 | }, x => x)); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/SharpX/Types/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace SharpX; 2 | 3 | /// The Unit type is a type that indicates the absence of a specific value. 4 | /// THE type has only a single value, which acts as a placeholder when no other value 5 | /// exists or is needed. 6 | public readonly struct Unit : IComparable 7 | { 8 | private static readonly Unit @default = new Unit(); 9 | 10 | /// Returns the hash code for this Unit. 11 | public override int GetHashCode() => 0; 12 | 13 | /// Determines whether this instance and a specified object, which must also be a 14 | /// Unit object, have the same value. 15 | public override bool Equals(object? obj) => obj == null || obj is Unit; 16 | 17 | /// Compares always to equality. 18 | public int CompareTo(object? obj) => 0; 19 | 20 | /// Converts this instance to a string representation. 21 | public override string ToString() => "()"; 22 | 23 | /// Unit singleton instance. 24 | public static Unit Default { get { return @default; } } 25 | 26 | /// Returns Unit after executing a delegate. 27 | public static Unit Do(Action action) 28 | { 29 | Guard.DisallowNull(nameof(action), action); 30 | 31 | action(); 32 | 33 | return @default; 34 | } 35 | 36 | /// Returns Unit after executing an async delegate. DoAsync(Func func) 38 | { 39 | Guard.DisallowNull(nameof(func), func); 40 | 41 | func().Wait(); 42 | 43 | return Task.FromResult(@default); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SharpX/Utilities/CryptoRandom.cs: -------------------------------------------------------------------------------- 1 | // Based on https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/september/net-matters-tales-from-the-cryptorandom. 2 | #pragma warning disable 8602, 8618 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Security.Cryptography; 6 | 7 | namespace SharpX; 8 | 9 | /// A thread safe random number generator based on the RNGCryptoServiceProvider. 10 | /// Initializes a new instance of the CryptoRandom class with optional random 11 | /// buffer. 12 | [Obsolete("CryptoRandom is obsolete. To generate a random number, use one of the RandomNumberGenerator static methods instead.")] 13 | public class CryptoRandom(bool enableRandomPool) : Random 14 | { 15 | private readonly RNGCryptoServiceProvider _rng = new(); 16 | private byte[] _buffer; 17 | private int _bufferPosition; 18 | 19 | /// Gets a value indicating whether this instance has random pool enabled. 20 | public bool IsRandomPoolEnabled { get; private set; } = enableRandomPool; 21 | 22 | /// Initializes a new instance of the CryptoRandom class with. Using this 23 | /// overload will enable the random buffer pool. 24 | public CryptoRandom() : this(true) { } 25 | 26 | /// Initializes a new instance of the CryptoRandom class. This method will 27 | /// disregard whatever value is passed as seed and it's only implemented in order to be fully 28 | /// backwards compatible with System.Random. Using this overload will enable the random 29 | /// buffer pool. 30 | [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ignoredSeed", 31 | Justification = "Cannot remove this parameter as we implement the full API of System.Random")] 32 | public CryptoRandom(int seed) : this(true) { } 33 | 34 | void InitBuffer() 35 | { 36 | if (IsRandomPoolEnabled) { 37 | if (_buffer == null || _buffer.Length != 512) { 38 | _buffer = new byte[512]; 39 | } 40 | } 41 | else { 42 | if (_buffer == null || _buffer.Length != 4) { 43 | _buffer = new byte[4]; 44 | } 45 | } 46 | _rng.GetBytes(_buffer); 47 | _bufferPosition = 0; 48 | } 49 | 50 | /// Returns a non-negative random integer. 51 | public override int Next() => 52 | // Mask away the sign bit so that we always return nonnegative integers 53 | (int)GetRandomUInt32() & 0x7FFFFFFF; 54 | 55 | /// Returns a non-negative random integer that is less than the specified 56 | /// maximum. 57 | public override int Next(int maxValue) 58 | { 59 | if (maxValue < 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); 60 | 61 | return Next(0, maxValue); 62 | } 63 | 64 | /// Returns a non-negative random integer that is within a specified range. 65 | public override int Next(int minValue, int maxValue) 66 | { 67 | if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue)); 68 | if (minValue == maxValue) return minValue; 69 | 70 | long diff = maxValue - minValue; 71 | while (true) { 72 | uint rand = GetRandomUInt32(); 73 | long max = 1 + (long)uint.MaxValue; 74 | long remainder = max % diff; 75 | if (rand < max - remainder) { 76 | return (int)(minValue + (rand % diff)); 77 | } 78 | } 79 | } 80 | 81 | /// Returns a random floating-point number that is greater than or equal to 0.0, and 82 | /// less than 1.0. 83 | public override double NextDouble() => GetRandomUInt32() / (1.0 + uint.MaxValue); 84 | 85 | /// Fills the elements of a specified array of bytes with random numbers. 86 | public override void NextBytes(byte[] buffer) 87 | { 88 | if (buffer == null) throw new ArgumentNullException(nameof(buffer)); 89 | 90 | lock (this) 91 | { 92 | if (IsRandomPoolEnabled && _buffer == null) { 93 | InitBuffer(); 94 | } 95 | // Can we fit the requested number of bytes in the buffer? 96 | if (IsRandomPoolEnabled && _buffer.Length <= buffer.Length) 97 | { 98 | int count = buffer.Length; 99 | EnsureRandomBuffer(count); 100 | Buffer.BlockCopy(_buffer, _bufferPosition, buffer, 0, count); 101 | _bufferPosition += count; 102 | } 103 | else { 104 | // Draw bytes directly from the RNGCryptoProvider 105 | _rng.GetBytes(buffer); 106 | } 107 | } 108 | } 109 | 110 | uint GetRandomUInt32() 111 | { 112 | lock (this) { 113 | EnsureRandomBuffer(4); 114 | uint rand = BitConverter.ToUInt32(_buffer, _bufferPosition); 115 | _bufferPosition += 4; 116 | return rand; 117 | } 118 | } 119 | 120 | void EnsureRandomBuffer(int requiredBytes) 121 | { 122 | if (_buffer == null) { 123 | InitBuffer(); 124 | } 125 | 126 | if (requiredBytes > _buffer.Length) throw new ArgumentOutOfRangeException(nameof(requiredBytes), 127 | "Cannot be greater than random buffer."); 128 | 129 | if ((_buffer.Length - _bufferPosition) < requiredBytes) { 130 | InitBuffer(); 131 | } 132 | } 133 | } 134 | 135 | static class _RandomNumberGeneratorCompatibility 136 | { 137 | #pragma warning disable CS0618 // Type or member is obsolete 138 | public static CryptoRandom _cryptoRandom = new CryptoRandom(); 139 | #pragma warning restore CS0618 140 | 141 | public static void GetBytes(byte[] data) => _cryptoRandom.NextBytes(data); 142 | 143 | public static byte[] GetBytes(int count) 144 | { 145 | var data = new byte[count]; 146 | _cryptoRandom.NextBytes(data); 147 | return data; 148 | } 149 | 150 | public static int GetInt32(int toExclusive)=> _cryptoRandom.Next(maxValue: toExclusive); 151 | 152 | public static int GetInt32(int fromInclusive, int toExclusive) => _cryptoRandom.Next(fromInclusive, toExclusive); 153 | } 154 | -------------------------------------------------------------------------------- /src/SharpX/Utilities/Guard.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace SharpX; 4 | 5 | public static class Guard 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static void DisallowNull(string argumentName, object? value) => 9 | _ = value ?? throw new ArgumentNullException(argumentName, $"{argumentName} cannot be null."); 10 | 11 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 12 | public static void DisallowDefault(string argumentName, T value) 13 | where T : struct 14 | { 15 | if (value.Equals(default(T))) throw new ArgumentNullException(argumentName, $"{argumentName} cannot be default."); 16 | } 17 | 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static void DisallowWhitespace(string argumentName, string value) 20 | { 21 | if (value.Any(c => char.IsWhiteSpace(c))) throw new ArgumentException( 22 | $"{argumentName} cannot be made of or contains only white spaces.", argumentName); 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static void DisallowEmptyWhitespace(string argumentName, string value) 27 | { 28 | if (value.Trim().Length == 0) throw new ArgumentException( 29 | $"{argumentName} cannot be empty or contains only white spaces.", argumentName); 30 | } 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public static void DisallowEmptyWhitespace(string argumentName, string[] value) 34 | { 35 | if (value.Any(s => s.Trim().Length == 0)) throw new ArgumentException( 36 | $"{argumentName} items cannot be empty or contains only white spaces.", argumentName); 37 | } 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static void DisallowNegative(string argumentName, int value) 41 | { 42 | if (value < 0) throw new ArgumentOutOfRangeException(argumentName, $"{argumentName} cannot be lesser than zero."); 43 | } 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public static void DisallowNegativeZero(string argumentName, int value) 47 | { 48 | if (value < 1) throw new ArgumentOutOfRangeException(argumentName, $"{argumentName} cannot be lesser than one."); 49 | } 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static void DisallowOdd(string argumentName, int value) 53 | { 54 | if (value % 2 != 0) throw new ArgumentOutOfRangeException(argumentName, $"{argumentName} cannot be odd."); 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public static void DisallowArraySize(string argumentName, int length, T[] value) 59 | { 60 | if (value.Length < length) throw new ArgumentException( 61 | $"{argumentName} cannot contain less than {length} elements.", argumentName); 62 | } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public static void DisallowEmptyArray(string argumentName, T[] value) 66 | { 67 | if (value.Length == 0) throw new ArgumentException($"{argumentName} cannot be empty.", argumentName); 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public static void DisallowEmptyEnumerable(string argumentName, IEnumerable value) 72 | { 73 | if (value.Count() == 0) throw new ArgumentException($"{argumentName} cannot be empty.", argumentName); 74 | } 75 | 76 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 | public static void DisallowMalformedGuid(string argumentName, string value) 78 | { 79 | try { Guid.Parse(value); } 80 | catch { throw new ArgumentException($"{argumentName} must be a well formed GUID.", argumentName); } 81 | } 82 | 83 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 84 | public static void DisallowNothing(string argumentName, Maybe value) 85 | { 86 | if (value.Tag == MaybeType.Nothing) throw new ArgumentException($"{argumentName} cannot be nothing.", argumentName); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/SharpX/paket.references: -------------------------------------------------------------------------------- 1 | group main 2 | FSharp.Core 3 | FsCheck 4 | Microsoft.NETCore.Platforms 5 | Microsoft.Extensions.Logging.Abstractions 6 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{csproj,config}] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | # C# files 6 | [*.cs] 7 | 8 | #### Core EditorConfig Options #### 9 | 10 | # Indentation and spacing 11 | indent_size = 4 12 | indent_style = space[] 13 | tab_width = 4 14 | 15 | # New line preferences 16 | end_of_line = crlf 17 | insert_final_newline = true 18 | 19 | #### .NET Coding Conventions #### 20 | 21 | # Organize usings 22 | dotnet_separate_import_directive_groups = false 23 | dotnet_sort_system_directives_first = true 24 | file_header_template = unset 25 | 26 | # this. and Me. preferences 27 | dotnet_style_qualification_for_event = false 28 | dotnet_style_qualification_for_field = false 29 | dotnet_style_qualification_for_method = false 30 | dotnet_style_qualification_for_property = false 31 | 32 | # Language keywords vs BCL types preferences 33 | dotnet_style_predefined_type_for_locals_parameters_members = true 34 | dotnet_style_predefined_type_for_member_access = true 35 | 36 | # Parentheses preferences 37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 38 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 39 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 40 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 41 | 42 | # Modifier preferences 43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 44 | 45 | # Expression-level preferences 46 | dotnet_style_coalesce_expression = true 47 | dotnet_style_collection_initializer = true 48 | dotnet_style_explicit_tuple_names = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_compound_assignment = true 54 | dotnet_style_prefer_conditional_expression_over_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_return = true 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 57 | dotnet_style_prefer_inferred_tuple_names = true 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 59 | dotnet_style_prefer_simplified_boolean_expressions = true 60 | dotnet_style_prefer_simplified_interpolation = true 61 | 62 | # Field preferences 63 | dotnet_style_readonly_field = true 64 | 65 | # Parameter preferences 66 | dotnet_code_quality_unused_parameters = all 67 | 68 | # Suppression preferences 69 | dotnet_remove_unnecessary_suppression_exclusions = none 70 | 71 | #### C# Coding Conventions #### 72 | 73 | # var preferences 74 | csharp_style_var_elsewhere = false 75 | csharp_style_var_for_built_in_types = false 76 | csharp_style_var_when_type_is_apparent = false 77 | 78 | # Expression-bodied members 79 | csharp_style_expression_bodied_accessors = true 80 | csharp_style_expression_bodied_constructors = false 81 | csharp_style_expression_bodied_indexers = true 82 | csharp_style_expression_bodied_lambdas = true 83 | csharp_style_expression_bodied_local_functions = false 84 | csharp_style_expression_bodied_methods = false 85 | csharp_style_expression_bodied_operators = false 86 | csharp_style_expression_bodied_properties = true 87 | 88 | # Pattern matching preferences 89 | csharp_style_pattern_matching_over_as_with_null_check = true 90 | csharp_style_pattern_matching_over_is_with_cast_check = true 91 | csharp_style_prefer_not_pattern = true 92 | csharp_style_prefer_pattern_matching = true 93 | csharp_style_prefer_switch_expression = true 94 | 95 | # Null-checking preferences 96 | csharp_style_conditional_delegate_call = true 97 | 98 | # Modifier preferences 99 | csharp_prefer_static_local_function = true 100 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 101 | 102 | # Code-block preferences 103 | csharp_prefer_braces = true 104 | csharp_prefer_simple_using_statement = true 105 | 106 | # Expression-level preferences 107 | csharp_prefer_simple_default_expression = true 108 | csharp_style_deconstructed_variable_declaration = true 109 | csharp_style_implicit_object_creation_when_type_is_apparent = true 110 | csharp_style_inlined_variable_declaration = true 111 | csharp_style_pattern_local_over_anonymous_function = true 112 | csharp_style_prefer_index_operator = true 113 | csharp_style_prefer_range_operator = true 114 | csharp_style_throw_expression = true 115 | csharp_style_unused_value_assignment_preference = discard_variable 116 | csharp_style_unused_value_expression_statement_preference = discard_variable 117 | 118 | # 'using' directive preferences 119 | csharp_using_directive_placement = outside_namespace 120 | 121 | #### C# Formatting Rules #### 122 | 123 | # New line preferences 124 | csharp_new_line_before_catch = true 125 | csharp_new_line_before_else = true 126 | csharp_new_line_before_finally = true 127 | csharp_new_line_before_members_in_anonymous_types = true 128 | csharp_new_line_before_members_in_object_initializers = true 129 | csharp_new_line_before_open_brace = anonymous_methods,anonymous_types,lambdas,methods,object_collection_array_initializers,properties,types 130 | csharp_new_line_between_query_expression_clauses = true 131 | 132 | # Indentation preferences 133 | csharp_indent_block_contents = true 134 | csharp_indent_braces = false 135 | csharp_indent_case_contents = true 136 | csharp_indent_case_contents_when_block = true 137 | csharp_indent_labels = one_less_than_current 138 | csharp_indent_switch_labels = true 139 | 140 | # Space preferences 141 | csharp_space_after_cast = false 142 | csharp_space_after_colon_in_inheritance_clause = true 143 | csharp_space_after_comma = true 144 | csharp_space_after_dot = false 145 | csharp_space_after_keywords_in_control_flow_statements = true 146 | csharp_space_after_semicolon_in_for_statement = true 147 | csharp_space_around_binary_operators = before_and_after 148 | csharp_space_around_declaration_statements = false 149 | csharp_space_before_colon_in_inheritance_clause = true 150 | csharp_space_before_comma = false 151 | csharp_space_before_dot = false 152 | csharp_space_before_open_square_brackets = false 153 | csharp_space_before_semicolon_in_for_statement = false 154 | csharp_space_between_empty_square_brackets = false 155 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 156 | csharp_space_between_method_call_name_and_opening_parenthesis = false 157 | csharp_space_between_method_call_parameter_list_parentheses = false 158 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 159 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 160 | csharp_space_between_method_declaration_parameter_list_parentheses = false 161 | csharp_space_between_parentheses = false 162 | csharp_space_between_square_brackets = false 163 | 164 | # Wrapping preferences 165 | csharp_preserve_single_line_blocks = true 166 | csharp_preserve_single_line_statements = true 167 | 168 | #### Naming styles #### 169 | 170 | # Naming rules 171 | 172 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 173 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 174 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 175 | 176 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 177 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 178 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 179 | 180 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 181 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 182 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 183 | 184 | # Symbol specifications 185 | 186 | dotnet_naming_symbols.interface.applicable_kinds = interface 187 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 188 | dotnet_naming_symbols.interface.required_modifiers = 189 | 190 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 191 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 192 | dotnet_naming_symbols.types.required_modifiers = 193 | 194 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 195 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 196 | dotnet_naming_symbols.non_field_members.required_modifiers = 197 | 198 | # Naming styles 199 | 200 | dotnet_naming_style.pascal_case.required_prefix = 201 | dotnet_naming_style.pascal_case.required_suffix = 202 | dotnet_naming_style.pascal_case.word_separator = 203 | dotnet_naming_style.pascal_case.capitalization = pascal_case 204 | 205 | dotnet_naming_style.begins_with_i.required_prefix = I 206 | dotnet_naming_style.begins_with_i.required_suffix = 207 | dotnet_naming_style.begins_with_i.word_separator = 208 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 209 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/EitherSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using FluentAssertions; 6 | using FsCheck; 7 | using FsCheck.Fluent; 8 | using FsCheck.Xunit; 9 | using SharpX; 10 | using SharpX.FsCheck; 11 | using Xunit; 12 | 13 | namespace Outcomes; 14 | 15 | public class EitherSpecs 16 | { 17 | [Property(Arbitrary = new[] { typeof(ArbitraryValue) })] 18 | public Property A_non_default_value_is_wrapped_into_a_Left(object leftValue) 19 | { 20 | Func property = () => new Either(leftValue).Equals(Either.Left(leftValue)); 21 | 22 | return property.When(leftValue != default); 23 | } 24 | 25 | [Property(Arbitrary = new[] { typeof(ArbitraryValue) })] 26 | public Property A_non_default_value_is_wrapped_into_a_Right(object rightValue) 27 | { 28 | Func property = () => new Either(rightValue).Equals(Either.Right(rightValue)); 29 | 30 | return property.When(rightValue != default); 31 | } 32 | 33 | [Fact] 34 | public void Trying_to_get_a_value_from_Left_with_FromLeftOrFail_raises_Exception_in_case_of_Right() 35 | { 36 | var sut = Either.Right(new CryptoRandom().Next()); 37 | 38 | Action action = () => sut.FromLeftOrFail(); 39 | 40 | action.Should().ThrowExactly() 41 | .WithMessage("The value is empty."); 42 | } 43 | 44 | [Fact] 45 | public void Trying_to_get_a_value_from_Right_with_FromRightOrFail_raises_Exception_in_case_of_Left() 46 | { 47 | var sut = Either.Left("bad result"); 48 | 49 | Action action = () => sut.FromRightOrFail(); 50 | 51 | action.Should().ThrowExactly() 52 | .WithMessage("The value is empty."); 53 | } 54 | 55 | [Fact] 56 | public void Shoud_partition_lefts_from_rights() 57 | { 58 | var eithers = new List>() 59 | { 60 | Either.Left("foo"), 61 | Either.Right(3), 62 | Either.Left("bar"), 63 | Either.Right(7), 64 | Either.Left("baz"), 65 | }; 66 | 67 | var outcome = eithers.Partition(); 68 | 69 | outcome.Should().NotBeNull(); 70 | outcome.Item1.Should().NotBeNullOrEmpty() 71 | .And.HaveCount(3) 72 | .And.ContainInOrder(eithers.Lefts()); 73 | outcome.Item2.Should().NotBeNullOrEmpty() 74 | .And.HaveCount(2) 75 | .And.ContainInOrder(eithers.Rights()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/EnumerableExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Metrics; 4 | using System.Linq; 5 | using FluentAssertions; 6 | using FsCheck; 7 | using FsCheck.Fluent; 8 | using FsCheck.Xunit; 9 | using SharpX; 10 | using SharpX.Extensions; 11 | using SharpX.FsCheck; 12 | using Xunit; 13 | 14 | namespace Outcomes; 15 | 16 | public class EnumerableExtensionsSpecs 17 | { 18 | #region TryHead 19 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 20 | public void Trying_to_get_the_head_element_of_a_sequence_should_return_Just( 21 | int[] values) 22 | { 23 | var outcome = values.TryHead(); 24 | 25 | outcome.Should().Match>(x => 26 | x.Tag == MaybeType.Just && x._value == values.ElementAt(0)); 27 | } 28 | 29 | [Fact] 30 | public void Trying_to_get_the_head_element_of_an_empty_sequence_should_return_Nothing() 31 | { 32 | var outcome = Enumerable.Empty().TryHead(); 33 | 34 | outcome.Tag.Should().Be(MaybeType.Nothing); 35 | } 36 | #endregion 37 | 38 | #region TryLast 39 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 40 | public void Trying_to_get_the_last_element_of_a_sequence_should_return_Just( 41 | int[] values) 42 | { 43 | var outcome = values.TryLast(); 44 | 45 | outcome.Should().Match>(x => 46 | x.Tag == MaybeType.Just && x._value == values.Last()); 47 | } 48 | 49 | [Fact] 50 | public void Trying_to_get_the_last_element_of_an_empty_sequence_should_return_Nothing() 51 | { 52 | var outcome = Enumerable.Empty().TryLast(); 53 | 54 | outcome.Tag.Should().Be(MaybeType.Nothing); 55 | } 56 | #endregion 57 | 58 | #region ToMaybe 59 | [Fact] 60 | public void An_empty_sequence_should_be_converted_to_Nothing() 61 | { 62 | var outcome = Enumerable.Empty().ToMaybe(); 63 | 64 | outcome.Should().Match>>(x => x.Tag == MaybeType.Nothing); 65 | } 66 | 67 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 68 | public void An_not_empty_sequence_should_be_converted_to_Just(int[] values) 69 | { 70 | var outcome = values.ToMaybe(); 71 | 72 | outcome.Tag.Should().Be(MaybeType.Just); 73 | } 74 | #endregion 75 | 76 | #region Choose 77 | [Theory] 78 | [InlineData( 79 | new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 80 | new int[] {0, 2, 4, 6, 8})] 81 | public void Should_choose_elements_to_create_a_new_sequence( 82 | IEnumerable values, IEnumerable expected) 83 | { 84 | var outcome = values.Choose(x => x % 2 == 0 85 | ? Maybe.Just(x) 86 | : Maybe.Nothing()); 87 | 88 | outcome.Should().BeEquivalentTo(expected); 89 | } 90 | #endregion 91 | 92 | #region Intersperse 93 | [Property] 94 | public void Should_intersperse_a_value_in_a_sequence(IntWithMinMax value) 95 | { 96 | var sequence = new int[] {0, 1, 2, 3, 4}; 97 | 98 | var outcome = sequence.Intersperse(value.Get); 99 | 100 | outcome.Should().NotBeNullOrEmpty() 101 | .And.HaveCount(sequence.Count() * 2 - 1) 102 | .And.SatisfyRespectively( 103 | item => item.Should().Be(0), 104 | item => item.Should().Be(value.Get), 105 | item => item.Should().Be(1), 106 | item => item.Should().Be(value.Get), 107 | item => item.Should().Be(2), 108 | item => item.Should().Be(value.Get), 109 | item => item.Should().Be(3), 110 | item => item.Should().Be(value.Get), 111 | item => item.Should().Be(4) 112 | ); 113 | } 114 | #endregion 115 | 116 | #region FlattenOnce 117 | [Fact] 118 | public void Should_flatten_a_sequence_by_one_level() 119 | { 120 | var sequence = new List>() 121 | { 122 | new int[] {0, 1, 2}, 123 | new int[] {3, 4, 5}, 124 | new int[] {6, 7, 8} 125 | }; 126 | 127 | var outcome = sequence.FlattenOnce(); 128 | 129 | outcome.Should().BeEquivalentTo(new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}); 130 | } 131 | #endregion 132 | 133 | #region Tail 134 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 135 | public void Should_return_the_tail_of_a_sequence(int[] values) 136 | { 137 | var outcome = values.Tail(); 138 | 139 | outcome.Should().HaveCount(values.Count() - 1) 140 | .And.BeEquivalentTo(values.Skip(1)); 141 | } 142 | 143 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 144 | public void Should_return_the_tail_of_a_sequence_using_TailOrEmpty(int[] values) 145 | { 146 | var outcome = values.TailOrEmpty(); 147 | 148 | outcome.Should().HaveCount(values.Count() - 1) 149 | .And.BeEquivalentTo(values.Skip(1)); 150 | } 151 | 152 | [Fact] 153 | public void Trying_to_get_the_tail_of_an_empty_sequence_throws_ArgumentException() 154 | { 155 | Action action = () => { foreach (var _ in Enumerable.Empty().Tail()) { } }; 156 | 157 | action.Should().ThrowExactly() 158 | .WithMessage("The input sequence has an insufficient number of elements."); 159 | } 160 | 161 | [Fact] 162 | public void Trying_to_get_the_tail_of_an_empty_sequence_returns_an_empty_sequence_using_TailOrEmpty() 163 | { 164 | var outcome = Enumerable.Empty().TailOrEmpty(); 165 | 166 | outcome.Should().HaveCount(0) 167 | .And.BeEquivalentTo(Enumerable.Empty()); 168 | } 169 | #endregion 170 | 171 | #region Materialize 172 | [Fact] 173 | public void Should_materialize_a_sequence() 174 | { 175 | Action action = () => NullYielder(true).Materialize(); 176 | 177 | action.Should().Throw(); 178 | 179 | IEnumerable NullYielder(bool raise) 180 | { 181 | if (raise) throw new Exception(); 182 | 183 | yield return null; 184 | } 185 | } 186 | #endregion 187 | 188 | #region ChunkBySize 189 | [Fact] 190 | public void Should_partition_a_sequence_by_chunk_size_into_arrays_without_remainder() 191 | { 192 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; 193 | 194 | var outcome = values.ChunkBySize(3); 195 | 196 | outcome.Should().NotBeNullOrEmpty() 197 | .And.HaveCount(3) 198 | .And.SatisfyRespectively( 199 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2 }), 200 | item => item.Should().BeEquivalentTo(new int[] { 3, 4, 5 }), 201 | item => item.Should().BeEquivalentTo(new int[] { 6, 7, 8 })); 202 | } 203 | 204 | [Fact] 205 | public void Should_partition_a_sequence_by_chunk_size_into_arrays_with_remainder() 206 | { 207 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 208 | 209 | var outcome = values.ChunkBySize(3); 210 | 211 | outcome.Should().NotBeNullOrEmpty() 212 | .And.HaveCount(4) 213 | .And.SatisfyRespectively( 214 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2 }), 215 | item => item.Should().BeEquivalentTo(new int[] { 3, 4, 5 }), 216 | item => item.Should().BeEquivalentTo(new int[] { 6, 7, 8 }), 217 | item => item.Should().BeEquivalentTo(new int[] { 9, 10 })); 218 | } 219 | 220 | [Fact] 221 | public void Should_partition_a_sequence_in_a_single_chunk_if_chunk_size_is_equal_than_elements_count() 222 | { 223 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 224 | 225 | var outcome = values.ChunkBySize(10); 226 | 227 | outcome.Should().NotBeNullOrEmpty() 228 | .And.HaveCount(1) 229 | .And.SatisfyRespectively( 230 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); 231 | } 232 | 233 | [Fact] 234 | public void Should_partition_a_sequence_in_a_single_chunk_if_chunk_size_is_greater_than_elements_count() 235 | { 236 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 237 | 238 | var outcome = values.ChunkBySize(11); 239 | 240 | outcome.Should().NotBeNullOrEmpty() 241 | .And.HaveCount(1) 242 | .And.SatisfyRespectively( 243 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); 244 | } 245 | 246 | [Theory] 247 | [InlineData(0)] 248 | [InlineData(-1)] 249 | public void Trying_to_partition_a_sequence_by_zero_or_less_chunks_throws_ArgumentException( 250 | int value) 251 | { 252 | Action action = () => { foreach (var _ in new[] { 0, 1, 2 }.ChunkBySize(value)) {}; }; 253 | 254 | action.Should().ThrowExactly() 255 | .WithMessage("The input must be positive."); 256 | } 257 | 258 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 259 | public void Trying_to_partition_a_sequence_by_a_negative_number_of_chunks_throws_ArgumentException( 260 | int[] values) 261 | { 262 | Action action = () => { foreach (var _ in values.ChunkBySize(-1)) {}; }; 263 | 264 | action.Should().ThrowExactly() 265 | .WithMessage("The input must be positive."); 266 | } 267 | #endregion 268 | 269 | #region SplitAt 270 | [Fact] 271 | public void Should_split_a_sequence_before_index() 272 | { 273 | var value = new int[] { 0, 1, 3, 4, 5 }; 274 | var outcome = value.SplitAt(3); 275 | 276 | outcome.Item1.Should().NotBeNullOrEmpty().And.HaveCount(3).And.ContainInOrder(0, 1, 3); 277 | outcome.Item2.Should().NotBeNullOrEmpty().And.HaveCount(2).And.ContainInOrder(4, 5); 278 | } 279 | 280 | [Fact] 281 | public void Left_array_is_empty_when_splitting_at_index_zero() 282 | { 283 | var value = new int[] { 0, 1, 3, 4, 5 }; 284 | var outcome = value.SplitAt(0); 285 | 286 | outcome.Item1.Should().BeEmpty(); 287 | outcome.Item2.Should().NotBeNullOrEmpty().And.BeEquivalentTo(value); 288 | } 289 | 290 | [Fact] 291 | public void Right_array_is_empty_when_splitting_with_index_equal_to_sequence_elements_count() 292 | { 293 | var value = new int[] { 0, 1, 3, 4, 5 }; 294 | var outcome = value.SplitAt(value.Count()); 295 | 296 | outcome.Item1.Should().NotBeNullOrEmpty().And.BeEquivalentTo(value); 297 | outcome.Item2.Should().BeEmpty(); 298 | } 299 | 300 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 301 | public void Trying_to_split_a_sequence_with_a_negative_index_throws_ArgumentException( 302 | int[] values) 303 | { 304 | Action action = () => values.SplitAt(-1); 305 | 306 | action.Should().ThrowExactly() 307 | .WithMessage("The input must be non-negative."); 308 | } 309 | 310 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 311 | public void Trying_to_split_a_sequence_with_an_index_greater_than_elements_count_throws_ArgumentException( 312 | int[] values) 313 | { 314 | Action action = () => values.SplitAt(values.Count() + 1); 315 | 316 | action.Should().ThrowExactly() 317 | .WithMessage("The input sequence has an insufficient number of elements."); 318 | } 319 | #endregion 320 | 321 | #region Choice 322 | [Fact] 323 | public void Should_choose_every_element_of_a_limited_seq_after_many_iterations() 324 | { 325 | var seq = Primitives.GenerateSeq(count: 3).Materialize(); 326 | var occurred = seq.ToDictionary(x => x, _ => false); 327 | 328 | for (var i = 0; i <= 999; i++) { 329 | occurred[seq.Choice()] = true; 330 | } 331 | 332 | occurred.Values.Should().AllBeEquivalentTo(true); 333 | } 334 | #endregion 335 | 336 | #region Shuffle 337 | // TODO: add a Guid arbitrary generator 338 | [Fact] 339 | public void Should_change_order_but_preserve_original_elements() 340 | { 341 | for (var i = 0; i < 100; i++) 342 | { 343 | var value = Guid.NewGuid().ToString(); 344 | 345 | var outcome = new string(value.Shuffle().ToArray()); 346 | 347 | outcome.Should().NotBe(value); 348 | outcome.Should().HaveLength(value.Length); 349 | outcome.ToArray().Should().Contain(value.ToArray()); 350 | } 351 | } 352 | #endregion 353 | 354 | #region Intersperse 355 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 356 | public void Should_intersperse_a_value(string[] values) 357 | { 358 | var arbitraryString = Strings.Generate(9); 359 | 360 | var outcome = values.Intersperse(arbitraryString); 361 | 362 | var outcomeSubset = outcome.Where((number, index) => index % 2 != 0); 363 | 364 | outcomeSubset.Should().NotBeNullOrEmpty() 365 | .And.AllBe(arbitraryString); 366 | } 367 | 368 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 369 | public void Intersperse_randomly_with_chance_zero_yields_original_sequence(string[] values) 370 | { 371 | var arbitraryString = Strings.Generate(9); 372 | 373 | var outcome = values.Intersperse(arbitraryString, chance: 0, count: 1); 374 | 375 | outcome.Should().BeEquivalentTo(values); 376 | } 377 | 378 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 379 | public void Intersperse_randomly_with_count_zero_yields_original_sequence(string[] values) 380 | { 381 | var arbitraryString = Strings.Generate(9); 382 | 383 | var outcome = values.Intersperse(arbitraryString, chance: 100, count: 0); 384 | 385 | outcome.Should().BeEquivalentTo(values); 386 | } 387 | 388 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 389 | public void Should_intersperse_a_value_randomly(string[] values) 390 | { 391 | var arbitraryString = Strings.Generate(9); 392 | 393 | var outcome = values.Intersperse(arbitraryString, chance: 50, count: 2); 394 | 395 | outcome.Should().NotBeNullOrEmpty() 396 | .And.Contain(arbitraryString) 397 | .And.Contain(values); 398 | } 399 | 400 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 401 | public void Should_intersperse_null_values(string[] values) 402 | { 403 | var input = values.Where(x => x != null); 404 | 405 | var outcome = input.Intersperse(null); 406 | 407 | outcome.Should().NotBeNullOrEmpty() 408 | .And.Contain((string)null) 409 | .And.Contain(input); 410 | } 411 | 412 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 413 | public void Should_intersperse_null_values_randomly(string[] values) 414 | { 415 | var input = values.Where(x => x != null); 416 | 417 | var outcome = input.Intersperse(null, chance: 50, count: 2); 418 | 419 | outcome.Should().NotBeNullOrEmpty() 420 | .And.Contain((string)null) 421 | .And.Contain(input); 422 | } 423 | #endregion 424 | 425 | #region DistinctCount 426 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegerSeq) })] 427 | public Property Count_squence_items_distinctly(int[] values) 428 | { 429 | return (values.DistinctCount() == values.Distinct().Count()).ToProperty(); 430 | } 431 | #endregion 432 | } 433 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/FSharpResultExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using FsCheck; 4 | using FsCheck.Xunit; 5 | using Microsoft.FSharp.Core; 6 | using SharpX; 7 | using SharpX.Extensions; 8 | using SharpX.FsCheck; 9 | using Xunit; 10 | 11 | namespace Outcomes; 12 | 13 | public class FSharpResultExtensionsSpecs 14 | { 15 | [Property] 16 | public void Should_match_a_result(IntWithMinMax value) 17 | { 18 | int? expected = null; 19 | var sut = FSharpResult.NewOk(value.Get); 20 | 21 | sut.Match( 22 | matched => expected = value.Get, 23 | _ => { throw new InvalidOperationException(); } 24 | ); 25 | 26 | expected.Should().Be(value.Get); 27 | } 28 | 29 | [Fact] 30 | public void Should_match_an_error() 31 | { 32 | string error = null; 33 | var sut = FSharpResult.NewError("bad result"); 34 | 35 | sut.Match( 36 | _ => { throw new InvalidOperationException(); }, 37 | message => { error = message; } 38 | ); 39 | 40 | error.Should().Be("bad result"); 41 | } 42 | 43 | [Property] 44 | public void Should_map_a_value(int value) 45 | { 46 | var sut = FSharpResult.NewOk(value); 47 | 48 | Func func = x => x / 0.5; 49 | var outcome = sut.Map(func); 50 | 51 | outcome.IsOk.Should().BeTrue(); 52 | outcome.ResultValue.Should().Be(func(value)); 53 | } 54 | 55 | [Fact] 56 | public void Should_keep_error_when_mapping_a_value_of_a_fail() 57 | { 58 | var sut = FSharpResult.NewError("bad result"); 59 | 60 | var outcome = sut.Map(x => x / 0.5); 61 | 62 | outcome.IsOk.Should().BeFalse(); 63 | outcome.ResultValue.Should().Be(default); 64 | } 65 | 66 | [Property] 67 | public void Should_bind_a_value(IntWithMinMax value) 68 | { 69 | var sut = FSharpResult.NewOk(value.Get); 70 | 71 | Func> func = 72 | x => FSharpResult.NewOk(x / 0.5); 73 | var outcome = sut.Bind(func); 74 | 75 | outcome.IsOk.Should().BeTrue(); 76 | outcome.ResultValue.Should().Be(func(value.Get).ResultValue); 77 | } 78 | 79 | [Fact] 80 | public void Should_keep_error_when_binding_a_value_of_fail() 81 | { 82 | var sut = FSharpResult.NewError("bad result"); 83 | 84 | var outcome = sut.Bind(x => FSharpResult.NewOk(x / 0.5)); 85 | 86 | outcome.IsOk.Should().BeFalse(); 87 | outcome.ResultValue.Should().Be(default); 88 | } 89 | 90 | [Property] 91 | public void Should_return_a_value(IntWithMinMax value) 92 | { 93 | var sut = FSharpResult.NewOk(value.Get); 94 | 95 | var outcome = sut.ReturnOrFail(); 96 | 97 | outcome.Should().Be(value.Get); 98 | } 99 | 100 | [Fact] 101 | public void Should_throws_exception_on_a_fail() 102 | { 103 | var sut = FSharpResult.NewError("bad result"); 104 | 105 | Action action = () => sut.ReturnOrFail(); 106 | 107 | action.Should().ThrowExactly() 108 | .WithMessage("bad result"); 109 | } 110 | 111 | [Property] 112 | public void Should_return_and_map_a_value(IntWithMinMax value) 113 | { 114 | var sut = FSharpResult.NewOk(value.Get); 115 | 116 | Func func = x => x / 0.5; 117 | var outcome = sut.Return(func, 0); 118 | 119 | outcome.Should().Be(func(value.Get)); 120 | } 121 | 122 | [Fact] 123 | public void Should_return_alternate_value_on_a_file() 124 | { 125 | var sut = FSharpResult.NewError("bad result"); 126 | 127 | Func func = x => x / 0.5; 128 | var outcome = sut.Return(func, 0); 129 | 130 | outcome.Should().Be(0); 131 | } 132 | 133 | [Property] 134 | public void Should_build_a_Maybe_Just_from_a_success(IntWithMinMax value) 135 | { 136 | var sut = FSharpResult.NewOk(value.Get); 137 | 138 | var outcome = sut.ToMaybe(); 139 | 140 | outcome.IsJust().Should().BeTrue(); 141 | outcome.FromJust().Should().Be(value.Get); 142 | } 143 | 144 | [Fact] 145 | public void Should_build_a_Maybe_Nothing_from_a_fail() 146 | { 147 | var sut = FSharpResult.NewError("bad result"); 148 | 149 | var outcome = sut.ToMaybe(); 150 | 151 | outcome.IsNothing().Should().BeTrue(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/LoggerExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging; 6 | using Moq; 7 | using Xunit; 8 | using Sut = SharpX.Extensions.LoggerExtensions; 9 | 10 | namespace Outcomes; 11 | 12 | public class LoggerExtensionsSpecs 13 | { 14 | private class EmptyObject { } 15 | 16 | [Theory] 17 | [InlineData("WarnWith")] 18 | [InlineData("FailWith")] 19 | [InlineData("PanicWith")] 20 | public void Methods_returns_a_reference_type(string methodName) 21 | { 22 | var logger = new Mock().Object; 23 | 24 | var outcome = methodName != "PanicWith" 25 | ? InvokeOnSut(methodName, logger, "Message {Data}", new EmptyObject(), new object[] { "data value" }) 26 | : InvokeOnSut(methodName, logger, "Message {Data}", new EmptyObject(), new Exception(), new object[] { "data value" }); 27 | 28 | outcome.Should().NotBeNull(); 29 | outcome.GetType().Should().Be(typeof(EmptyObject)); 30 | } 31 | 32 | [Theory] 33 | [InlineData("WarnWith")] 34 | [InlineData("FailWith")] 35 | [InlineData("PanicWith")] 36 | public void Methods_returns_a_value_type(string methodName) 37 | { 38 | var logger = new Mock().Object; 39 | 40 | var outcome = methodName != "PanicWith" 41 | ? InvokeOnSut(methodName, logger, "Message {Data}", false, new object[] { "data value" }) 42 | : InvokeOnSut(methodName, logger, "Message {Data}", false, new Exception(), new object[] { "data value" }); 43 | 44 | outcome.Should().NotBeNull(); 45 | outcome.GetType().Should().Be(typeof(bool)); 46 | } 47 | 48 | [Theory] 49 | [InlineData("WarnWith")] 50 | [InlineData("FailWith")] 51 | [InlineData("PanicWith")] 52 | public void Methods_raise_an_exception_when_passing_a_null_reference_type(string methodName) 53 | { 54 | var logger = new Mock().Object; 55 | 56 | Action test = () => 57 | { 58 | _ = methodName != "PanicWith" 59 | ? InvokeOnSut(methodName, logger, "Message {Data}", (EmptyObject)null, new object[] { "data value" }) 60 | : InvokeOnSut(methodName, logger, "Message {Data}", (EmptyObject)null, new Exception(), new object[] { "data value" }); 61 | }; 62 | 63 | test.Should().Throw() 64 | .WithInnerException().WithMessage("returnValue cannot be null. (Parameter 'returnValue')"); 65 | } 66 | 67 | private static object InvokeOnSut(string methodName, params object[] args) 68 | { 69 | var method = typeof(Sut).GetMethods(BindingFlags.Public | BindingFlags.Static) 70 | .Single(x => x.Name == methodName && x.IsGenericMethod); 71 | var generic = method.MakeGenericMethod(typeof(T)); 72 | 73 | return generic.Invoke(null, args); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/MaybeSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using FsCheck; 8 | using FsCheck.Fluent; 9 | using FsCheck.Xunit; 10 | using SharpX; 11 | using SharpX.Extensions; 12 | using SharpX.FsCheck; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | using static FsCheck.ResultContainer; 16 | 17 | namespace Outcomes; 18 | 19 | public class MaybeSpecs 20 | { 21 | static Random _random = new CryptoRandom(); 22 | 23 | [Property(Arbitrary = new[] { typeof(ArbitraryValue) })] 24 | public Property A_non_default_value_is_wrapped_into_a_Just(object value) 25 | { 26 | Func property = () => value.ToMaybe() == Maybe.Just(value); 27 | 28 | return property.When(value != default); 29 | } 30 | 31 | [Property(MaxTest=1)] 32 | public Property A_default_value_is_converted_into_a_Nothing() 33 | { 34 | return (((string)null).ToMaybe() == Maybe.Nothing()).ToProperty(); 35 | } 36 | 37 | [Property(MaxTest=1)] 38 | public Property Build_a_Just_from_a_null_value_throws_ArgumentException() 39 | { 40 | return FsCheck.FSharp.Prop.Throws>( 41 | new Lazy>(() => Maybe.Just((string)null))); 42 | } 43 | 44 | [Property] 45 | public Property Build_correct_maybe_with_a_value_type(IntWithMinMax value) 46 | { 47 | var outcome = Maybe.Return(value.Get); 48 | 49 | return (value switch 50 | { 51 | var v when v.Get == default => outcome.Tag == MaybeType.Nothing, 52 | _ => outcome.Tag == MaybeType.Just && outcome.FromJust() == value.Get, 53 | }).ToProperty(); 54 | } 55 | 56 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 57 | public Property Build_correct_maybe_with_a_reference_type(string value) 58 | { 59 | var outcome = Maybe.Return(value); 60 | 61 | return (value switch 62 | { 63 | var v when v == default => outcome.Tag == MaybeType.Nothing, 64 | _ => outcome.Tag == MaybeType.Just && outcome.FromJust() == value, 65 | }).ToProperty(); 66 | } 67 | 68 | 69 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 70 | public Property FromJust_unwraps_the_value_or_lazily_returns_from_a_function(string value) 71 | { 72 | Func func = () => "foo"; 73 | 74 | var sut = Maybe.Return(value); 75 | 76 | var outcome = sut.FromJust(func); 77 | 78 | return (value switch 79 | { 80 | var v when v == default => outcome == func(), 81 | _ => outcome == value, 82 | }).ToProperty(); 83 | } 84 | 85 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 86 | public Property ToEnumerable_returns_a_singleton_sequence_with_Just_and_an_empty_with_Nothing(string value) 87 | { 88 | var sut = Maybe.Return(value); 89 | 90 | var outcome = sut.ToEnumerable(); 91 | 92 | return (value switch 93 | { 94 | var v when v == default => outcome.Count() == 0, 95 | _ => outcome.ElementAt(0) == value, 96 | }).ToProperty(); 97 | } 98 | 99 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 100 | public Property Justs_method_extracts_Just_values_from_a_sequence(string[] values) 101 | { 102 | var maybes = from value in values select Maybe.Return(value); 103 | 104 | return (from value in values where value != default select value).SequenceEqual(maybes.Justs()).ToProperty(); 105 | } 106 | 107 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 108 | public Property Nothings_method_counts_Nothing_values_of_a_sequence(string[] values) 109 | { 110 | var maybes = from value in values select Maybe.Return(values); 111 | 112 | return (values.Length == maybes.Nothings()).ToProperty(); 113 | } 114 | 115 | [Property(Arbitrary = new[] { typeof(ArbitraryStringSeq) })] 116 | public Property Map_throws_out_Just_values_from_a_sequence(string[] values) 117 | { 118 | Func> readInt = value => { 119 | if (int.TryParse(value, out int result)) return Maybe.Just(result); 120 | return Maybe.Nothing(); }; 121 | 122 | var expected = from value in values where int.TryParse(value, out int _) 123 | select int.Parse(value); 124 | 125 | var outcome = values.Map(readInt); 126 | 127 | return expected.SequenceEqual(outcome).ToProperty(); 128 | } 129 | 130 | 131 | [Property] 132 | public Property Match_Just_of_anonymous_tuple_type_with_lambda_function(IntWithMinMax value1, IntWithMinMax value2) 133 | { 134 | var sut = Maybe.Just((value1.Get, value2.Get)); 135 | 136 | int? outcome1 = null; 137 | int? outcome2 = null; 138 | sut.Match( 139 | (value1, value2) => Unit.Do(() => { outcome1 = value1; outcome2 = value2; }), 140 | () => Unit.Default); 141 | 142 | return (value1.Get == outcome1.Value && value2.Get == outcome2.Value).ToProperty(); 143 | } 144 | 145 | [Property] 146 | public Property Match_Just_of_anonymous_tuple_type(IntWithMinMax value1, IntWithMinMax value2) 147 | { 148 | var sut = Maybe.Just((value1.Get, value2.Get)); 149 | 150 | var outcome = sut.MatchJust(out int outcome1, out int outcome2); 151 | 152 | return (outcome && value1.Get == outcome1 && value2.Get == outcome2).ToProperty(); 153 | } 154 | 155 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 156 | public Property Map_throws_out_and_map_a_Just_value_or_lazily_build_one_in_case_of_Nothing(string value) 157 | { 158 | Func func = () => "foo"; 159 | 160 | var sut = Maybe.Return(value); 161 | 162 | var outcome = sut.Map(v => v, func); 163 | 164 | return (value switch 165 | { 166 | var v when v == default => func() == outcome, 167 | _ => value == outcome, 168 | }).ToProperty(); 169 | } 170 | 171 | [Theory] 172 | [InlineData(0)] 173 | [InlineData(1)] 174 | public void Should_return_a_Just_when_Try_succeed_otherwise_Nothing(int value) 175 | { 176 | var number = Primitives.GenerateSeq(count: 1).Single(); 177 | var outcome = Maybe.Try(() => number / value); 178 | 179 | if (value == 0) outcome.Tag.Should().Be(MaybeType.Nothing); 180 | else outcome.Should().NotBeNull().And.Match>(x => 181 | x.Tag == MaybeType.Just && 182 | x._value == number); 183 | } 184 | 185 | [Property(MaxTest=1)] 186 | public Property Return_false_when_a_Maybe_is_compared_to_null() 187 | { 188 | var sut = Maybe.Return("foo"); 189 | 190 | return (!sut.Equals(null)).ToProperty(); 191 | } 192 | 193 | [Property(MaxTest=1)] 194 | public Property Nothing_values_wrapping_same_type_are_equals() 195 | { 196 | var sut1 = Maybe.Nothing(); 197 | var sut2 = Maybe.Nothing(); 198 | 199 | return sut1.Equals(sut2).ToProperty(); 200 | } 201 | 202 | [Property(MaxTest=1)] 203 | public Property Nothing_values_wrapping_same_type_compared_as_object_are_equals() 204 | { 205 | var sut1 = Maybe.Nothing(); 206 | object sut2 = Maybe.Nothing(); 207 | 208 | return sut1.Equals(sut2).ToProperty(); 209 | } 210 | 211 | [Property(MaxTest=1)] 212 | public Property Nothing_wrapping_different_type_are_different() 213 | { 214 | var sut1 = Maybe.Nothing(); 215 | var sut2 = Maybe.Nothing(); 216 | 217 | return (sut1.Equals(sut2) == false).ToProperty(); 218 | } 219 | 220 | 221 | [Property(MaxTest=1)] 222 | public Property Maybe_of_different_type_are_not_equals(NonZeroInt value) 223 | { 224 | var sut1 = Maybe.Nothing(); 225 | var sut2 = Maybe.Return(value.Get); 226 | 227 | return (sut1.Equals(sut2) == false).ToProperty(); 228 | } 229 | 230 | [Property] 231 | public Property Maybe_of_different_type_compared_as_object_are_not_equals(NonZeroInt value) 232 | { 233 | var sut1 = Maybe.Nothing(); 234 | object sut2 = Maybe.Return(value); 235 | 236 | return (sut1.Equals(sut2) == false).ToProperty(); 237 | } 238 | 239 | [Property] 240 | public Property Maybe_with_different_values_are_not_equals(NonZeroInt value) 241 | { 242 | var sut1 = Maybe.Return(value); 243 | var otherValue = _random.Next(); 244 | var sut2 = Maybe.Return(otherValue == value.Get ? otherValue / 2 : otherValue); 245 | 246 | return (sut1.Equals(sut2) == false).ToProperty(); 247 | } 248 | 249 | [Property] 250 | public Property Maybe_with_identical_values_are_equals(NonZeroInt value) 251 | { 252 | var sut1 = Maybe.Return(value.Get); 253 | var sut2 = Maybe.Return(value.Get); 254 | 255 | return sut1.Equals(sut2).ToProperty(); 256 | } 257 | 258 | [Property] 259 | public Property Just_with_identical_value_has_same_hash_code(PositiveInt value, PositiveInt count) 260 | { 261 | Func property = () => { 262 | var outcome = from item in Enumerable.Repeat(Maybe.Just(value), count.Get) 263 | select item.GetHashCode(); 264 | 265 | return Maybe.Just(value).GetHashCode() == outcome.Distinct().Single(); 266 | }; 267 | 268 | return property.When(count.Get > 1); 269 | } 270 | 271 | [Property] 272 | public Property Nothing_of_identical_type_has_same_hash_code() 273 | { 274 | return (((string)null).ToMaybe().GetHashCode() == Maybe.Nothing().GetHashCode()).ToProperty(); 275 | } 276 | 277 | [Property] 278 | public void Maybe_with_identical_values_compared_as_object_are_equals(NonZeroInt value) 279 | { 280 | 281 | var sut1 = Maybe.Return(value); 282 | object sut2 = Maybe.Return(value); 283 | 284 | var outcome = sut1.Equals(sut2); 285 | 286 | outcome.Should().BeTrue(); 287 | } 288 | 289 | 290 | [Property(MaxTest=1)] 291 | public Property Just_wrapping_different_values_have_different_hash_codes() 292 | { 293 | var sut1 = Maybe.Just(_random.Next(0, 9)); 294 | var sut2 = Maybe.Just(Strings.Generate(10)); 295 | 296 | return (sut1.GetHashCode() != sut2.GetHashCode()).ToProperty(); 297 | } 298 | 299 | [Property(MaxTest=1)] 300 | public Property Nothing_of_different_type_have_different_hash_codes() 301 | { 302 | var sut1 = Maybe.Nothing(); 303 | var sut2 = Maybe.Nothing(); 304 | 305 | return (sut1.GetHashCode() != sut2.GetHashCode()).ToProperty(); 306 | } 307 | 308 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 309 | public Property Do_method_consumes_the_value_only_on_Just(string value) 310 | { 311 | var evidence = false; 312 | var sut = Maybe.Return(value); 313 | 314 | sut.Do(_ => { 315 | evidence = true; 316 | return Unit.Default; 317 | }); 318 | 319 | if (sut.IsNothing()) evidence.Should().BeFalse(); 320 | else evidence.Should().BeTrue(); 321 | 322 | return (sut.Tag == MaybeType.Nothing 323 | ? evidence == false : evidence == true).ToProperty(); 324 | } 325 | 326 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 327 | public async Task DoAsync_method_consumes_the_value_only_on_Just(string value) 328 | { 329 | var evidence = false; 330 | var sut = Maybe.Return(value); 331 | 332 | await sut.DoAsync(_ => { 333 | evidence = true; 334 | return Task.FromResult(Unit.Default); 335 | }); 336 | 337 | return (sut.Tag == MaybeType.Nothing 338 | ? evidence == false : evidence == true).ToProperty(); 339 | } 340 | 341 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 342 | public Property Do_method_consumes_the_tuple_value_only_on_Just(string value) 343 | { 344 | var evidence = false; 345 | var sut = Maybe.Return((value, value)); 346 | 347 | sut.Do(_ => { 348 | evidence = true; 349 | return Unit.Default; 350 | }); 351 | 352 | return (sut.Tag == MaybeType.Nothing 353 | ? evidence == false : evidence == true).ToProperty(); 354 | } 355 | 356 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 357 | public async Task DoAsync_method_consumes_the_tuple_value_only_on_Just(string value) 358 | { 359 | var evidence = false; 360 | var sut = Maybe.Return((value, value)); 361 | 362 | await sut.DoAsync(_ => { 363 | evidence = true; 364 | return Task.FromResult(Unit.Default); 365 | }); 366 | 367 | return (sut.Tag == MaybeType.Nothing 368 | ? evidence == false : evidence == true).ToProperty(); 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/ObjectExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using FluentAssertions; 4 | using FsCheck.Experimental; 5 | using SharpX; 6 | using SharpX.Extensions; 7 | using Xunit; 8 | 9 | namespace Outcomes 10 | { 11 | public class ObjectExtensionsSpecs 12 | { 13 | class FakeObject 14 | { 15 | public string StringValue { get; set; } 16 | public int IntValue { get; set; } 17 | } 18 | 19 | static readonly Random _random = new CryptoRandom(); 20 | 21 | // NOTE: cannot use Theory for 'real' generic method test 22 | [Fact] 23 | public void Should_discards_anything_to_Unit() 24 | { 25 | ((byte)1).ToUnit().Should().Be(Unit.Default); 26 | 2.ToUnit().Should().Be(Unit.Default); 27 | 3.3.ToUnit().Should().Be(Unit.Default); 28 | "4".ToUnit().Should().Be(Unit.Default); 29 | typeof(ObjectExtensionsSpecs).ToUnit().Should().Be(Unit.Default); 30 | Assembly.GetCallingAssembly().ToUnit().Should().Be(Unit.Default); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/PrimitivesSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using FsCheck; 5 | using FsCheck.Fluent; 6 | using FsCheck.Xunit; 7 | using SharpX; 8 | using SharpX.FsCheck; 9 | using Xunit; 10 | 11 | namespace Outcomes; 12 | 13 | public class PrimitivesSpecs 14 | { 15 | [Property(Arbitrary = new[] { typeof(ArbitraryValue) })] 16 | public Property Converting_a_value_to_enumerable_is_equivalent_to_a_single_element_array(object value) 17 | { 18 | Func property = () => (new[] { value }).SequenceEqual(Primitives.ToEnumerable(value)); 19 | 20 | return property.When(value != default); 21 | } 22 | 23 | #region ChanceOf 24 | [Fact] 25 | public void Should_return_false_when_chance_is_0_percent() 26 | { 27 | var outcome = Primitives.ChanceOf(0); 28 | 29 | outcome.Should().BeFalse(); 30 | } 31 | 32 | [Fact] 33 | public void Should_return_true_when_chance_is_100_percent() 34 | { 35 | var outcome = Primitives.ChanceOf(100); 36 | 37 | outcome.Should().BeTrue(); 38 | } 39 | #endregion 40 | 41 | #region GenerateSeq 42 | [Property] 43 | public Property Generate_a_sequence_of_n_unique_items(int count) 44 | { 45 | Func property = () => { 46 | var outcome = Primitives.GenerateSeq(() => Strings.Generate(9), count: count); 47 | 48 | return outcome.Count() == count && 49 | outcome.Distinct().Count() == count; 50 | }; 51 | 52 | return property.When(count >= 0); 53 | } 54 | 55 | [Property] 56 | public Property Generate_an_infinite_sequence_of_n_unique_items(int taken) 57 | { 58 | Func property = () => { 59 | var outcome = Primitives.GenerateSeq(() => Strings.Generate(9), count: null) 60 | .Take(taken); 61 | 62 | return outcome.Count() == taken && 63 | outcome.Distinct().Count() == taken; 64 | }; 65 | 66 | return property.When(taken >= 0); 67 | } 68 | 69 | [Property] 70 | public Property Generate_a_sequence_of_n_unique_item_using_of_type_int(int count) => 71 | Generate_a_sequence_of_n_unique_item_using_of_type(count); 72 | 73 | [Property] 74 | public Property Generate_a_sequence_of_n_unique_item_using_of_type_double(int count) => 75 | Generate_a_sequence_of_n_unique_item_using_of_type(count); 76 | 77 | [Property] 78 | public Property Generate_a_sequence_of_n_unique_item_using_of_type_string(int count) => 79 | Generate_a_sequence_of_n_unique_item_using_of_type(count); 80 | 81 | Property Generate_a_sequence_of_n_unique_item_using_of_type(int count) 82 | { 83 | 84 | Func property = () => { 85 | var outcome = Primitives.GenerateSeq(count: count); 86 | 87 | var correct = outcome.Count() == count && outcome.Distinct().Count() == count; 88 | return correct; 89 | }; 90 | 91 | return property.When(count >= 0); 92 | } 93 | #endregion 94 | 95 | #region IsNumber 96 | // NOTE: cannot use Theory for 'real' generic method test 97 | [Fact] 98 | public void Should_return_true_for_numeric_types_and_false_for_any_other_type_or_null() 99 | { 100 | Primitives.IsNumber((object)null).Should().BeFalse(); 101 | Primitives.IsNumber(default(byte)).Should().BeTrue(); 102 | Primitives.IsNumber(default(short)).Should().BeTrue(); 103 | Primitives.IsNumber(default(ushort)).Should().BeTrue(); 104 | Primitives.IsNumber(default(int)).Should().BeTrue(); 105 | Primitives.IsNumber(default(uint)).Should().BeTrue(); 106 | Primitives.IsNumber(default(long)).Should().BeTrue(); 107 | Primitives.IsNumber(default(ulong)).Should().BeTrue(); 108 | Primitives.IsNumber(default(float)).Should().BeTrue(); 109 | Primitives.IsNumber(default(double)).Should().BeTrue(); 110 | Primitives.IsNumber(default(decimal)).Should().BeTrue(); 111 | Primitives.IsNumber("1234567890").Should().BeFalse(); 112 | Primitives.IsNumber(new object()).Should().BeFalse(); 113 | Primitives.IsNumber(new { empty = "" }).Should().BeFalse(); 114 | } 115 | #endregion 116 | 117 | #region SafeInvoke 118 | [Fact] 119 | public void SafeInvoke_swallows_an_exception() 120 | { 121 | Exception raised = null; 122 | try { 123 | Primitives.SafeInvoke(() => throw new Exception()); 124 | } 125 | catch (Exception ex) { 126 | raised = ex; 127 | } 128 | 129 | raised.Should().BeNull(); 130 | } 131 | #endregion 132 | } 133 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/ResultSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System; 4 | using System.Linq; 5 | using FluentAssertions; 6 | using SharpX; 7 | using Xunit; 8 | 9 | namespace Outcomes; 10 | 11 | public class Result 12 | { 13 | [Fact] 14 | public void Should_fail_when_Try_catches_an_exception() 15 | { 16 | var exn = new Exception("Hello World"); 17 | var result = Result.Try(() => { throw exn; }); 18 | exn.Should().Be(result.FailedWith().First()); 19 | } 20 | 21 | [Fact] 22 | public void Should_succeed_when_Try_completes_without_an_exception() 23 | { 24 | var result = Result.Try(() => "hello world"); 25 | "hello world".Should().Be(result.SucceededWith()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/StringExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using FsCheck.Fluent; 6 | using FsCheck.Xunit; 7 | using SharpX; 8 | using SharpX.Extensions; 9 | using Xunit; 10 | using Property = FsCheck.Property; 11 | 12 | namespace Outcomes; 13 | 14 | public class StringExtensionsSpecs 15 | { 16 | [Theory] 17 | [InlineData("foo", true)] 18 | [InlineData("0123456789", false)] 19 | [InlineData("foo01234", false)] 20 | [InlineData("foo.bar", false)] 21 | [InlineData("foo bar", false)] 22 | public void Should_detect_letter_characters(string value, bool expected) 23 | { 24 | var outcome = value.IsAlpha(); 25 | 26 | outcome.Should().Be(expected); 27 | } 28 | 29 | [Theory] 30 | [InlineData("foo", true)] 31 | [InlineData("0123456789", true)] 32 | [InlineData("foo01234", true)] 33 | [InlineData("foo.bar", false)] 34 | [InlineData("foo bar", false)] 35 | public void Should_detect_alphanumeric_characters(string value, bool expected) 36 | { 37 | var outcome = value.IsAlphanumeric(); 38 | 39 | outcome.Should().Be(expected); 40 | } 41 | 42 | [Theory] 43 | [InlineData("foo01234", false)] 44 | [InlineData("foo.bar", false)] 45 | [InlineData("foo bar", true)] 46 | [InlineData("foo\nbar", true)] 47 | [InlineData("foo\tbar", true)] 48 | public void Should_detect_whitespace_characters(string value, bool expected) 49 | { 50 | var outcome = value.ContainsWhitespace(); 51 | 52 | outcome.Should().Be(expected); 53 | } 54 | 55 | #region Sanitize 56 | [Theory] 57 | [InlineData("foo bar@", "foo bar")] 58 | [InlineData("foo\tbar@", "foo bar")] 59 | [InlineData("foo-bar@", "foobar")] 60 | public void Should_sanitize_strings_normalizing_white_spaces(string value, string expected) 61 | { 62 | var outcome = value.Sanitize(); 63 | 64 | outcome.Should().Be(expected); 65 | } 66 | 67 | [Theory] 68 | [InlineData("foo\nbar@", "foo\nbar")] 69 | [InlineData("foo\tbar@", "foo\tbar")] 70 | public void Should_sanitize_strings_without_normalizing_white_spaces(string value, string expected) 71 | { 72 | var outcome = value.Sanitize(normalizeWhiteSpace: false); 73 | 74 | outcome.Should().Be(expected); 75 | } 76 | #endregion 77 | 78 | [Theory] 79 | [InlineData("hello this is a test", new object[] {'!', "!!", 10}, "hello ! this !! is 10 a test")] 80 | public void Should_intersperse_values(string value, object[] values, string expected) 81 | { 82 | var outcome = value.Intersperse(values); 83 | 84 | outcome.Should().Be(expected); 85 | } 86 | 87 | [Theory] 88 | [InlineData("foo", 0, "", "")] 89 | [InlineData("foo", 1, "", "foo")] 90 | [InlineData("foo", 5, " ", "foo foo foo foo foo")] 91 | public void Should_replicate(string value, int count, string separator, string expected) 92 | { 93 | var outcome = value.Replicate(count, separator); 94 | 95 | outcome.Should().Be(expected); 96 | } 97 | 98 | #region Mangle 99 | [Theory] 100 | [InlineData("foo", 0, 0)] 101 | [InlineData("foo", 1, 1)] 102 | [InlineData("fooo", 3, 2)] 103 | [InlineData("foo bar", 3, 3)] 104 | public void Should_mangle(string value, int times, int maxLength) 105 | { 106 | int expectedMangleSize = times * maxLength; 107 | 108 | var outcome = value.Mangle(times, maxLength); 109 | 110 | outcome.Length.Should().Be(value.Length + expectedMangleSize); 111 | 112 | var mangleSize = (from @char in outcome.ToCharArray() 113 | where !char.IsLetterOrDigit(@char) && !char.IsWhiteSpace(@char) 114 | select @char).Count(); 115 | 116 | mangleSize.Should().Be(expectedMangleSize); 117 | } 118 | 119 | [Fact] 120 | public void Mangle_same_string_length_throws_ArgumentException() 121 | { 122 | Action action = () => "foo".Mangle(3, 3); 123 | 124 | action.Should().ThrowExactly() 125 | .WithMessage("times"); 126 | } 127 | 128 | [Fact] 129 | public void Mangle_beyond_string_length_throws_ArgumentException() 130 | { 131 | Action action = () => "foo bar baz".Mangle(100, 3); 132 | 133 | action.Should().ThrowExactly() 134 | .WithMessage("times"); 135 | } 136 | #endregion 137 | 138 | [Theory] 139 | [InlineData("foo", "foo")] 140 | [InlineData(" foo ", "foo")] 141 | [InlineData(" foo\t bar\t\t baz\t", "foo bar baz")] 142 | public void Should_normalize_white_spaces(string value, string expected) 143 | { 144 | var outcome = value.NormalizeWhiteSpace(); 145 | 146 | outcome.Should().Be(expected);; 147 | } 148 | 149 | [Theory] 150 | [InlineData("foo bar baz", 2, "foo bar baz")] 151 | [InlineData("fooo bar baz", 3, "fooo ")] 152 | public void Should_strip_by_length(string value, int length, string expected) 153 | { 154 | var outcome = value.StripByLength(length); 155 | 156 | outcome.Should().Be(expected); 157 | } 158 | 159 | [Theory] 160 | [InlineData( 161 | new string[] {"foo bar baz", "fooo baar baaz"}, 162 | new string[] {"foo", "bar", "baz", "fooo", "baar", "baaz"})] 163 | public void Should_flatten_a_string_sequence_into_words( 164 | IEnumerable values, IEnumerable expected) 165 | { 166 | var outcome = values.FlattenOnce(); 167 | 168 | outcome.Should().BeEquivalentTo(expected); 169 | } 170 | 171 | [Fact] 172 | public void Should_create_guid_from_a_guid_string() 173 | { 174 | var value = new Guid().ToString(); 175 | 176 | var outcome = value.ToGuid(safe: false); 177 | 178 | outcome.Should().Be(new Guid(value)); 179 | 180 | outcome = value.ToGuid(safe: true); 181 | 182 | outcome.Should().Be(new Guid(value)); 183 | } 184 | 185 | [Property(MaxTest=1)] 186 | public Property Convert_an_URI_string_to_an_Uri_instance() 187 | { 188 | return ("https://github.com/gsscoder/sharpx/wiki".ToUri() == new Uri("https://github.com/gsscoder/sharpx/wiki")) 189 | .ToProperty(); 190 | } 191 | 192 | [Property(MaxTest=1)] 193 | public Property An_invalid_URI_string_raises_an_exception_when_safe_is_false() 194 | { 195 | return ("not an Uri string".ToUri(safe: true) == default).ToProperty(); 196 | } 197 | 198 | [Property(MaxTest=1)] 199 | public Property An_invalid_URI_string_is_converted_to_default_when_safe_is_true() 200 | { 201 | 202 | return FsCheck.FSharp.Prop.Throws(new Lazy(() =>"not an Uri string".ToUri(safe: false))); 203 | } 204 | 205 | [Property(MaxTest=1)] 206 | public Property EqualsIgnoreCase_cannot_compare_null_if_safe_is_false() 207 | { 208 | return FsCheck.FSharp.Prop.Throws(new Lazy(() => ((string)null).EqualsIgnoreCase("foo", safe: false))); 209 | } 210 | 211 | [Fact] 212 | public void EqualsIgnoreCase_can_compare_null_if_safe_is_true() 213 | { 214 | ((string)null).EqualsIgnoreCase("foo", safe: true).Should().BeFalse(); 215 | } 216 | 217 | [Fact] 218 | public void EqualsIgnoreCase_compares_null_to_null_as_true() 219 | { 220 | ((string)null).EqualsIgnoreCase(null, safe: true).Should().BeTrue(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/StringsSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using FluentAssertions; 8 | using FsCheck; 9 | using FsCheck.Fluent; 10 | using FsCheck.Xunit; 11 | using SharpX; 12 | using SharpX.Extensions; 13 | using SharpX.FsCheck; 14 | using Xunit; 15 | 16 | namespace Outcomes; 17 | 18 | public class StringsSpecs 19 | { 20 | static readonly Random _random = new CryptoRandom(); 21 | 22 | [Theory] 23 | [InlineData('f', 0, "", "")] 24 | [InlineData('f', 1, "", "f")] 25 | [InlineData('f', 5, " ", "f f f f f")] 26 | public void Should_replicate(char value, int count, string separator, string expected) 27 | { 28 | var outcome = Strings.ReplicateChar(value, count, separator); 29 | 30 | outcome.Should().Be(expected); 31 | } 32 | 33 | #region Generate 34 | [Property] 35 | public void Trying_to_generate_a_random_string_with_less_than_one_char_raises_ArgumentException(NegativeInt value) 36 | { 37 | Action action = () => Strings.Generate(value.Get); 38 | 39 | action.Should().ThrowExactly(); 40 | } 41 | 42 | [Fact] 43 | public void Should_generate_an_empty_string_when_length_is_zero() 44 | { 45 | var outcome = Strings.Generate(0); 46 | 47 | outcome.Should().NotBeNull().And.BeEmpty(); 48 | } 49 | 50 | [Property] 51 | public void Should_generate_a_random_string_of_given_length(PositiveInt value) 52 | { 53 | var strings = new List() { Strings.Generate(_random.Next(1, 60)) }; 54 | 55 | var outcome = Strings.Generate(value.Get); 56 | 57 | outcome.Should().NotBeNull().And.HaveLength(value.Get); 58 | strings.Should().NotContain(outcome); 59 | 60 | strings.Add(outcome); 61 | } 62 | 63 | [Fact] 64 | public void Trying_to_set_allow_quotes_with_disallowed_special_chars_raises_ArgumentException() 65 | { 66 | Action action = () => Strings.Generate(1, new GenerateOptions() 67 | { 68 | AllowSpecialChars = false, 69 | AllowQuoteChars = true 70 | }); 71 | 72 | action.Should().ThrowExactly() 73 | .WithMessage("Cannot allow quote chars when special chars are disallowed. (Parameter 'options')"); 74 | } 75 | 76 | [Property] 77 | public void Should_generate_a_random_string_of_given_length_with_special_chars(PositiveInt value) 78 | { 79 | var outcome = Strings.Generate(value.Get + 10, 80 | new GenerateOptions { AllowSpecialChars = true, AllowQuoteChars = true }); 81 | 82 | outcome.Should().NotBeNull().And.HaveLength(value.Get + 10); 83 | outcome.Any(Strings.IsSpecialChar).Should().BeTrue(); 84 | } 85 | 86 | [Property] 87 | public void Should_generate_a_random_string_of_given_length_with_special_chars_no_quotes(PositiveInt value) 88 | { 89 | var outcome = Strings.Generate(value.Get + 10, 90 | new GenerateOptions { AllowSpecialChars = true, AllowQuoteChars = false }); 91 | 92 | outcome.Should().NotBeNull().And.HaveLength(value.Get + 10); 93 | outcome.Any(c => c == '"' || c == '\'' || c == '`').Should().BeFalse(); 94 | } 95 | 96 | [Property] 97 | public void Should_generate_a_random_string_of_given_length_with_prefix(PositiveInt value) 98 | { 99 | const string prefix = "prfx_"; 100 | 101 | var outcome = Strings.Generate(value.Get, null, prefix); 102 | 103 | outcome.Should().NotBeNull().And.HaveLength(value.Get + prefix.Length); 104 | outcome.Should().StartWith(prefix); 105 | } 106 | 107 | [Fact] 108 | public void Random_strings_have_no_whitespace_characters() 109 | { 110 | var outcomes = Primitives.GenerateSeq(() => Strings.Generate(99), 99); 111 | 112 | outcomes.Should().NotContain(x => x.ContainsWhitespace()); 113 | } 114 | 115 | [Fact] 116 | public void Should_generate_a_random_string_of_random_length_when_length_is_not_specified() 117 | { 118 | var outcome = Strings.Generate(); 119 | 120 | outcome.Should().NotBeNullOrWhiteSpace(); 121 | outcome.Length.Should().BeInRange(8, 32); 122 | } 123 | #endregion 124 | 125 | [Theory] 126 | [InlineData("")] 127 | [InlineData(" ")] 128 | [InlineData(" ")] 129 | [InlineData(" \n ")] 130 | [InlineData(" \r ")] 131 | [InlineData(" \t ")] 132 | public void Should_not_detect_empty_string_or_whitespace_as_special_character(string value) 133 | { 134 | var outcome = Strings.ContainsSpecialChar(value); 135 | 136 | outcome.Should().BeFalse(); 137 | } 138 | 139 | [Theory] 140 | [InlineData("foobar/baz")] 141 | [InlineData("foo#?bar/baz")] 142 | [InlineData("fo!ob_ar|baz")] 143 | [InlineData("foob?ar^baz")] 144 | [InlineData("foo@bar_baz")] 145 | [InlineData("foobar[b]]az")] 146 | [InlineData("f00barbaz_")] 147 | public void Should_detect_strings_with_special_characters(string value) 148 | { 149 | var outcome = Strings.ContainsSpecialChar(value); 150 | 151 | outcome.Should().BeTrue(); 152 | } 153 | 154 | [Theory] 155 | [InlineData("foobarbaz")] 156 | [InlineData("foo bar baz")] 157 | [InlineData("fòòbàrbàz")] 158 | [InlineData("f00b4rb4z")] 159 | public void Should_detect_strings_without_special_characters(string value) 160 | { 161 | var outcome = Strings.ContainsSpecialChar(value); 162 | 163 | outcome.Should().BeFalse(); 164 | } 165 | 166 | [Theory] 167 | [InlineData("foo_bar_baz")] 168 | [InlineData("foo bar baz")] 169 | [InlineData("fòòbàrbàz")] 170 | [InlineData(" \n\r\t" )] 171 | [InlineData("f00-b4r-b4z")] 172 | public void Should_detect_strings_without_special_characters_except_excluded(string value) 173 | { 174 | var outcome = Strings.ContainsSpecialChar(value, '_', '-'); 175 | 176 | outcome.Should().BeFalse(); 177 | } 178 | 179 | [Theory] 180 | [InlineData("")] 181 | [InlineData(" ")] 182 | [InlineData("\t")] 183 | [InlineData("\r")] 184 | [InlineData("\n")] 185 | [InlineData("\t \r \n")] 186 | public void Should_detect_empty_or_whitespace_string(string value) 187 | { 188 | var outcome = Strings.IsEmptyWhitespace(value); 189 | 190 | outcome.Should().BeTrue(); 191 | } 192 | 193 | [Fact] 194 | public void Should_get_an_empty_string_when_start_index_and_length_are_zero() 195 | { 196 | var outcome = Strings.Substring(string.Empty, 0, 0, safe: true); 197 | 198 | outcome.Should().Be(string.Empty); 199 | } 200 | 201 | [Theory] 202 | [InlineData("foo")] 203 | [InlineData("foo bar")] 204 | [InlineData("foo bar baz")] 205 | public void Should_get_same_string_when_start_index_and_length_match_string_length(string value) 206 | { 207 | var outcome = Strings.Substring(value, 0, value.Length, safe: true); 208 | 209 | outcome.Should().Be(value); 210 | } 211 | 212 | [Theory] 213 | [InlineData("foo")] 214 | [InlineData("baar")] 215 | [InlineData("baaaz")] 216 | public void Should_get_a_substring_without_exception_even_when_length_exceeds_input_string_length(string value) 217 | { 218 | var input = Strings.Mangle(Strings.Replicate(value, _random.Next(1, 3), separator: "@"), 219 | times: 2, maxLength: 3); 220 | 221 | var outcome = Strings.Substring(input, 0, input.Length * _random.Next(2, 3), safe: true); 222 | 223 | outcome.Should().Be(input); 224 | } 225 | 226 | [Theory] 227 | [InlineData(null)] 228 | [InlineData("")] 229 | [InlineData(" ")] 230 | public void Should_normalize_a_null_or_white_space_string_to_an_empty_string(string value) 231 | { 232 | var outcome = Strings.NormalizeToEmpty(value); 233 | 234 | outcome.Should().Be(string.Empty); 235 | } 236 | 237 | [Theory] 238 | [InlineData("foo")] 239 | [InlineData("bar baz")] 240 | public void Should_not_normalize_a_string_that_is_not_null_or_white_space_to_an_empty_string(string value) 241 | { 242 | var outcome = Strings.NormalizeToEmpty(value); 243 | 244 | outcome.Should().Be(value); 245 | } 246 | 247 | 248 | #region RandomizeCase 249 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 250 | public Property RandomizeCase__Characters_case_is_randomized_in_strings_with_letters(NonNull value) 251 | { 252 | var randomized = Strings.RandomizeCase(value.Get); 253 | 254 | return (randomized != value.Get && randomized.EqualsIgnoreCase(value.Get)) 255 | .When(value.Get.Any(Char.IsLetter)); 256 | } 257 | 258 | [Property(Arbitrary = new[] { typeof(ArbitraryString) })] 259 | public Property RandomizeCase__Strings_without_characters_are_returned_unchanged(NonNull value) 260 | { 261 | var value_ = new string(value.Get.Where(c => !Char.IsLetter(c)).ToArray()); 262 | var unchanged = Strings.RandomizeCase(value_); 263 | 264 | return (unchanged == value_).ToProperty(); 265 | } 266 | 267 | [Fact] 268 | public void RandomizeCase__Empty_string_is_returned_unchanged() 269 | { 270 | Strings.RandomizeCase(String.Empty).Should().Be(String.Empty); 271 | } 272 | 273 | [Theory] 274 | [InlineData("any kind of")] 275 | [InlineData("string")] 276 | [InlineData("without diacritics")] 277 | [InlineData("is changed 0 times")] 278 | public void RemoveDiacritics__Strings_without_diacritics_remain_unchanged(string value) 279 | { 280 | Strings.RemoveDiacritics(value).Should().Be(value); 281 | } 282 | 283 | [Theory] 284 | [InlineData("âny kỉnd ōf", "any kind of")] 285 | [InlineData("strỉng", "string")] 286 | [InlineData("wïthōut diâcritics", "without diacritics")] 287 | [InlineData("ïs chânged 0 timếs", "is changed 0 times")] 288 | public void RemoveDiacritics__Strings_with_diacritics_are_normalized(string input, string output) 289 | { 290 | Strings.RemoveDiacritics(input).Should().Be(output); 291 | } 292 | #endregion 293 | } 294 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/TrailSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System.Collections.Generic; 4 | using Xunit; 5 | using FluentAssertions; 6 | using SharpX; 7 | 8 | namespace Outcomes; 9 | 10 | public class TrailSpecs 11 | { 12 | [Fact] 13 | public void Should_collect_successes() 14 | { 15 | var ok1 = Result.Succeed(1, "ok 1"); 16 | var ok2 = Result.Succeed(2); 17 | var ok3 = Result.Succeed(2, "ok 3"); 18 | var sut = Trial.Collect(new Result[] { ok1, ok2, ok3 }); 19 | 20 | var outcome = sut as Ok, string>; 21 | 22 | outcome.Should().NotBeNull(); 23 | outcome.Success.Should().NotBeNullOrEmpty().And 24 | .ContainInOrder(1,2); 25 | outcome.Messages.Should().NotBeNullOrEmpty().And 26 | .ContainInOrder("ok 1", "ok 3"); 27 | } 28 | 29 | [Fact] 30 | public void Should_propagate_fails() 31 | { 32 | var ok1 = Result.Succeed(1, "ok 1"); 33 | var ok2 = Result.Succeed(2); 34 | var fail1 = Result.FailWith("non ok 1"); 35 | var fail2 = Result.FailWith("non ok 2"); 36 | var sut = Trial.Collect(new Result[] { ok1, ok2, fail1, fail2 }); 37 | 38 | var outcome = sut as Bad, string>; 39 | 40 | outcome.Should().NotBeNull(); 41 | outcome.Messages.Should().NotBeNullOrEmpty().And 42 | .ContainInOrder("ok 1", "non ok 1", "non ok 2"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Outcomes/UnitSpecs.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0618 2 | 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using FluentAssertions; 6 | using FluentAssertions.Equivalency; 7 | using FsCheck; 8 | using FsCheck.Fluent; 9 | using FsCheck.Xunit; 10 | using SharpX; 11 | using SharpX.Extensions; 12 | using Xunit; 13 | 14 | namespace Outcomes; 15 | 16 | public class UnitSpecs 17 | { 18 | [Fact] 19 | public void Unit_values_should_be_equals() 20 | { 21 | var sut1 = new Unit(); 22 | var sut2 = new Unit(); 23 | 24 | var outcome = sut1.Equals(sut2); 25 | 26 | outcome.Should().BeTrue(); 27 | } 28 | 29 | [Fact] 30 | public void Unit_values_should_compare_to_equality() 31 | { 32 | var sut1 = new Unit(); 33 | var sut2 = new Unit(); 34 | 35 | var outcome = sut1.CompareTo(sut2); 36 | 37 | outcome.Should().Be(0); 38 | } 39 | 40 | [Fact] 41 | public void Do_executes_a_delegate_and_returns_Unit_value() 42 | { 43 | var evidence = 0; 44 | 45 | var outcome = Unit.Do(() => evidence++); 46 | 47 | evidence.Should().Be(1); 48 | outcome.Should().Be(Unit.Default); 49 | } 50 | 51 | [Fact] 52 | public async Task Do_executes_an_async_delegate_and_returns_Unit_value() 53 | { 54 | var evidence = 0; 55 | 56 | var outcome = await Unit.DoAsync(() => { 57 | evidence++; 58 | return Task.CompletedTask; 59 | }); 60 | 61 | evidence.Should().Be(1); 62 | outcome.Should().Be(Unit.Default); 63 | } 64 | 65 | [Property(MaxTest=1)] 66 | public FsCheck.Property Chain_Unit_values_with_And_extension() 67 | { 68 | return FsCheck.Fluent.Prop.ToProperty( 69 | Unit.Do(() => VoidMethod()) 70 | .And(DoSomething().ToUnit()).Equals(Unit.Default)); 71 | 72 | void VoidMethod() {} 73 | int DoSomething() => 3 * 2; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/SharpX.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | net8.0 8 | 13.0 9 | false 10 | gsscoder 11 | gsscoder 12 | SharpX 13 | CS0618 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Tests/NightClubsValidation.cs: -------------------------------------------------------------------------------- 1 | // From https://github.com/fsprojects/fsharpx/blob/master/tests/FSharpx.CSharpTests/ValidationExample.cs. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using FluentAssertions; 7 | using SharpX; 8 | using Xunit; 9 | 10 | namespace Tests; 11 | 12 | enum Sobriety { Sober, Tipsy, Drunk, Paralytic, Unconscious } 13 | enum Gender { Male, Female } 14 | 15 | class Person 16 | { 17 | public Gender Gender { get; private set; } 18 | public int Age { get; private set; } 19 | public List Clothes { get; private set; } 20 | public Sobriety Sobriety { get; private set; } 21 | 22 | public Person(Gender gender, int age, List clothes, Sobriety sobriety) 23 | { 24 | Gender = gender; 25 | Age = age; 26 | Clothes = clothes; 27 | Sobriety = sobriety; 28 | } 29 | } 30 | 31 | class Club 32 | { 33 | public static Result CheckAge(Person p) 34 | { 35 | if (p.Age < 18) { 36 | return Result.FailWith("Too young!"); 37 | } 38 | if (p.Age > 40) { 39 | return Result.FailWith("Too old!"); 40 | } 41 | return Result.Succeed(p); 42 | } 43 | 44 | public static Result CheckClothes(Person p) 45 | { 46 | if (p.Gender == Gender.Male && !p.Clothes.Contains("Tie")) { 47 | return Result.FailWith("Smarten up!"); 48 | } 49 | if (p.Gender == Gender.Female && p.Clothes.Contains("Trainers")) { 50 | return Result.FailWith("Wear high heels!"); 51 | } 52 | return Result.Succeed(p); 53 | } 54 | 55 | public static Result CheckSobriety(Person p) 56 | { 57 | if (new[] { Sobriety.Drunk, Sobriety.Paralytic, Sobriety.Unconscious } 58 | .Contains(p.Sobriety)) { 59 | return Result.FailWith("Sober up!"); 60 | } 61 | return Result.Succeed(p); 62 | } 63 | } 64 | 65 | class ClubbedToDeath 66 | { 67 | public static Result CostToEnter(Person p) 68 | { 69 | return from a in Club.CheckAge(p) 70 | from b in Club.CheckClothes(a) 71 | from c in Club.CheckSobriety(b) 72 | select c.Gender == Gender.Female ? 0m : 5m; 73 | } 74 | } 75 | 76 | public class Test1 77 | { 78 | [Fact] 79 | public void Part1() 80 | { 81 | var Dave = new Person(Gender.Male, 41, new List { "Tie", "Jeans" }, Sobriety.Sober); 82 | var costDave = ClubbedToDeath.CostToEnter(Dave); 83 | "Too old!".Should().Be(costDave.FailedWith().First()); 84 | 85 | var Ken = new Person(Gender.Male, 28, new List { "Tie", "Shirt" }, Sobriety.Tipsy); 86 | var costKen = ClubbedToDeath.CostToEnter(Ken); 87 | 5m.Should().Be(costKen.SucceededWith()); 88 | 89 | var Ruby = new Person(Gender.Female, 25, new List { "High heels" }, Sobriety.Tipsy); 90 | var costRuby = ClubbedToDeath.CostToEnter(Ruby); 91 | costRuby.Match( 92 | (x, msgs) => 93 | { 94 | 0m.Should().Be(x); 95 | }, 96 | msgs => 97 | { 98 | Assert.True(false, "fail"); 99 | }); 100 | 101 | var Ruby17 = new Person(Ruby.Gender, 17, Ruby.Clothes, Ruby.Sobriety); 102 | var costRuby17 = ClubbedToDeath.CostToEnter(Ruby17); 103 | "Too young!".Should().Be(costRuby17.FailedWith().First()); 104 | 105 | var KenUnconscious = new Person(Ken.Gender, Ken.Age, Ken.Clothes, Sobriety.Unconscious); 106 | var costKenUnconscious = ClubbedToDeath.CostToEnter(KenUnconscious); 107 | costKenUnconscious.Match( 108 | (x, msgs) => 109 | { 110 | Assert.True(false, "fail"); 111 | }, 112 | msgs => 113 | { 114 | "Sober up!".Should().Be(msgs.First()); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/Tests/SimpleValidation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using SharpX; 5 | using Xunit; 6 | 7 | namespace Tests; 8 | 9 | public class Request 10 | { 11 | public string Name { get; set; } 12 | public string EMail { get; set; } 13 | } 14 | 15 | public class Validation 16 | { 17 | public static Result ValidateInput(Request input) 18 | { 19 | if (input.Name == "") { 20 | return Result.FailWith("Name must not be blank"); 21 | } 22 | if (input.EMail == "") { 23 | return Result.FailWith("Email must not be blank"); 24 | } 25 | return Result.Succeed(input); 26 | 27 | } 28 | } 29 | 30 | public class SimpleValidation 31 | { 32 | [Fact] 33 | public void CanCreateSuccess() 34 | { 35 | var request = new Request { Name = "Giacomo", EMail = "mail@support.com" }; 36 | var result = Validation.ValidateInput(request); 37 | request.Should().Be(result.SucceededWith()); 38 | } 39 | } 40 | 41 | public class SimplePatternMatching 42 | { 43 | [Fact] 44 | public void CanMatchSuccess() 45 | { 46 | var request = new Request { Name = "Giacomo", EMail = "mail@support.com" }; 47 | var result = Validation.ValidateInput(request); 48 | result.Match( 49 | (x, msgs) => { request.Should().Be(x); }, 50 | msgs => { throw new Exception("wrong match case"); }); 51 | } 52 | 53 | [Fact] 54 | public void CanMatchFailure() 55 | { 56 | var request = new Request { Name = "Giacomo", EMail = "" }; 57 | var result = Validation.ValidateInput(request); 58 | result.Match( 59 | (x, msgs) => { throw new Exception("wrong match case"); }, 60 | msgs => { "Email must not be blank".Should().Be(msgs.ElementAt(0)); }); 61 | } 62 | } 63 | 64 | public class SimpleEitherPatternMatching 65 | { 66 | [Fact] 67 | public void CanMatchSuccess() 68 | { 69 | var request = new Request { Name = "Giacomo", EMail = "mail@support.com" }; 70 | var result = 71 | Validation 72 | .ValidateInput(request) 73 | .Either( 74 | (x, msgs) => x, 75 | msgs => { throw new Exception("wrong match case"); }); 76 | request.Should().Be(result); 77 | } 78 | 79 | [Fact] 80 | public void CanMatchFailure() 81 | { 82 | var request = new Request { Name = "Giacomo", EMail = "" }; 83 | var result = 84 | Validation.ValidateInput(request) 85 | .Either( 86 | (x, msgs) => { throw new Exception("wrong match case"); }, 87 | msgs => msgs.ElementAt(0)); 88 | 89 | "Email must not be blank".Should().Be(result); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/SharpX.Tests/paket.references: -------------------------------------------------------------------------------- 1 | group tests 2 | FluentAssertions 3 | FsCheck 4 | FsCheck.Xunit 5 | Microsoft.NET.Test.Sdk 6 | xunit 7 | xunit.runner.visualstudio 8 | coverlet.collector 9 | Microsoft.NETCore.Platforms 10 | Moq 11 | --------------------------------------------------------------------------------