├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .idea └── .idea.Typely │ └── .idea │ ├── .gitignore │ ├── encodings.xml │ ├── indexLayout.xml │ └── vcs.xml ├── Directory.Build.props ├── HowToPublishNugets.md ├── LICENSE ├── README.md ├── Typely.sln ├── assets └── logo-300.png ├── benchmarks └── Typely.Benchmarks │ ├── ClassVsStructBenchmark.cs │ ├── EqualsBenchmark.cs │ ├── ErrorBenchmark.cs │ ├── Program.cs │ ├── README.md │ ├── RandomString.cs │ ├── ReadonlyStructBenchmark.cs │ ├── Typely.Benchmarks.csproj │ └── ValueObjectLibrariesBenchmark.cs ├── sample ├── README.md ├── Sample.Api │ ├── ContactsApi.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Sample.Api.csproj │ ├── TestsController.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Sample.Domain │ ├── .idea │ │ └── .idea.Sample.dir │ │ │ └── .idea │ │ │ └── encodings.xml │ ├── ContactAggregate │ │ ├── Address.cs │ │ ├── Contact.cs │ │ ├── PhoneType.cs │ │ └── TypelySpecification.cs │ └── Sample.Domain.csproj └── Sample.Infrastructure │ ├── AppDbContext.cs │ ├── ContactConfiguration.cs │ ├── Migrations │ ├── 20230423224612_InitialCreate.Designer.cs │ ├── 20230423224612_InitialCreate.cs │ ├── 20230428170636_MaxLength.Designer.cs │ ├── 20230428170636_MaxLength.cs │ ├── 20230430140013_TableAddress.Designer.cs │ ├── 20230430140013_TableAddress.cs │ ├── 20240924122024_InitialData.Designer.cs │ ├── 20240924122024_InitialData.cs │ └── AppDbContextModelSnapshot.cs │ └── Sample.Infrastructure.csproj ├── src ├── .vscode │ ├── launch.json │ └── tasks.json ├── Typely.AspNetCore.Swashbuckle │ ├── SwaggerGenOptionsExtensions.cs │ ├── Typely.AspNetCore.Swashbuckle.csproj │ ├── TypelyValueSchemaFilter.cs │ └── nuget-readme.md ├── Typely.AspNetCore │ ├── Http │ │ ├── ApplicationBuilderExtensions.cs │ │ ├── HttpValidationTemplatedProblemDetails.cs │ │ ├── TemplatedProblemDetail.cs │ │ └── TypelyValidationResultMiddleware.cs │ ├── Mvc │ │ ├── ModelBinding │ │ │ ├── TypelyValueModelBinder.cs │ │ │ └── TypelyValueModelBinderProvider.cs │ │ └── MvcOptionsExtensions.cs │ ├── Typely.AspNetCore.csproj │ └── nuget-readme.md ├── Typely.Core │ ├── Builders │ │ ├── IComparableRuleBuilder.cs │ │ ├── ITypelyBuilder.cs │ │ ├── ITypelyBuilderOfBool.cs │ │ ├── ITypelyBuilderOfByte.cs │ │ ├── ITypelyBuilderOfChar.cs │ │ ├── ITypelyBuilderOfDateOnly.cs │ │ ├── ITypelyBuilderOfDateTime.cs │ │ ├── ITypelyBuilderOfDateTimeOffset.cs │ │ ├── ITypelyBuilderOfDecimal.cs │ │ ├── ITypelyBuilderOfDouble.cs │ │ ├── ITypelyBuilderOfFloat.cs │ │ ├── ITypelyBuilderOfGuid.cs │ │ ├── ITypelyBuilderOfInt.cs │ │ ├── ITypelyBuilderOfLong.cs │ │ ├── ITypelyBuilderOfSByte.cs │ │ ├── ITypelyBuilderOfShort.cs │ │ ├── ITypelyBuilderOfString.cs │ │ ├── ITypelyBuilderOfTimeOnly.cs │ │ ├── ITypelyBuilderOfTimeSpan.cs │ │ ├── ITypelyBuilderOfUInt.cs │ │ ├── ITypelyBuilderOfULong.cs │ │ └── ITypelyBuilderOfUShort.cs │ ├── Converters │ │ ├── TypelyJsonConverter.cs │ │ └── TypelyTypeConverter.cs │ ├── ErrorMessages.Designer.cs │ ├── ErrorMessages.resx │ ├── Extensions │ │ └── TypeExtensions.cs │ ├── IMaxLength.cs │ ├── ITypelySpecification.cs │ ├── ITypelyValue.cs │ ├── Typely.Core.csproj │ ├── TypelyOptions.cs │ ├── TypelyValue.cs │ ├── ValidationError.cs │ ├── ValidationErrorFactory.cs │ ├── ValidationException.cs │ ├── ValidationPlaceholders.cs │ └── nuget-readme.md ├── Typely.EfCore │ ├── Conventions │ │ ├── ConventionModelBuilderExtensions.cs │ │ ├── ConventionSetBuilderExtensions.cs │ │ ├── TypelyConversionConvention.cs │ │ └── TypelyMaxLengthConvention.cs │ ├── Typely.EfCore.csproj │ ├── TypelyValueConverter.cs │ └── nuget-readme.md └── Typely.Generators │ ├── Analysers │ └── TypelySpecificationAnalyser.cs │ ├── Comparers │ ├── DictionaryComparer.cs │ └── ImmutableArrayComparer.cs │ ├── Extensions │ └── ClassDeclarationSyntaxExtensions.cs │ ├── Infrastructure │ └── SymbolExtensions.cs │ ├── Typely.Generators.csproj │ ├── Typely │ ├── DiagnosticDescriptors.cs │ ├── Emitting │ │ ├── Emitter.Class.cs │ │ ├── Emitter.Struct.cs │ │ └── Emitter.cs │ ├── ErrorCodes.cs │ ├── ErrorMessageResource.cs │ ├── Parsing │ │ ├── ConstructTypeKind.cs │ │ ├── EmittableRule.cs │ │ ├── EmittableRuleBuilder.cs │ │ ├── EmittableType.cs │ │ ├── EmittableTypeBuilder.cs │ │ ├── EmittableTypeOrDiagnostic.cs │ │ ├── NamespaceResolver.cs │ │ ├── ParsedInvocation.cs │ │ ├── ParsedInvocationExtensions.cs │ │ ├── ParsedStatement.cs │ │ ├── Parser.cs │ │ ├── TypeBuilders │ │ │ ├── EmittableTypeBuilderBase.cs │ │ │ ├── EmittableTypeBuilderFactory.cs │ │ │ ├── EmittableTypeBuilderOfBool.cs │ │ │ ├── EmittableTypeBuilderOfByte.cs │ │ │ ├── EmittableTypeBuilderOfChar.cs │ │ │ ├── EmittableTypeBuilderOfDateOnly.cs │ │ │ ├── EmittableTypeBuilderOfDateTime.cs │ │ │ ├── EmittableTypeBuilderOfDateTimeOffset.cs │ │ │ ├── EmittableTypeBuilderOfDecimal.cs │ │ │ ├── EmittableTypeBuilderOfDouble.cs │ │ │ ├── EmittableTypeBuilderOfFloat.cs │ │ │ ├── EmittableTypeBuilderOfGuid.cs │ │ │ ├── EmittableTypeBuilderOfInt.cs │ │ │ ├── EmittableTypeBuilderOfLong.cs │ │ │ ├── EmittableTypeBuilderOfSByte.cs │ │ │ ├── EmittableTypeBuilderOfShort.cs │ │ │ ├── EmittableTypeBuilderOfString.cs │ │ │ ├── EmittableTypeBuilderOfTimeOnly.cs │ │ │ ├── EmittableTypeBuilderOfTimeSpan.cs │ │ │ ├── EmittableTypeBuilderOfULong.cs │ │ │ ├── EmittableTypeBuilderOfUShort.cs │ │ │ ├── EmittableTypeBuilderOfUint.cs │ │ │ └── IEmittableTypeBuilder.cs │ │ └── TypeProperties.cs │ ├── SupportedMembers.cs │ ├── SymbolNames.cs │ ├── TypelyBuilder.cs │ ├── TypelyBuilderOf.cs │ ├── TypelyGenerator.cs │ └── ValidationPlaceholder.cs │ └── nuget-readme.md └── tests ├── Typely.AspNetCore.Swashbuckle.Tests ├── BaseFixture.cs ├── SchemaFilterContextFixture.cs ├── SwaggerGenOptionsExtensionsTests.cs ├── Typely.AspNetCore.Swashbuckle.Tests.csproj ├── TypelySpecification.cs ├── TypelyValueSchemaFilterTests.cs └── Usings.cs ├── Typely.AspNetCore.Tests ├── Http │ ├── TestServerFixture.cs │ ├── TypelyValidationResultMiddlewareTests.Middleware_ShouldReturn_ErrorResponse.verified.txt │ └── TypelyValidationResultMiddlewareTests.cs ├── Mvc │ ├── ModelBinding │ │ ├── ModelBinderFixture.cs │ │ ├── ModelBinderProviderContextFixture.cs │ │ ├── ModelBindingContextFixture.cs │ │ ├── TestModelBinderProviderContext.cs │ │ ├── TypelySpecification.cs │ │ ├── TypelyValueModelBinderProviderTests.cs │ │ └── TypelyValueModelBinderTests.cs │ └── MvcOptionsTests.cs ├── Typely.AspNetCore.Tests.csproj └── Usings.cs ├── Typely.EfCore.Tests ├── Common │ ├── MyDbContext.cs │ ├── Person.cs │ └── TypelySpecificationOfPerson.cs ├── Conventions │ └── ConventionTests.cs ├── Typely.EfCore.Tests.csproj ├── TypelyValueConverterTests.cs └── Usings.cs ├── Typely.Generators.Tests ├── Analysers │ ├── TypelySpecificationAnalyserFixture.cs │ └── TypelySpecificationAnalyserTests.cs ├── BaseFixture.cs ├── Comparers │ ├── DictionaryComparerTests.cs │ └── ImmutableArrayComparerTests.cs ├── CompilationFixture.cs ├── CompilationWithAnalysersFixture.cs ├── Extensions │ └── ClassDeclarationSyntaxExtensionsTests.cs ├── ModuleInitializer.cs ├── TestExtensions.cs ├── Typely.Generators.Tests.csproj ├── Typely │ ├── Parsing │ │ ├── EmittableRuleTests.cs │ │ ├── EmittableTypeBuilderFactoryTests.cs │ │ ├── EmittableTypeTests.cs │ │ ├── ParenthesizedDeclarationSpecification.cs │ │ ├── ParsedInvocationExtensionsTests.cs │ │ ├── ParserContext.cs │ │ ├── ParserContextFixture.cs │ │ ├── ParserTests.cs │ │ └── TypePropertiesTests.cs │ ├── Snapshots │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfBool_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfByte_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfChar_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfDateOnly_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfDateTimeOffset_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfDateTime_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfDecimal_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfDouble_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfFloat_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfGuid_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfInt_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfLong_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfSByte_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfShort_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfString_Should_EmitTypes#Typely.Generators.Tests.Typely.Specifications.Code.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfString_Should_EmitTypes#UserAggregate.UserId.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfTimeOnly_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfTimeSpan_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfUInt_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfULong_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.AllSupportedScenariosOfUShort_Should_EmitTypes#Election.Votes.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.MissingName_Should_OutputDiagnostic.verified.txt │ │ ├── TypelyGeneratorSnapshotTests.MissingTypeName_Should_Continue.verified.txt │ │ ├── TypelyGeneratorSnapshotTests.MultipleConfigurations_Should_EmitTypes#Typely.Generators.Tests.Typely.Specifications.A.MultiConfigDiffNamespace.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.MultipleConfigurations_Should_EmitTypes#Typely.Generators.Tests.Typely.Specifications.B.MultiConfigDiffNamespace.g.verified.cs │ │ ├── TypelyGeneratorSnapshotTests.UnsupportedSpecification_Should_OutputDiagnostics#Typely.Generators.Tests.Typely.Specifications.ShouldGenerate.g.verified.cs │ │ └── TypelyGeneratorSnapshotTests.UnsupportedSpecification_Should_OutputDiagnostics.verified.txt │ ├── Specifications │ │ ├── A │ │ │ ├── CustomLocalization.Designer.cs │ │ │ ├── CustomLocalization.resx │ │ │ └── MultipleSpecificationA.cs │ │ ├── B │ │ │ └── MultipleSpecificationB.cs │ │ ├── BoolSpecification.cs │ │ ├── ByteSpecification.cs │ │ ├── CharSpecification.cs │ │ ├── DateOnlySpecification.cs │ │ ├── DateTimeOffsetSpecification.cs │ │ ├── DateTimeSpecification.cs │ │ ├── DecimalSpecification.cs │ │ ├── Diagnostics │ │ │ ├── UnsupportedFieldsSpecification.cs │ │ │ ├── UnsupportedMethodsSpecification.cs │ │ │ ├── UnsupportedPropertiesSpecification.cs │ │ │ ├── UnsupportedTypesSpecification.cs │ │ │ └── UnsupportedVariablesSpecification.cs │ │ ├── DiagnosticsSpecification.cs │ │ ├── DoubleSpecification.cs │ │ ├── EmptySpecification.cs │ │ ├── FloatSpecification.cs │ │ ├── GuidSpecification.cs │ │ ├── IntSpecification.cs │ │ ├── LocalizedMessages.Designer.cs │ │ ├── LocalizedMessages.resx │ │ ├── LocalizedNames.Designer.cs │ │ ├── LocalizedNames.resx │ │ ├── LongSpecification.cs │ │ ├── NamespaceSpecification.cs │ │ ├── NoNamespaceSpecification.cs │ │ ├── SByteSpecification.cs │ │ ├── ShortSpecification.cs │ │ ├── StringSpecification.cs │ │ ├── TimeOnlySpecification.cs │ │ ├── TimeSpanSpecification.cs │ │ ├── UIntSpecification.cs │ │ ├── ULongSpecification.cs │ │ ├── UShortSpecification.cs │ │ └── WrappedNamespaceSpecification.cs │ ├── TypelyGeneratorDriver.cs │ ├── TypelyGeneratorDriverFixture.cs │ └── TypelyGeneratorSnapshotTests.cs └── Usings.cs ├── Typely.Isolated.Tests ├── Typely.Isolated.Tests.csproj ├── Usings.cs └── ValidationErrorFactory.Tests.cs └── Typely.Tests ├── Asserts.cs ├── CompleteTypelySpecification.cs ├── Converters ├── TypelyJsonConverterTests.cs ├── TypelySpecification.cs └── TypelyTypeConverterTests.cs ├── TypeGeneration ├── BoolType │ ├── BoolSpecification.cs │ └── BoolTests.cs ├── ByteType │ ├── ByteSpecification.cs │ └── ByteTests.cs ├── CharType │ ├── CharConfiguration.cs │ └── CharTests.cs ├── DateOnlyType │ ├── DateOnlyConfiguration.cs │ └── DateOnlyTests.cs ├── DateTimeOffsetType │ ├── DateTimeOffsetConfiguration.cs │ └── DateTimeOffsetTests.cs ├── DateTimeType │ ├── DateTimeConfiguration.cs │ └── DateTimeTests.cs ├── DecimalType │ ├── DecimalConfiguration.cs │ └── DecimalTests.cs ├── DoubleType │ ├── DoubleConfiguration.cs │ └── DoubleTests.cs ├── FloatType │ ├── FloatConfiguration.cs │ └── FloatTests.cs ├── GuidType │ ├── GuidConfiguration.cs │ └── GuidTests.cs ├── IntType │ ├── IntSpecification.cs │ └── IntTests.cs ├── LongType │ ├── LongConfiguration.cs │ └── LongTests.cs ├── SByteType │ ├── SByteConfiguration.cs │ └── SByteTests.cs ├── ShortType │ ├── ShortConfiguration.cs │ └── ShortTests.cs ├── StringType │ ├── StringSpecification.cs │ └── StringTests.cs ├── TimeOnlyType │ ├── TimeOnlyConfiguration.cs │ └── TimeOnlyTests.cs ├── TimeSpanType │ ├── TimeSpanConfiguration.cs │ └── TimeSpanTests.cs ├── UIntType │ ├── UIntConfiguration.cs │ └── UIntTests.cs ├── ULongType │ ├── ULongConfiguration.cs │ └── ULongTests.cs └── UShortType │ ├── UShortConfiguration.cs │ └── UShortTests.cs ├── Typely.Tests.csproj ├── TypelyOptionsTests.cs ├── Usings.cs ├── ValidationErrorTests.Validate_ShouldReturn_ValidationError.verified.txt └── ValidationErrorTests.cs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: pipeline 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | dotnet-version: ['8.0'] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup .NET Core SDK ${{ matrix.dotnet-version }} 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: ${{ matrix.dotnet-version }} 20 | 21 | - name: Restore dependencies 22 | run: dotnet restore ./Typely.sln 23 | 24 | - name: Build 25 | run: dotnet build --configuration Release ./Typely.sln 26 | 27 | - name: Test 28 | run: dotnet test --no-restore ./Typely.sln /p:CollectCoverage=true /p:CoverletOutput="../Coverage/" /p:MergeWith="../Coverage/coverage.json" /p:CoverletOutputFormat=\"cobertura,json\" /maxcpucount:1 29 | 30 | - name: Upload coverage reports to Codecov 31 | uses: codecov/codecov-action@v3 32 | 33 | - name: Create nuget packages 34 | run: | 35 | dotnet pack --configuration Release ./src/Typely.Core/Typely.Core.csproj 36 | dotnet pack --configuration Release ./src/Typely.Generators/Typely.Generators.csproj 37 | dotnet pack --configuration Release ./src/Typely.EfCore/Typely.EfCore.csproj 38 | dotnet pack --configuration Release ./src/Typely.AspNetCore/Typely.AspNetCore.csproj 39 | dotnet pack --configuration Release ./src/Typely.AspNetCore.Swashbuckle/Typely.AspNetCore.Swashbuckle.csproj 40 | 41 | - name: Publish packages 42 | run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate 43 | -------------------------------------------------------------------------------- /.idea/.idea.Typely/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /modules.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.Typely.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.Typely/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.Typely/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | . 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/.idea.Typely/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /HowToPublishNugets.md: -------------------------------------------------------------------------------- 1 | # How to publish packages 2 | 3 | 1. Increment `Typely.Core` 4 | 1. Commit 5 | 1. Wait for nuget to index the package 6 | 1. Increment `Typely.AspNetCore`, `Typely.AspNetCore.Swashbuckle` and `Typely.EfCore` 7 | 1. Update references to `Typely.Core` in csprojs. 8 | 1. Commit 9 | 1. Merge if all successful -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adam Paquette 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 | -------------------------------------------------------------------------------- /assets/logo-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typely-io/Typely/1ca6b01b5f8499eaa6faf2f196b5231cb2251473/assets/logo-300.png -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/ClassVsStructBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace Typely.Benchmarks; 4 | 5 | [MemoryDiagnoser] 6 | public class ClassVsStructBenchmark 7 | { 8 | private string _text = string.Empty; 9 | 10 | [Params(10, 100)] 11 | public int N; 12 | 13 | [GlobalSetup] 14 | public void Setup() 15 | { 16 | var randomString = new RandomString(); 17 | _text = randomString.Next(N); 18 | } 19 | 20 | [Benchmark] 21 | public void Struct() => new NameStruct(_text); 22 | 23 | [Benchmark] 24 | public void Class() => new NameClass(_text); 25 | } 26 | 27 | public class NameClass 28 | { 29 | public string Value { get; } 30 | 31 | public NameClass(string value) 32 | { 33 | Value = value; 34 | } 35 | } 36 | 37 | public struct NameStruct 38 | { 39 | public string Value { get; } 40 | 41 | public NameStruct(string value) 42 | { 43 | Value = value; 44 | } 45 | } -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/ErrorBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace Typely.Benchmarks; 4 | 5 | [MemoryDiagnoser] 6 | public class ErrorBenchmark 7 | { 8 | readonly string _val = new Random().Next().ToString(); 9 | 10 | [Benchmark] 11 | public void ReturnException() 12 | { 13 | try 14 | { 15 | throw new ArgumentOutOfRangeException("Param", _val, "Parameter must not be empty."); 16 | } 17 | catch (Exception) 18 | { 19 | } 20 | } 21 | 22 | [Benchmark] 23 | public void ReturnValidationError() => new ValidationErrorTest("ERR001", "Parameter must not be empty.", _val, "Param"); 24 | } 25 | 26 | public class ValidationErrorTest 27 | { 28 | public string ErrorCode { get; init; } 29 | public string ErrorMessage { get; init; } 30 | public object AttemptedValue { get; init; } 31 | public object Source { get; init; } 32 | public ValidationErrorTest(string errorCode, string errorMessage, object attemptedValue, object source) 33 | { 34 | ErrorCode = errorCode; 35 | ErrorMessage = errorMessage; 36 | AttemptedValue = attemptedValue; 37 | Source = source; 38 | } 39 | } -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using Typely.Benchmarks; 3 | 4 | BenchmarkRunner.Run(); 5 | //BenchmarkRunner.Run(); 6 | //BenchmarkRunner.Run(); 7 | //BenchmarkRunner.Run(); 8 | //BenchmarkRunner.Run(); 9 | //BenchmarkRunner.Run(); 10 | //BenchmarkRunner.Run(typeof(Program).Assembly); -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # ClassVsStructBenchmark 2 | 3 | | Method | N | Mean | Error | StdDev | Median | Gen0 | Allocated | 4 | |------- |---- |----------:|----------:|----------:|----------:|-------:|----------:| 5 | | Struct | 10 | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.0000 ns | - | - | 6 | | Class | 10 | 2.0950 ns | 0.0106 ns | 0.0088 ns | 2.0922 ns | 0.0015 | 24 B | 7 | | Struct | 100 | 0.0011 ns | 0.0011 ns | 0.0010 ns | 0.0008 ns | - | - | 8 | | Class | 100 | 2.1348 ns | 0.0534 ns | 0.0473 ns | 2.1205 ns | 0.0015 | 24 B | 9 | -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/RandomString.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Benchmarks; 2 | 3 | public class RandomString 4 | { 5 | private readonly Random _random = new(); 6 | 7 | public string Next(int length) 8 | { 9 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 10 | var stringChars = new char[length]; 11 | 12 | for (int i = 0; i < stringChars.Length; i++) 13 | { 14 | stringChars[i] = chars[_random.Next(chars.Length)]; 15 | } 16 | 17 | return new String(stringChars); 18 | } 19 | } -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/ReadonlyStructBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace Typely.Benchmarks; 4 | 5 | [MemoryDiagnoser] 6 | public class ReadonlyStructBenchmark 7 | { 8 | private struct NumberStruct(int value) 9 | { 10 | public int Value { get; } = value; 11 | 12 | public override string ToString() => Value.ToString(); 13 | } 14 | 15 | private readonly struct NumberReadonlyStruct(int value) 16 | { 17 | public int Value { get; } = value; 18 | 19 | public override string ToString() => Value.ToString(); 20 | } 21 | 22 | private readonly NumberStruct _numberStruct = new(1); 23 | private readonly NumberReadonlyStruct _numberReadonlyStruct = new(1); 24 | 25 | [Benchmark] 26 | public string Struct() 27 | { 28 | // Making a defensive copy of the value 29 | return _numberStruct.ToString(); 30 | } 31 | 32 | [Benchmark] 33 | public string ReadOnlyStruct() 34 | { 35 | return _numberReadonlyStruct.ToString(); 36 | } 37 | } -------------------------------------------------------------------------------- /benchmarks/Typely.Benchmarks/Typely.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | # Migrations 2 | 3 | cd sample 4 | dotnet ef migrations add InitialCreate --project Sample.Infrastructure --startup-project Sample.Api -------------------------------------------------------------------------------- /sample/Sample.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:36490", 8 | "sslPort": 44371 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5230", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7087;http://localhost:5230", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/Sample.Api/Sample.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | appsettings.json 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sample/Sample.Api/TestsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Sample.Domain.ContactAggregate; 3 | using Typely.Core; 4 | 5 | namespace Sample.Api; 6 | 7 | [Route("tests/[controller]")] 8 | [ApiController] 9 | public class TestsController : ControllerBase 10 | { 11 | [HttpGet("string")] 12 | public string Foo(FirstName firstName) => $"Value: {firstName}"; 13 | 14 | [HttpGet("int")] 15 | public string Foo(ContactId contactId) => $"Value: {contactId}"; 16 | 17 | [HttpPost("validation-problem")] 18 | public IActionResult ValidationProblem(Contact contact) 19 | { 20 | var error = new ValidationError("A", "AAA", null, "type", new Dictionary()); 21 | 22 | ModelState.AddModelError("FirstName", "Validation error message"); 23 | return ValidationProblem(); 24 | } 25 | } -------------------------------------------------------------------------------- /sample/Sample.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/Sample.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /sample/Sample.Domain/.idea/.idea.Sample.dir/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample/Sample.Domain/ContactAggregate/Address.cs: -------------------------------------------------------------------------------- 1 | namespace Sample.Domain.ContactAggregate; 2 | 3 | public class Address 4 | { 5 | public AddressId Id { get; set; } 6 | public required CivicNumber CivicNumber { get; set; } 7 | public required Street Street { get; set; } 8 | public required City City { get; set; } 9 | public required State State { get; set; } 10 | public required ZipCode ZipCode { get; set; } 11 | } -------------------------------------------------------------------------------- /sample/Sample.Domain/ContactAggregate/Contact.cs: -------------------------------------------------------------------------------- 1 | namespace Sample.Domain.ContactAggregate; 2 | 3 | public class Contact 4 | { 5 | public ContactId Id { get; set; } 6 | public required FirstName FirstName { get; set; } 7 | public required LastName LastName { get; set; } 8 | public Phone? Phone { get; set; } 9 | public PhoneType? PhoneType { get; set; } 10 | 11 | public required ICollection
Addresses { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /sample/Sample.Domain/ContactAggregate/PhoneType.cs: -------------------------------------------------------------------------------- 1 | namespace Sample.Domain.ContactAggregate; 2 | 3 | public enum PhoneType 4 | { 5 | Mobile = 1, 6 | Work = 2, 7 | Home = 3 8 | } -------------------------------------------------------------------------------- /sample/Sample.Domain/ContactAggregate/TypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.RegularExpressions; 3 | using Typely.Core; 4 | using Typely.Core.Builders; 5 | 6 | namespace Sample.Domain.ContactAggregate; 7 | 8 | internal class TypelySpecification : ITypelySpecification 9 | { 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | //Contact 13 | builder.OfInt().For("ContactId").GreaterThan(0); 14 | 15 | builder.OfString() 16 | .For("Phone") 17 | .MaxLength(12) 18 | .Matches(new Regex("[0-9]{3}-[0-9]{3}-[0-9]{4}")); 19 | 20 | builder.OfString() 21 | .For("ZipCode") 22 | .Matches(new Regex(@"^((\d{5}-\d{4})|(\d{5})|([A-Z|a-z]\d[A-Z|a-z]\d[A-Z|a-z]\d))$")) 23 | .Normalize(x => x.ToUpper()); 24 | 25 | var title = builder.OfString() 26 | .NotEmpty() 27 | .MaxLength(100) 28 | .Normalize(x => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(x)); 29 | 30 | title.For("FirstName"); 31 | title.For("LastName"); 32 | title.For("Street"); 33 | title.For("City"); 34 | title.For("State"); 35 | 36 | //Address 37 | builder.OfInt().For("AddressId").GreaterThan(0); 38 | builder.OfInt().For("CivicNumber").GreaterThan(0); 39 | } 40 | } -------------------------------------------------------------------------------- /sample/Sample.Domain/Sample.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/Sample.Infrastructure/AppDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Sample.Domain.ContactAggregate; 3 | using Typely.EfCore.Conventions; 4 | 5 | namespace Sample.Infrastructure; 6 | 7 | public class AppDbContext : DbContext 8 | { 9 | public AppDbContext(DbContextOptions options) : base(options) { } 10 | 11 | public DbSet Contacts => Set(); 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); 16 | } 17 | 18 | protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) 19 | { 20 | configurationBuilder.Conventions.AddTypelyConventions(); 21 | } 22 | } -------------------------------------------------------------------------------- /sample/Sample.Infrastructure/Migrations/20230423224612_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Sample.Infrastructure.Migrations 6 | { 7 | /// 8 | public partial class InitialCreate : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "Contacts", 15 | columns: table => new 16 | { 17 | Id = table.Column(type: "INTEGER", nullable: false), 18 | FirstName = table.Column(type: "TEXT", nullable: false), 19 | LastName = table.Column(type: "TEXT", nullable: false), 20 | Phone = table.Column(type: "TEXT", nullable: false), 21 | Street = table.Column(type: "TEXT", nullable: false), 22 | City = table.Column(type: "TEXT", nullable: false), 23 | State = table.Column(type: "TEXT", nullable: false), 24 | ZipCode = table.Column(type: "TEXT", nullable: false) 25 | }, 26 | constraints: table => 27 | { 28 | table.PrimaryKey("PK_Contacts", x => x.Id); 29 | }); 30 | } 31 | 32 | /// 33 | protected override void Down(MigrationBuilder migrationBuilder) 34 | { 35 | migrationBuilder.DropTable( 36 | name: "Contacts"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/Sample.Infrastructure/Migrations/20230428170636_MaxLength.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Sample.Infrastructure.Migrations 6 | { 7 | /// 8 | public partial class MaxLength : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | 14 | } 15 | 16 | /// 17 | protected override void Down(MigrationBuilder migrationBuilder) 18 | { 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/Sample.Infrastructure/Sample.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/Typely.Generators.Tests/bin/Debug/net7.0/Typely.Generators.Tests.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}/Typely.Generators.Tests", 15 | "console": "internalConsole", 16 | "stopAtEntry": false 17 | }, 18 | { 19 | "name": ".NET Core Attach", 20 | "type": "coreclr", 21 | "request": "attach" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /src/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Typely.Generators.Tests/Typely.Generators.Tests.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/Typely.Generators.Tests/Typely.Generators.Tests.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/Typely.Generators.Tests/Typely.Generators.Tests.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore.Swashbuckle/SwaggerGenOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace Typely.AspNetCore.Swashbuckle; 5 | 6 | /// 7 | /// SwaggerGen options extensions. 8 | /// 9 | public static class SwaggerGenOptionsExtensions 10 | { 11 | /// 12 | /// Adds the . 13 | /// 14 | /// The options. 15 | /// The options. 16 | public static SwaggerGenOptions UseTypelySchemaFilter(this SwaggerGenOptions options) 17 | { 18 | options.SchemaFilter(); 19 | return options; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Typely.AspNetCore.Swashbuckle/Typely.AspNetCore.Swashbuckle.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 8.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Typely.AspNetCore.Swashbuckle/TypelyValueSchemaFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using Typely.Core; 4 | 5 | namespace Typely.AspNetCore.Swashbuckle; 6 | 7 | public class TypelyValueSchemaFilter : ISchemaFilter 8 | { 9 | public void Apply(OpenApiSchema schema, SchemaFilterContext context) 10 | { 11 | var typelyValueType = GetTypelyValueTypeOrDefault(context.Type); 12 | if (typelyValueType is null) 13 | { 14 | return; 15 | } 16 | 17 | var valueType = typelyValueType.GenericTypeArguments[0]; 18 | if (!context.SchemaRepository.TryLookupByType(valueType, out var valueSchema)) 19 | { 20 | valueSchema = context.SchemaGenerator.GenerateSchema(valueType, context.SchemaRepository); 21 | } 22 | 23 | schema.Type = valueSchema.Type; 24 | schema.Format = valueSchema.Format; 25 | schema.Default = valueSchema.Default; 26 | schema.Example = valueSchema.Example; 27 | schema.Enum = valueSchema.Enum; 28 | schema.Minimum = valueSchema.Minimum; 29 | schema.Maximum = valueSchema.Maximum; 30 | schema.Pattern = valueSchema.Pattern; 31 | foreach (var (key, prop) in valueSchema.Properties) 32 | { 33 | schema.Properties.Add(key, prop); 34 | } 35 | } 36 | 37 | private static Type? GetTypelyValueTypeOrDefault(Type type) => 38 | type.GetInterfaces() 39 | .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypelyValue<,>)); 40 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore.Swashbuckle/nuget-readme.md: -------------------------------------------------------------------------------- 1 | Typely: Unleashing the power of value object creation with a fluent Api. 2 | 3 | This library lets you use Typely in ASP.NET Core by generating OpenAPI documentation with Swashbuckle. 4 | 5 | # Documentation 6 | 7 | - https://docs.typely.net/ 8 | 9 | # Usage 10 | ```csharp 11 | var builder = WebApplication.CreateBuilder(args); 12 | builder.Services.AddSwaggerGen(options => options.UseTypelySchemaFilter()); 13 | ``` -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Http/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Typely.Core; 4 | 5 | namespace Typely.AspNetCore.Http; 6 | 7 | /// 8 | /// Application builder extensions. 9 | /// 10 | public static class ApplicationBuilderExtensions 11 | { 12 | /// 13 | /// Adds a middleware to format Typely's as JSON compatible with . 14 | /// 15 | /// The instance this method extends. 16 | /// The . 17 | public static IApplicationBuilder UseTypelyValidationResult(this IApplicationBuilder app) => 18 | app.UseMiddleware(); 19 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Http/TemplatedProblemDetail.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.AspNetCore.Http; 2 | 3 | /// 4 | /// A problem detail with placeholders. 5 | /// 6 | public record TemplatedProblemDetail 7 | { 8 | /// 9 | /// A unique identifier for the error. 10 | /// 11 | /// The value is used for translations. 12 | public required string Code { get; init; } 13 | 14 | /// 15 | /// The error message localized with placeholders. 16 | /// 17 | public required string Message { get; init; } 18 | 19 | /// 20 | /// The value that caused the error. 21 | /// 22 | public string? AttemptedValue { get; init; } 23 | 24 | /// 25 | /// Type's name that generated the error. 26 | /// 27 | public required string TypeName { get; init; } 28 | 29 | /// 30 | /// List of placeholders with their values. 31 | /// 32 | public required Dictionary PlaceholderValues { get; init; } 33 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Http/TypelyValidationResultMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Net; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using Typely.Core; 6 | 7 | namespace Typely.AspNetCore.Http; 8 | 9 | /// 10 | /// Middleware that catches Typely's and returns an serialized. 11 | /// 12 | public class TypelyValidationResultMiddleware 13 | { 14 | private readonly RequestDelegate _next; 15 | private readonly JsonSerializerOptions _serializerOptions; 16 | 17 | public TypelyValidationResultMiddleware(RequestDelegate next) 18 | { 19 | _next = next; 20 | _serializerOptions = 21 | new JsonSerializerOptions(JsonSerializerDefaults.Web) 22 | { 23 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull 24 | }; 25 | } 26 | 27 | public async Task InvokeAsync(HttpContext context) 28 | { 29 | try 30 | { 31 | await _next(context); 32 | } 33 | catch (ValidationException ex) 34 | { 35 | await HandleExceptionAsync(context, ex); 36 | } 37 | } 38 | 39 | private Task HandleExceptionAsync(HttpContext context, ValidationException validationException) 40 | { 41 | context.Response.ContentType = "application/json"; 42 | context.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity; 43 | 44 | var problemDetails = HttpValidationTemplatedProblemDetails.From(validationException); 45 | var errorJson = JsonSerializer.Serialize(problemDetails, _serializerOptions); 46 | return context.Response.WriteAsync(errorJson); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Mvc/ModelBinding/TypelyValueModelBinderProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; 3 | using Typely.Core; 4 | 5 | namespace Typely.AspNetCore.Mvc.ModelBinding; 6 | 7 | /// 8 | /// Provides bindings for . 9 | /// 10 | public class TypelyValueModelBinderProvider : IModelBinderProvider 11 | { 12 | public IModelBinder? GetBinder(ModelBinderProviderContext context) 13 | { 14 | if (context == null) 15 | { 16 | throw new ArgumentNullException(nameof(context)); 17 | } 18 | 19 | var implementedInterface = context.Metadata.ModelType.GetInterface(typeof(ITypelyValue<,>).FullName!); 20 | if (implementedInterface == null) 21 | { 22 | return null; 23 | } 24 | 25 | var binderType = typeof(TypelyValueModelBinder<,>).MakeGenericType(implementedInterface.GenericTypeArguments); 26 | return new BinderTypeModelBinder(binderType); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Mvc/MvcOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Typely.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace Typely.AspNetCore.Mvc; 5 | 6 | /// 7 | /// MVC options extensions. 8 | /// 9 | public static class MvcOptionsExtensions 10 | { 11 | /// 12 | /// Support Typely validations through model binding. 13 | /// 14 | /// The options. 15 | /// The options. 16 | public static MvcOptions UseTypelyModelBinderProvider(this MvcOptions options) 17 | { 18 | options.ModelBinderProviders.Insert(0, new TypelyValueModelBinderProvider()); 19 | return options; 20 | } 21 | } -------------------------------------------------------------------------------- /src/Typely.AspNetCore/Typely.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 8.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Typely.AspNetCore/nuget-readme.md: -------------------------------------------------------------------------------- 1 | Typely: Unleashing the power of value object creation with a fluent Api. 2 | 3 | This library lets you use Typely in ASP.NET Core projects by providing validation exception handling and model binding. 4 | 5 | # Documentation 6 | 7 | - https://docs.typely.net/ 8 | 9 | # Usage 10 | 11 | This middleware will catch any `ValidationException` thrown by Typely and return a JSON response with the validation errors. 12 | ```csharp 13 | var builder = WebApplication.CreateBuilder(args); 14 | var app = builder.Build(); 15 | 16 | // Register this middleware before other middleware components 17 | app.UseTypelyValidationResult(); 18 | ``` 19 | Example of response: 20 | ```` 21 | { 22 | "templates": { 23 | "ZipCode": [ 24 | { 25 | "code": "Matches", 26 | "message": "'{Name}' is not in the correct format. Expected format '{RegularExpression}'.", 27 | "typeName": "ZipCode", 28 | "placeholderValues": { 29 | "RegularExpression": "^((\\d{5}-\\d{4})|(\\d{5})|([A-Z|a-z]\\d[A-Z|a-z]\\d[A-Z|a-z]\\d))$", 30 | "Name": "ZipCode", 31 | "ActualLength": "7" 32 | } 33 | } 34 | ] 35 | }, 36 | "errors": { 37 | "ZipCode": [ 38 | "'ZipCode' is not in the correct format. Expected format '^((\\d{5}-\\d{4})|(\\d{5})|([A-Z|a-z]\\d[A-Z|a-z]\\d[A-Z|a-z]\\d))$'." 39 | ] 40 | }, 41 | "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", 42 | "title": "One or more validation errors occurred.", 43 | "status": 400 44 | } 45 | ```` 46 | 47 | If you want to add validation errors into the model state of MVC during the binding phase of the request, configure the below option: 48 | ```csharp 49 | var builder = WebApplication.CreateBuilder(args); 50 | builder.Services.AddControllers(options => options.UseTypelyModelBinderProvider()); 51 | ``` -------------------------------------------------------------------------------- /src/Typely.Core/Builders/IComparableRuleBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder of types. 5 | /// 6 | /// Type of the inheriting rule builder. 7 | /// Underlying value's type. 8 | public interface IComparableRuleBuilder 9 | { 10 | /// 11 | /// Ensure the value object's value is less than the specified one. 12 | /// 13 | /// Value to be compared to. 14 | /// Fluent . 15 | TRuleBuilder LessThan(TValue value); 16 | 17 | /// 18 | /// Ensure the value object's value is less than or equal to the specified one. 19 | /// 20 | /// Value to be compared to. 21 | /// Fluent . 22 | TRuleBuilder LessThanOrEqualTo(TValue value); 23 | 24 | /// 25 | /// Ensure the value object's value is greater than the specified one. 26 | /// 27 | /// Value to be compared to. 28 | /// Fluent . 29 | TRuleBuilder GreaterThan(TValue value); 30 | 31 | /// 32 | /// Ensure the value object's value is greater than or equal to the specified one. 33 | /// 34 | /// Value to be compared to. 35 | /// Fluent . 36 | TRuleBuilder GreaterThanOrEqualTo(TValue value); 37 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfBool.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying bool type. 5 | /// 6 | public interface ITypelyBuilderOfBool : ITypelyBuilder 7 | { 8 | } 9 | 10 | /// 11 | /// Rule builder of bool. 12 | /// 13 | public interface IRuleBuilderOfBool : 14 | IRuleBuilder, 15 | ITypelyBuilderOfBool 16 | { 17 | } 18 | 19 | /// 20 | /// Factory for creating similar value objects of bool. 21 | /// 22 | public interface IFactoryOfBool : ITypelyBuilder 23 | { 24 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfByte.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying byte type. 5 | /// 6 | public interface ITypelyBuilderOfByte : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of byte. 13 | /// 14 | public interface IRuleBuilderOfByte : 15 | IRuleBuilder, 16 | ITypelyBuilderOfByte 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of byte. 22 | /// 23 | public interface IFactoryOfByte : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfChar.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying char type. 5 | /// 6 | public interface ITypelyBuilderOfChar : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of char. 13 | /// 14 | public interface IRuleBuilderOfChar : 15 | IRuleBuilder, 16 | ITypelyBuilderOfChar 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of char. 22 | /// 23 | public interface IFactoryOfChar : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfDateOnly.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | #if NET7_0_OR_GREATER 4 | /// 5 | /// Builder dedicated to creating a value object with the underlying DateOnly type. 6 | /// 7 | public interface ITypelyBuilderOfDateOnly : ITypelyBuilder, 8 | IComparableRuleBuilder 9 | { 10 | } 11 | 12 | /// 13 | /// Rule builder of DateOnly. 14 | /// 15 | public interface IRuleBuilderOfDateOnly : 16 | IRuleBuilder, 17 | ITypelyBuilderOfDateOnly 18 | { 19 | } 20 | 21 | /// 22 | /// Factory for creating similar value objects of DateOnly. 23 | /// 24 | public interface IFactoryOfDateOnly : ITypelyBuilder 25 | { 26 | } 27 | #endif -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfDateTime.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying DateTime type. 5 | /// 6 | public interface ITypelyBuilderOfDateTime : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of DateTime. 13 | /// 14 | public interface IRuleBuilderOfDateTime : 15 | IRuleBuilder, 16 | ITypelyBuilderOfDateTime 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of DateTime. 22 | /// 23 | public interface IFactoryOfDateTime : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfDateTimeOffset.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying DateTimeOffset type. 5 | /// 6 | public interface ITypelyBuilderOfDateTimeOffset : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of DateTimeOffset. 13 | /// 14 | public interface IRuleBuilderOfDateTimeOffset : 15 | IRuleBuilder, 16 | ITypelyBuilderOfDateTimeOffset 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of DateTimeOffset. 22 | /// 23 | public interface IFactoryOfDateTimeOffset : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfDecimal.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying decimal type. 5 | /// 6 | public interface ITypelyBuilderOfDecimal : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of decimal. 13 | /// 14 | public interface IRuleBuilderOfDecimal : 15 | IRuleBuilder, 16 | ITypelyBuilderOfDecimal 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of decimal. 22 | /// 23 | public interface IFactoryOfDecimal : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfDouble.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying double type. 5 | /// 6 | public interface ITypelyBuilderOfDouble : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of double. 13 | /// 14 | public interface IRuleBuilderOfDouble : 15 | IRuleBuilder, 16 | ITypelyBuilderOfDouble 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of double. 22 | /// 23 | public interface IFactoryOfDouble : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfFloat.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying float type. 5 | /// 6 | public interface ITypelyBuilderOfFloat : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of float. 13 | /// 14 | public interface IRuleBuilderOfFloat : 15 | IRuleBuilder, 16 | ITypelyBuilderOfFloat 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of float. 22 | /// 23 | public interface IFactoryOfFloat : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfGuid.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying Guid type. 5 | /// 6 | public interface ITypelyBuilderOfGuid : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of Guid. 13 | /// 14 | public interface IRuleBuilderOfGuid : 15 | IRuleBuilder, 16 | ITypelyBuilderOfGuid 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of Guid. 22 | /// 23 | public interface IFactoryOfGuid : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfInt.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying int type. 5 | /// 6 | public interface ITypelyBuilderOfInt : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of int. 13 | /// 14 | public interface IRuleBuilderOfInt : 15 | IRuleBuilder, 16 | ITypelyBuilderOfInt 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of int. 22 | /// 23 | public interface IFactoryOfInt : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfLong.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying long type. 5 | /// 6 | public interface ITypelyBuilderOfLong : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of long. 13 | /// 14 | public interface IRuleBuilderOfLong : 15 | IRuleBuilder, 16 | ITypelyBuilderOfLong 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of long. 22 | /// 23 | public interface IFactoryOfLong : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfSByte.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying sbyte type. 5 | /// 6 | public interface ITypelyBuilderOfSByte : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of sbyte. 13 | /// 14 | public interface IRuleBuilderOfSByte : 15 | IRuleBuilder, 16 | ITypelyBuilderOfSByte 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of sbyte. 22 | /// 23 | public interface IFactoryOfSByte : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfShort.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying short type. 5 | /// 6 | public interface ITypelyBuilderOfShort : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of short. 13 | /// 14 | public interface IRuleBuilderOfShort : 15 | IRuleBuilder, 16 | ITypelyBuilderOfShort 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of short. 22 | /// 23 | public interface IFactoryOfShort : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfString.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Typely.Core.Builders; 4 | 5 | /// 6 | /// Builder dedicated to creating a value object with the underlying string type. 7 | /// 8 | public interface ITypelyBuilderOfString : ITypelyBuilder, 9 | IComparableRuleBuilder 10 | { 11 | IRuleBuilderOfString Length(int min, int max); 12 | IRuleBuilderOfString Length(int exactLength); 13 | IRuleBuilderOfString MinLength(int minLength); 14 | IRuleBuilderOfString MaxLength(int maxLength); 15 | IRuleBuilderOfString Matches(Regex regex); 16 | } 17 | 18 | /// 19 | /// Rule builder of string. 20 | /// 21 | public interface IRuleBuilderOfString : 22 | IRuleBuilder, 23 | ITypelyBuilderOfString 24 | { 25 | } 26 | 27 | /// 28 | /// Factory for creating similar value objects of string. 29 | /// 30 | public interface IFactoryOfString : ITypelyBuilder 31 | { 32 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfTimeOnly.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | #if NET7_0_OR_GREATER 4 | /// 5 | /// Builder dedicated to creating a value object with the underlying TimeOnly type. 6 | /// 7 | public interface ITypelyBuilderOfTimeOnly : ITypelyBuilder, 8 | IComparableRuleBuilder 9 | { 10 | } 11 | 12 | /// 13 | /// Rule builder of TimeOnly. 14 | /// 15 | public interface IRuleBuilderOfTimeOnly : 16 | IRuleBuilder, 17 | ITypelyBuilderOfTimeOnly 18 | { 19 | } 20 | 21 | /// 22 | /// Factory for creating similar value objects of TimeOnly. 23 | /// 24 | public interface IFactoryOfTimeOnly : ITypelyBuilder 25 | { 26 | } 27 | #endif -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfTimeSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying TimeSpan type. 5 | /// 6 | public interface ITypelyBuilderOfTimeSpan : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of TimeSpan. 13 | /// 14 | public interface IRuleBuilderOfTimeSpan : 15 | IRuleBuilder, 16 | ITypelyBuilderOfTimeSpan 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of TimeSpan. 22 | /// 23 | public interface IFactoryOfTimeSpan : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfUInt.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying uint type. 5 | /// 6 | public interface ITypelyBuilderOfUInt : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of uint. 13 | /// 14 | public interface IRuleBuilderOfUInt : 15 | IRuleBuilder, 16 | ITypelyBuilderOfUInt 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of uint. 22 | /// 23 | public interface IFactoryOfUInt : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfULong.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying long type. 5 | /// 6 | public interface ITypelyBuilderOfULong : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of long. 13 | /// 14 | public interface IRuleBuilderOfULong : 15 | IRuleBuilder, 16 | ITypelyBuilderOfULong 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of long. 22 | /// 23 | public interface IFactoryOfULong : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Builders/ITypelyBuilderOfUShort.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Builders; 2 | 3 | /// 4 | /// Builder dedicated to creating a value object with the underlying short type. 5 | /// 6 | public interface ITypelyBuilderOfUShort : ITypelyBuilder, 7 | IComparableRuleBuilder 8 | { 9 | } 10 | 11 | /// 12 | /// Rule builder of short. 13 | /// 14 | public interface IRuleBuilderOfUShort : 15 | IRuleBuilder, 16 | ITypelyBuilderOfUShort 17 | { 18 | } 19 | 20 | /// 21 | /// Factory for creating similar value objects of short. 22 | /// 23 | public interface IFactoryOfUShort : ITypelyBuilder 24 | { 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/Converters/TypelyJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0_OR_GREATER 2 | 3 | using System.Text.Json.Serialization; 4 | using System.Text.Json; 5 | 6 | namespace Typely.Core.Converters; 7 | 8 | /// 9 | /// Json converter for typely types. 10 | /// 11 | /// The wrapped type. 12 | /// The value object type. 13 | public class TypelyJsonConverter : JsonConverter where TTypelyValue : ITypelyValue 14 | { 15 | /// 16 | public override TTypelyValue? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 17 | { 18 | var value = JsonSerializer.Deserialize(ref reader, options); 19 | 20 | return TTypelyValue.TryFrom(value!, out var typelyType, out var validationError) 21 | ? typelyType 22 | : throw new ValidationException(validationError!); 23 | } 24 | 25 | /// 26 | public override void Write(Utf8JsonWriter writer, TTypelyValue typelyType, JsonSerializerOptions options) => 27 | JsonSerializer.Serialize(writer, typelyType.Value, options); 28 | } 29 | #endif -------------------------------------------------------------------------------- /src/Typely.Core/Converters/TypelyTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Globalization; 3 | 4 | namespace Typely.Core.Converters; 5 | 6 | /// 7 | /// Provides a way of converting types to their underlying value type and vice versa. 8 | /// 9 | /// The underlying type of the value object. 10 | /// Value object's type. 11 | public class TypelyTypeConverter : TypeConverter 12 | where TTypelyValue : ITypelyValue 13 | { 14 | private readonly TypeConverter _underlyingValueConverter = TypeDescriptor.GetConverter(typeof(TValue)); 15 | 16 | /// 17 | public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => 18 | sourceType == typeof(TValue) || _underlyingValueConverter.CanConvertFrom(context, sourceType); 19 | 20 | /// 21 | public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => 22 | destinationType == typeof(TValue) || _underlyingValueConverter.CanConvertTo(context, destinationType); 23 | 24 | /// 25 | public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) 26 | { 27 | if (value is not TValue underlyingValue) 28 | { 29 | underlyingValue = (TValue)_underlyingValueConverter.ConvertFrom(context, culture, value)!; 30 | } 31 | 32 | return TypelyValue.From(underlyingValue); 33 | } 34 | 35 | /// 36 | public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, 37 | Type destinationType) => 38 | value is not ITypelyValue typelyValue ? default(object?) : typelyValue.Value; 39 | } -------------------------------------------------------------------------------- /src/Typely.Core/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core.Extensions; 2 | 3 | /// 4 | /// Type extensions 5 | /// 6 | public static class TypeExtensions 7 | { 8 | /// 9 | /// Determines whether is implemented by the current . 10 | /// 11 | /// The type to be verified. 12 | /// when the type implements the interface. 13 | public static bool ImplementsITypelyValue(this Type type) 14 | { 15 | var underlyingType = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) 16 | ? Nullable.GetUnderlyingType(type)! 17 | : type; 18 | 19 | var typelyType = typeof(ITypelyValue<,>); 20 | return underlyingType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typelyType); 21 | } 22 | 23 | /// 24 | /// Return the underlying type when nullable or the current type. 25 | /// 26 | /// Type. 27 | /// The wrapped type 28 | public static Type GetTypeOrUnderlyingType(this Type type) 29 | { 30 | var nullable = type.IsGenericType && 31 | type.GetGenericTypeDefinition() == typeof(Nullable<>); 32 | return nullable ? Nullable.GetUnderlyingType(type)! : type; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Typely.Core/IMaxLength.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | public interface IMaxLength 4 | { 5 | static abstract int MaxLength { get; } 6 | } -------------------------------------------------------------------------------- /src/Typely.Core/ITypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core.Builders; 2 | 3 | namespace Typely.Core; 4 | 5 | /// 6 | /// Represent a specification of how to create value objects. 7 | /// 8 | /// 9 | /// The class will be parsed as text by the generator. You cannot create and use custom functions inside the class. 10 | /// 11 | public interface ITypelySpecification 12 | { 13 | /// 14 | /// Define how to create value objects. 15 | /// 16 | /// Builder used to create value objects. 17 | void Create(ITypelyBuilder builder); 18 | } -------------------------------------------------------------------------------- /src/Typely.Core/Typely.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | Typely: Unleashing the power of value object creation with a fluent Api. This package contains interfaces that a consumer must implement. 6 | 8.0.0 7 | 8 | 9 | 10 | 11 | True 12 | True 13 | ErrorMessages.resx 14 | 15 | 16 | 17 | 18 | 19 | PublicResXFileCodeGenerator 20 | ErrorMessages.Designer.cs 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Typely.Core/TypelyOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | /// 4 | /// Options to modify the Typely's value object at runtime. 5 | /// 6 | public class TypelyOptions 7 | { 8 | public static TypelyOptions Instance { get; } = new(); 9 | 10 | /// 11 | /// Add the actual value to the exceptions when thrown. 12 | /// 13 | /// Output the actual value when set to . 14 | /// Fluent 15 | public TypelyOptions EnableSensitiveDataLogging(bool enabled = true) 16 | { 17 | IsSensitiveDataLoggingEnabled = enabled; 18 | return this; 19 | } 20 | 21 | /// 22 | /// if the actual value being validated will be thrown in exceptions. 23 | /// 24 | public bool IsSensitiveDataLoggingEnabled { get; private set; } 25 | } -------------------------------------------------------------------------------- /src/Typely.Core/TypelyValue.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | /// 4 | /// Static methods for a type generated by Typely. 5 | /// 6 | public static class TypelyValue 7 | { 8 | /// 9 | /// Validates the value object and throw if in error. 10 | /// 11 | /// The underlying type of the value object. 12 | /// Value object's type. 13 | /// Value to validate. 14 | /// 15 | public static void ValidateAndThrow(TValue value) where TTypelyValue : ITypelyValue 16 | { 17 | #if NET7_0_OR_GREATER 18 | var validationError = TTypelyValue.Validate(value); 19 | if (validationError != null) 20 | { 21 | throw new ValidationException(validationError); 22 | } 23 | #else 24 | throw new NotImplementedException(); 25 | #endif 26 | } 27 | 28 | /// 29 | /// Creates a if the validation succeeds. 30 | /// 31 | /// The underlying value. 32 | /// The created . 33 | /// If validations failed. 34 | public static TTypelyValue From(TValue value) where TTypelyValue : ITypelyValue 35 | { 36 | #if NET7_0_OR_GREATER 37 | return TTypelyValue.From(value); 38 | #else 39 | throw new NotImplementedException(); 40 | #endif 41 | } 42 | } -------------------------------------------------------------------------------- /src/Typely.Core/ValidationErrorFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | /// 4 | /// Factory for ValidationError 5 | /// 6 | public static class ValidationErrorFactory 7 | { 8 | /// 9 | /// Creates a ValidationError. 10 | /// 11 | /// Type of the value. 12 | /// Validated value. 13 | /// Unique code for the error. 14 | /// Error message containing placeholders in braces. 15 | /// The name of the calss or struct to create. 16 | /// List of the placeholders with their values. 17 | /// A . 18 | public static ValidationError Create(TValue value, string errorCode, 19 | string errorMessageWithPlaceholders, string typeName, Dictionary? placeholderValues = null) 20 | { 21 | object? attemptedValue = null; 22 | 23 | if (placeholderValues == null) 24 | { 25 | placeholderValues = new Dictionary(); 26 | } 27 | 28 | placeholderValues.TryAdd(ValidationPlaceholders.Name, typeName); 29 | 30 | if (typeof(TValue) == typeof(string)) 31 | { 32 | var actualLength = (value as string)?.Length ?? 0; 33 | placeholderValues.Add(ValidationPlaceholders.ActualLength, actualLength.ToString()); 34 | } 35 | 36 | if (TypelyOptions.Instance.IsSensitiveDataLoggingEnabled) 37 | { 38 | placeholderValues.TryAdd(ValidationPlaceholders.Value, value); 39 | attemptedValue = value; 40 | } 41 | 42 | return new ValidationError(errorCode, errorMessageWithPlaceholders, attemptedValue, typeName, placeholderValues); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Typely.Core/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | /// 4 | /// Exception thrown when a validation fails. 5 | /// 6 | public class ValidationException : Exception 7 | { 8 | public ValidationError ValidationError { get; } 9 | 10 | public ValidationException(ValidationError validationError) : base(validationError.ErrorMessage) 11 | { 12 | ValidationError = validationError; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Typely.Core/ValidationPlaceholders.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Core; 2 | 3 | /// 4 | /// Keywords used in validation messages. 5 | /// 6 | public class ValidationPlaceholders 7 | { 8 | public const string Name = nameof(Name); 9 | public const string Value = nameof(Value); 10 | public const string ComparisonValue = nameof(ComparisonValue); 11 | public const string MinLength = nameof(MinLength); 12 | public const string MaxLength = nameof(MaxLength); 13 | public const string ExactLength = nameof(ExactLength); 14 | public const string ActualLength = nameof(ActualLength); 15 | public const string Min = nameof(Min); 16 | public const string Max = nameof(Max); 17 | } -------------------------------------------------------------------------------- /src/Typely.Core/nuget-readme.md: -------------------------------------------------------------------------------- 1 | Typely: Unleashing the power of value object creation with a fluent Api. 2 | 3 | ## Example 4 | 5 | ```csharp 6 | public class TypesSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("Votes"); 11 | builder.OfString().For("Code").Length(4).NotEqual("0000"); 12 | 13 | builder.OfString() 14 | .For("UserId") 15 | .WithNamespace("UserAggregate") 16 | .WithName("Owner identifier") 17 | .NotEmpty() 18 | .NotEqual("0").WithMessage("{Name} cannot be equal to {ComparisonValue}.").WithErrorCode("ERR001") 19 | .MaxLength(20); 20 | 21 | builder.OfString() 22 | .For("Monday") 23 | .AsClass() 24 | .WithName(() => LocalizedNames.Moment) 25 | .MinLength(1).WithMessage(() => LocalizedMessages.MinLengthCustom) 26 | .MaxLength(20).WithMessage(() => LocalizedMessages.MaxLengthCustom); 27 | } 28 | } 29 | ``` 30 | 31 | # Documentation 32 | 33 | - https://docs.typely.net/ 34 | 35 | # Prerequisites 36 | 37 | - Supported .NET versions 38 | - .NET 7.0 and greater 39 | 40 | # Getting started 41 | 42 | Install packages 43 | ``` 44 | dotnet add package Typely.Core 45 | dotnet add package Typely.Generators 46 | ``` 47 | 48 | Create a class inheriting from `ITypelySpecification` 49 | ```csharp 50 | public class TypesSpecification : ITypelySpecification 51 | { 52 | public void Create(ITypelyBuilder builder) 53 | { 54 | builder.OfString().For("FirstName").NotEmpty(); 55 | } 56 | } 57 | ``` 58 | 59 | Usage 60 | ```csharp 61 | var firstName = FirstName.From("Adam"); 62 | FirstName.From(""); //Throws ValidationException 63 | 64 | if(!FirstName.TryFrom("value", out FirstName instance, out ValidationError? validationError)) 65 | { 66 | // Handle error 67 | } 68 | ``` -------------------------------------------------------------------------------- /src/Typely.EfCore/Conventions/ConventionModelBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Typely.Core; 4 | using Typely.Core.Extensions; 5 | 6 | namespace Typely.EfCore.Conventions; 7 | 8 | internal static class ConventionModelBuilderExtensions 9 | { 10 | /// 11 | /// Gets all non-navigation properties implementing declared on the entity type. 12 | /// 13 | /// Convention model builder 14 | /// Declared non-navigation properties implementing . 15 | public static IEnumerable 16 | GetTypelyValueProperties(this IConventionModelBuilder modelBuilder) => 17 | modelBuilder.Metadata 18 | .GetEntityTypes() 19 | .SelectMany(x => x.GetDeclaredProperties()) 20 | .Where(x => x.ClrType.ImplementsITypelyValue()); 21 | } -------------------------------------------------------------------------------- /src/Typely.EfCore/Conventions/TypelyConversionConvention.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 2 | using Microsoft.EntityFrameworkCore.Metadata.Conventions; 3 | using Typely.Core; 4 | using Typely.Core.Extensions; 5 | 6 | namespace Typely.EfCore.Conventions; 7 | 8 | /// 9 | /// Apply the value converter as a convention 10 | /// to all properties when a model is being finalized. 11 | /// 12 | /// 13 | /// See Model building conventions for more information and examples. 14 | /// 15 | public class TypelyConversionConvention : IModelFinalizingConvention 16 | { 17 | /// 18 | /// Called when a model is being finalized. 19 | /// 20 | /// The builder for the model. 21 | /// Additional information associated with convention execution. 22 | public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, 23 | IConventionContext context) 24 | { 25 | foreach (var property in modelBuilder.GetTypelyValueProperties()) 26 | { 27 | var typelyValueType = property.ClrType.GetTypeOrUnderlyingType(); 28 | 29 | var valueType = typelyValueType 30 | .GetInterfaces() 31 | .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ITypelyValue<,>)) 32 | .GetGenericArguments()[0]; 33 | 34 | var converter = typeof(TypelyValueConverter<,>).MakeGenericType(valueType, typelyValueType); 35 | 36 | property.Builder.HasConverter(converter); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Typely.EfCore/Conventions/TypelyMaxLengthConvention.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 2 | using Microsoft.EntityFrameworkCore.Metadata.Conventions; 3 | using Typely.Core; 4 | using Typely.Core.Extensions; 5 | 6 | namespace Typely.EfCore.Conventions; 7 | 8 | /// 9 | /// Configures the maximum length of data that can be stored in all 10 | /// properties when a model is being finalized. 11 | /// 12 | /// 13 | /// See Model building conventions for more information and examples. 14 | /// 15 | public class TypelyMaxLengthConvention : IModelFinalizingConvention 16 | { 17 | /// 18 | /// Called when a model is being finalized. 19 | /// 20 | /// The builder for the model. 21 | /// Additional information associated with convention execution. 22 | public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, 23 | IConventionContext context) 24 | { 25 | var maxLengthType = typeof(IMaxLength); 26 | 27 | foreach (var property in modelBuilder.GetTypelyValueProperties()) 28 | { 29 | var typelyValueType = property.ClrType.GetTypeOrUnderlyingType(); 30 | 31 | if (maxLengthType.IsAssignableFrom(typelyValueType)) 32 | { 33 | var getMaxLengthMethod = typelyValueType.GetMethod("get_" + nameof(IMaxLength.MaxLength)); 34 | var maxLength = (int)getMaxLengthMethod!.Invoke(null, null)!; 35 | 36 | property.Builder.HasMaxLength(maxLength); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Typely.EfCore/Typely.EfCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Typely: Unleashing the power of value object creation with a fluent Api. This package contains integrations with EF Core. 6 | 8.0.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Typely.EfCore/TypelyValueConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 2 | using Typely.Core; 3 | 4 | namespace Typely.EfCore; 5 | 6 | /// 7 | /// Defines conversions from an to the underlying value's type, in the store. 8 | /// 9 | /// 10 | /// See EF Core value converters for more information and examples. 11 | /// 12 | public class TypelyValueConverter : ValueConverter 13 | where TTypelyValue : ITypelyValue 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public TypelyValueConverter() 19 | : base( 20 | v => v.Value, 21 | v => TypelyValue.From(v)) 22 | { 23 | } 24 | } -------------------------------------------------------------------------------- /src/Typely.EfCore/nuget-readme.md: -------------------------------------------------------------------------------- 1 | Typely: Unleashing the power of value object creation with a fluent Api. 2 | 3 | This library lets you use Typely with Entity Framework Core by providing a set of conventions. 4 | 5 | # Documentation 6 | 7 | - https://docs.typely.net/ 8 | 9 | # Usage 10 | 11 | ```csharp 12 | protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) 13 | { 14 | configurationBuilder.Conventions.AddTypelyConventions(); 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /src/Typely.Generators/Comparers/DictionaryComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Comparers; 2 | 3 | public class DictionaryComparer : IEqualityComparer> 4 | where TKey : notnull 5 | { 6 | public static readonly DictionaryComparer Default = new(); 7 | 8 | private readonly IEqualityComparer _valueComparer; 9 | 10 | private DictionaryComparer(IEqualityComparer? valueComparer = null) 11 | { 12 | _valueComparer = valueComparer ?? EqualityComparer.Default; 13 | } 14 | 15 | public bool Equals(IDictionary? dict1, IDictionary? dict2) 16 | { 17 | if (ReferenceEquals(dict1, dict2)) 18 | { 19 | return true; 20 | } 21 | 22 | if (dict1 is null || dict2 is null) 23 | { 24 | return false; 25 | } 26 | 27 | if (dict1.Count != dict2.Count) 28 | { 29 | return false; 30 | } 31 | 32 | foreach (var keyValuePair in dict1) 33 | { 34 | if (!dict2.TryGetValue(keyValuePair.Key, out var value)) 35 | { 36 | return false; 37 | } 38 | 39 | if (!_valueComparer.Equals(keyValuePair.Value, value)) 40 | { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | public int GetHashCode(IDictionary? obj) 49 | { 50 | if (obj == null) 51 | { 52 | return 0; 53 | } 54 | 55 | int hash = 1; 56 | int keyHash = 0; 57 | int valueHash = 0; 58 | 59 | foreach (var pair in obj) 60 | { 61 | keyHash += EqualityComparer.Default.GetHashCode(pair.Key); 62 | valueHash += _valueComparer.GetHashCode(pair.Value); 63 | } 64 | 65 | hash = hash * 31 + keyHash; 66 | hash = hash * 31 + valueHash; 67 | 68 | return hash; 69 | } 70 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Comparers/ImmutableArrayComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace Typely.Generators.Comparers; 4 | 5 | public class ImmutableArrayComparer : IEqualityComparer> 6 | { 7 | public static readonly ImmutableArrayComparer Default = new(); 8 | 9 | private readonly IEqualityComparer _valueComparer; 10 | 11 | private ImmutableArrayComparer(IEqualityComparer? valueComparer = null) 12 | { 13 | _valueComparer = valueComparer ?? EqualityComparer.Default; 14 | } 15 | 16 | public bool Equals(ImmutableArray arr1, ImmutableArray arr2) 17 | { 18 | if (arr1.Length != arr2.Length) 19 | { 20 | return false; 21 | } 22 | 23 | foreach (var value in arr1) 24 | { 25 | if (!arr2.Contains(value)) 26 | { 27 | return false; 28 | } 29 | } 30 | 31 | return true; 32 | } 33 | 34 | public int GetHashCode(ImmutableArray arr) 35 | { 36 | int hash = 1; 37 | 38 | foreach (var value in arr) 39 | { 40 | hash = hash * 31 + _valueComparer.GetHashCode(value); 41 | } 42 | 43 | return hash; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Extensions/ClassDeclarationSyntaxExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp.Syntax; 2 | 3 | namespace Typely.Generators.Extensions; 4 | 5 | /// 6 | /// Extensions over . 7 | /// 8 | internal static class ClassDeclarationSyntaxExtensions 9 | { 10 | /// 11 | /// Indicates whether or not the class has a specific interface. 12 | /// 13 | public static bool HasInterface(this ClassDeclarationSyntax source, string interfaceName) => 14 | source.BaseList != null && source.BaseList.Types.Any(x => x.ToString() == interfaceName); 15 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Infrastructure/SymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Infrastructure; 4 | 5 | public static class SymbolExtensions 6 | { 7 | public static bool Implements(this ITypeSymbol type, ITypeSymbol interfaceType) 8 | { 9 | foreach (var t in type.AllInterfaces) 10 | { 11 | if (SymbolEqualityComparer.Default.Equals(t, interfaceType)) 12 | { 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/ErrorCodes.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | /// 4 | /// Unique error codes. 5 | /// 6 | internal static class ErrorCodes 7 | { 8 | public const string NotEmpty = nameof(NotEmpty); 9 | public const string NotEqual = nameof(NotEqual); 10 | public const string Length = nameof(Length); 11 | public const string ExactLength = nameof(ExactLength); 12 | public const string MinLength = nameof(MinLength); 13 | public const string MaxLength = nameof(MaxLength); 14 | public const string LessThan = nameof(LessThan); 15 | public const string LessThanOrEqualTo = nameof(LessThanOrEqualTo); 16 | public const string GreaterThan = nameof(GreaterThan); 17 | public const string GreaterThanOrEqualTo = nameof(GreaterThanOrEqualTo); 18 | public const string InclusiveBetween = nameof(InclusiveBetween); 19 | public const string ExclusiveBetween = nameof(ExclusiveBetween); 20 | public const string Must = nameof(Must); 21 | public const string Matches = nameof(Matches); 22 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/ErrorMessageResource.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | public static class ErrorMessageResource 4 | { 5 | public const string NotEmpty = "ErrorMessages.NotEmpty"; 6 | public const string NotEqual = "ErrorMessages.NotEqual"; 7 | public const string Length = "ErrorMessages.Length"; 8 | public const string ExactLength = "ErrorMessages.ExactLength"; 9 | public const string MinLength = "ErrorMessages.MinLength"; 10 | public const string MaxLength = "ErrorMessages.MaxLength"; 11 | public const string GreaterThan = "ErrorMessages.GreaterThan"; 12 | public const string GreaterThanOrEqualTo = "ErrorMessages.GreaterThanOrEqualTo"; 13 | public const string LessThan = "ErrorMessages.LessThan"; 14 | public const string LessThanOrEqualTo = "ErrorMessages.LessThanOrEqualTo"; 15 | public const string Matches = "ErrorMessages.Matches"; 16 | public const string Must = "ErrorMessages.Must"; 17 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/ConstructTypeKind.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely.Parsing; 2 | 3 | /// 4 | /// Enumeration for possible kinds of type constructs. 5 | /// 6 | internal enum ConstructTypeKind 7 | { 8 | Struct, 9 | Class 10 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/EmittableTypeOrDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing; 4 | 5 | internal class EmittableTypeOrDiagnostic : IEquatable 6 | { 7 | public EmittableType? EmittableType { get; } 8 | public Diagnostic? Diagnostic { get; } 9 | 10 | public EmittableTypeOrDiagnostic(EmittableType emittableType) 11 | { 12 | EmittableType = emittableType; 13 | Diagnostic = null; 14 | } 15 | 16 | public EmittableTypeOrDiagnostic(Diagnostic diagnostic) 17 | { 18 | EmittableType = null; 19 | Diagnostic = diagnostic; 20 | } 21 | 22 | 23 | public bool Equals(EmittableTypeOrDiagnostic? other) 24 | { 25 | if (ReferenceEquals(null, other)) 26 | { 27 | return false; 28 | } 29 | 30 | if (ReferenceEquals(this, other)) 31 | { 32 | return true; 33 | } 34 | 35 | return Equals(EmittableType, other.EmittableType) && Equals(Diagnostic, other.Diagnostic); 36 | } 37 | 38 | public override bool Equals(object? obj) 39 | { 40 | return ReferenceEquals(this, obj) || obj is EmittableTypeOrDiagnostic other && Equals(other); 41 | } 42 | 43 | public override int GetHashCode() 44 | { 45 | unchecked 46 | { 47 | return ((EmittableType != null ? EmittableType.GetHashCode() : 0) * 397) ^ (Diagnostic != null ? Diagnostic.GetHashCode() : 0); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/NamespaceResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing; 4 | 5 | public static class NamespaceResolver 6 | { 7 | public static IEnumerable GetNamespacesFromLambda(SyntaxNode lambdaNode, SemanticModel semanticModel) 8 | { 9 | var namespaces = new HashSet(); 10 | var symbolInfo = semanticModel.GetSymbolInfo(lambdaNode); 11 | 12 | if (symbolInfo.Symbol != null) 13 | { 14 | var containingNamespace = symbolInfo.Symbol.ContainingNamespace; 15 | if (containingNamespace != null) 16 | { 17 | namespaces.Add(containingNamespace.ToDisplayString()); 18 | } 19 | } 20 | 21 | return namespaces; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/ParsedInvocation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp.Syntax; 2 | using System.Diagnostics; 3 | 4 | namespace Typely.Generators.Typely.Parsing; 5 | 6 | [DebuggerDisplay("{MemberName}({ArgumentListSyntax})")] 7 | internal class ParsedInvocation 8 | { 9 | public string MemberName { get; set; } 10 | public ArgumentListSyntax ArgumentListSyntax { get; set; } 11 | 12 | public ParsedInvocation(ArgumentListSyntax argumentListSyntax, string memberName) 13 | { 14 | ArgumentListSyntax = argumentListSyntax; 15 | MemberName = memberName; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/ParsedStatement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing; 4 | 5 | internal class ParsedStatement 6 | { 7 | public ParsedStatement(SemanticModel semanticModel) 8 | { 9 | SemanticModel = semanticModel; 10 | } 11 | 12 | public string Root { get; set; } = string.Empty; 13 | public SemanticModel SemanticModel { get; } 14 | 15 | public List Invocations { get; } = new(); 16 | 17 | public bool IsValid() => !string.IsNullOrWhiteSpace(Root); 18 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfBool.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfBool : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfBool(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("bool", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfByte.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfByte : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfByte(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("byte", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfChar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfChar : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfChar(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("char", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfDateOnly.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfDateOnly : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfDateOnly(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("DateOnly", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfDateTime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfDateTime : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfDateTime(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("DateTime", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfDateTimeOffset.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfDateTimeOffset : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfDateTimeOffset(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("DateTimeOffset", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfDecimal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfDecimal : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfDecimal(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("decimal", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfDouble.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfDouble : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfDouble(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("double", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfFloat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfFloat : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfFloat(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("float", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfGuid.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfGuid : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfGuid(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("Guid", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfInt.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfInt : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfInt(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("int", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfLong.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfLong : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfLong(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("long", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfSByte.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfSByte : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfSByte(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("sbyte", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfShort.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfShort : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfShort(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("short", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfTimeOnly.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfTimeOnly : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfTimeOnly(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("TimeOnly", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfTimeSpan.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfTimeSpan : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfTimeSpan(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("TimeSpan", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfULong.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfULong : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfULong(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("ulong", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfUShort.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfUShort : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfUShort(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("ushort", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/EmittableTypeBuilderOfUint.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 4 | 5 | internal class EmittableTypeBuilderOfUint : EmittableTypeBuilderBase, IEmittableTypeBuilder 6 | { 7 | public EmittableTypeBuilderOfUint(string defaultNamespace, IEnumerable invocations, SemanticModel model) 8 | : base(invocations, new EmittableTypeBuilder("uint", true, defaultNamespace), model) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeBuilders/IEmittableTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely.Parsing.TypeBuilders; 2 | 3 | internal interface IEmittableTypeBuilder 4 | { 5 | EmittableType? Build(); 6 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/Parsing/TypeProperties.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely.Parsing; 2 | 3 | public class TypeProperties : Dictionary 4 | { 5 | public const string MaxLength = nameof(MaxLength); 6 | 7 | public void SetMaxLength(int value) 8 | { 9 | if (!ContainsKey(MaxLength)) 10 | { 11 | Add(MaxLength, value); 12 | } 13 | else 14 | { 15 | if ((int)this[MaxLength] > value) 16 | { 17 | this[MaxLength] = value; 18 | } 19 | } 20 | } 21 | 22 | public bool ContainsMaxLength() => ContainsKey(MaxLength); 23 | 24 | public object GetMaxLength() => this[MaxLength]; 25 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/SupportedMembers.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | public class SupportedMembers 4 | { 5 | public static IEnumerable All = new[] 6 | { 7 | TypelyBuilder.OfBool, TypelyBuilder.OfByte, TypelyBuilder.OfChar, TypelyBuilder.OfDateTime, 8 | TypelyBuilder.OfDateTimeOffset, TypelyBuilder.OfDecimal, TypelyBuilder.OfDouble, TypelyBuilder.OfFloat, 9 | TypelyBuilder.OfGuid, TypelyBuilder.OfInt, TypelyBuilder.OfLong, TypelyBuilder.OfSByte, 10 | TypelyBuilder.OfShort, TypelyBuilder.OfString, TypelyBuilder.OfTimeSpan, TypelyBuilder.OfUInt, 11 | TypelyBuilder.OfULong, TypelyBuilder.OfUShort, TypelyBuilder.OfDateOnly, TypelyBuilder.OfTimeOnly, 12 | TypelyBuilderOf.ForMethodName, TypelyBuilderOf.AsClassMethodName, TypelyBuilderOf.AsStructMethodName, 13 | TypelyBuilderOf.WithNameMethodName, TypelyBuilderOf.WithNamespaceMethodName, 14 | TypelyBuilderOf.NormalizeMethodName, TypelyBuilderOf.AsFactoryMethodName, 15 | TypelyBuilderOf.NotEmptyMethodName, TypelyBuilderOf.NotEqualMethodName, TypelyBuilderOf.MustMethodName, 16 | TypelyBuilderOf.MinLengthMethodName, TypelyBuilderOf.MaxLengthMethodName, TypelyBuilderOf.MatchesMethodName, 17 | TypelyBuilderOf.LengthMethodName, TypelyBuilderOf.GreaterThanMethodName, 18 | TypelyBuilderOf.GreaterThanOrEqualToMethodName, TypelyBuilderOf.LessThanMethodName, 19 | TypelyBuilderOf.LessThanOrEqualToMethodName, TypelyBuilderOf.WithMessageMethodName, 20 | TypelyBuilderOf.WithErrorCodeMethodName, 21 | }; 22 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/TypelyBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | public static class TypelyBuilder 4 | { 5 | public const string OfBool = nameof(OfBool); 6 | public const string OfByte = nameof(OfByte); 7 | public const string OfChar = nameof(OfChar); 8 | public const string OfDateTime = nameof(OfDateTime); 9 | public const string OfDateTimeOffset = nameof(OfDateTimeOffset); 10 | public const string OfDecimal = nameof(OfDecimal); 11 | public const string OfDouble = nameof(OfDouble); 12 | public const string OfFloat = nameof(OfFloat); 13 | public const string OfGuid = nameof(OfGuid); 14 | public const string OfInt = nameof(OfInt); 15 | public const string OfLong = nameof(OfLong); 16 | public const string OfSByte = nameof(OfSByte); 17 | public const string OfShort = nameof(OfShort); 18 | public const string OfString = nameof(OfString); 19 | public const string OfTimeSpan = nameof(OfTimeSpan); 20 | public const string OfUInt = nameof(OfUInt); 21 | public const string OfULong = nameof(OfULong); 22 | public const string OfUShort = nameof(OfUShort); 23 | public const string OfDateOnly = nameof(OfDateOnly); 24 | public const string OfTimeOnly = nameof(OfTimeOnly); 25 | } -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/TypelyBuilderOf.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | public static class TypelyBuilderOf 4 | { 5 | public const string ForMethodName = "For"; 6 | public const string AsClassMethodName = "AsClass"; 7 | public const string AsStructMethodName = "AsStruct"; 8 | public const string WithNameMethodName = "WithName"; 9 | public const string WithNamespaceMethodName = "WithNamespace"; 10 | public const string NormalizeMethodName = "Normalize"; 11 | public const string AsFactoryMethodName = "AsFactory"; 12 | public const string NotEmptyMethodName = "NotEmpty"; 13 | public const string NotEqualMethodName = "NotEqual"; 14 | public const string MustMethodName = "Must"; 15 | public const string MinLengthMethodName = "MinLength"; 16 | public const string MaxLengthMethodName = "MaxLength"; 17 | public const string MatchesMethodName = "Matches"; 18 | public const string LengthMethodName = "Length"; 19 | public const string GreaterThanMethodName = "GreaterThan"; 20 | public const string GreaterThanOrEqualToMethodName = "GreaterThanOrEqualTo"; 21 | public const string LessThanMethodName = "LessThan"; 22 | public const string LessThanOrEqualToMethodName = "LessThanOrEqualTo"; 23 | public const string WithMessageMethodName = "WithMessage"; 24 | public const string WithErrorCodeMethodName = "WithErrorCode"; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/Typely.Generators/Typely/ValidationPlaceholder.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.Generators.Typely; 2 | 3 | public static class ValidationPlaceholder 4 | { 5 | public const string ComparisonValue = nameof(ComparisonValue); 6 | public const string MinLength = nameof(MinLength); 7 | public const string MaxLength = nameof(MaxLength); 8 | public const string ExactLength = nameof(ExactLength); 9 | public const string RegularExpression = nameof(RegularExpression); 10 | } -------------------------------------------------------------------------------- /src/Typely.Generators/nuget-readme.md: -------------------------------------------------------------------------------- 1 | Typely: Unleashing the power of value object creation with a fluent Api. 2 | 3 | ## Example 4 | 5 | ```csharp 6 | public class TypesSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("Votes"); 11 | builder.OfString().For("Code").Length(4).NotEqual("0000"); 12 | 13 | builder.OfString() 14 | .For("UserId") 15 | .WithNamespace("UserAggregate") 16 | .WithName("Owner identifier") 17 | .NotEmpty() 18 | .NotEqual("0").WithMessage("{Name} cannot be equal to {ComparisonValue}.").WithErrorCode("ERR001") 19 | .MaxLength(20); 20 | 21 | builder.OfString() 22 | .For("Monday") 23 | .AsClass() 24 | .WithName(() => LocalizedNames.Moment) 25 | .MinLength(1).WithMessage(() => LocalizedMessages.MinLengthCustom) 26 | .MaxLength(20).WithMessage(() => LocalizedMessages.MaxLengthCustom); 27 | } 28 | } 29 | ``` 30 | 31 | # Documentation 32 | 33 | - https://docs.typely.net/ 34 | 35 | # Prerequisites 36 | 37 | - Supported .NET versions 38 | - .NET 7.0 and greater 39 | 40 | # Getting started 41 | 42 | Install packages 43 | ``` 44 | dotnet add package Typely.Core 45 | dotnet add package Typely.Generators 46 | ``` 47 | 48 | Create a class inheriting from `ITypelySpecification` 49 | ```csharp 50 | public class TypesSpecification : ITypelySpecification 51 | { 52 | public void Create(ITypelyBuilder builder) 53 | { 54 | builder.OfString().For("FirstName").NotEmpty(); 55 | } 56 | } 57 | ``` 58 | 59 | Usage 60 | ```csharp 61 | var firstName = FirstName.From("Adam"); 62 | FirstName.From(""); //Throws ValidationException 63 | 64 | if(!FirstName.TryFrom("value", out FirstName instance, out ValidationError? validationError)) 65 | { 66 | // Handle error 67 | } 68 | ``` -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/BaseFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | 4 | namespace Typely.AspNetCore.Swashbuckle.Tests; 5 | 6 | public class BaseFixture 7 | { 8 | protected IFixture Fixture { get; } = new Fixture().Customize(new AutoMoqCustomization()); 9 | 10 | public T Create() => Fixture.Create(); 11 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/SchemaFilterContextFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Microsoft.OpenApi.Models; 3 | using Moq; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System.Reflection; 6 | 7 | namespace Typely.AspNetCore.Swashbuckle.Tests; 8 | 9 | public class SchemaFilterContextFixture : BaseFixture 10 | { 11 | public SchemaFilterContextFixture() 12 | { 13 | Fixture.Register(() => null); 14 | Fixture.Register(() => null); 15 | } 16 | 17 | public SchemaFilterContextFixture WithType() 18 | { 19 | Fixture.Register(() => typeof(T)); 20 | Fixture.Freeze>() 21 | .Setup(x => 22 | x.GenerateSchema(It.IsAny(), It.IsAny(), null, null, null)) 23 | .Returns(new OpenApiSchema 24 | { 25 | Type = "string", 26 | Properties = new Dictionary 27 | { 28 | ["property1"] = new() { Type = "string" } 29 | } 30 | }); 31 | return this; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/SwaggerGenOptionsExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.SwaggerGen; 2 | 3 | namespace Typely.AspNetCore.Swashbuckle.Tests; 4 | 5 | public class SwaggerGenOptionsExtensionsTests 6 | { 7 | [Fact] 8 | public void UseTypelyValueSchemaFilter_Should_AddTheFilter() 9 | { 10 | var options = new SwaggerGenOptions(); 11 | options.UseTypelySchemaFilter(); 12 | Assert.Single(options.SchemaFilterDescriptors); 13 | } 14 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/Typely.AspNetCore.Swashbuckle.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/TypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.AspNetCore.Swashbuckle.Tests; 5 | 6 | public class TypelySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfByte().For("ByteTest"); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/TypelyValueSchemaFilterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | 3 | namespace Typely.AspNetCore.Swashbuckle.Tests; 4 | 5 | public class TypelyValueSchemaFilterTests 6 | { 7 | private readonly TypelyValueSchemaFilter _filter = new(); 8 | 9 | [Fact] 10 | public void ApplyTheUnderlyingSchemaType_When_ImplementsITypelyValue() 11 | { 12 | var schema = new OpenApiSchema(); 13 | var context = new SchemaFilterContextFixture() 14 | .WithType() 15 | .Create(); 16 | 17 | _filter.Apply(schema, context); 18 | 19 | Assert.Equal("string", schema.Type); 20 | Assert.Single(schema.Properties.Values); 21 | } 22 | 23 | [Fact] 24 | public void SchemaIsNotTouched_When_TypeDoesNotImplementITypelyValue() 25 | { 26 | var schema = new OpenApiSchema(); 27 | var context = new SchemaFilterContextFixture() 28 | .WithType() 29 | .Create(); 30 | 31 | _filter.Apply(schema, context); 32 | 33 | Assert.Null(schema.Type); 34 | Assert.Empty(schema.Properties.Values); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Swashbuckle.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Http/TestServerFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.TestHost; 5 | using Typely.AspNetCore.Http; 6 | using Typely.Core; 7 | 8 | namespace Typely.AspNetCore.Tests.Http; 9 | 10 | public class TestServerFixture 11 | { 12 | private IWebHostBuilder _webHostBuilder = new WebHostBuilder(); 13 | 14 | public TestServerFixture WithTypelyMiddlewareAndValidationError() 15 | { 16 | _webHostBuilder = new WebHostBuilder() 17 | .Configure(app => 18 | { 19 | app.UseTypelyValidationResult(); 20 | app.Run(context => 21 | { 22 | throw new ValidationException( 23 | new ValidationError( 24 | "ErrorCode", 25 | "ErrorMessageWithPlaceholders", 26 | "AttemptedValue", 27 | "TypeName", 28 | new Dictionary { { "Placeholder", "PlaceholderValue" } })); 29 | }); 30 | }); 31 | 32 | return this; 33 | } 34 | 35 | public TestServerFixture WithTypelyMiddleware() 36 | { 37 | _webHostBuilder = new WebHostBuilder() 38 | .Configure(app => 39 | { 40 | app.UseTypelyValidationResult(); 41 | app.Run(async context => 42 | { 43 | await context.Response.WriteAsync("Hello World!"); 44 | }); 45 | }); 46 | 47 | return this; 48 | } 49 | 50 | public TestServer Create() => new(_webHostBuilder); 51 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Http/TypelyValidationResultMiddlewareTests.Middleware_ShouldReturn_ErrorResponse.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", 3 | "title": "One or more validation errors occurred.", 4 | "status": 422, 5 | "templates": { 6 | "TypeName": [ 7 | { 8 | "code": "ErrorCode", 9 | "message": "ErrorMessageWithPlaceholders", 10 | "attemptedValue": "AttemptedValue", 11 | "typeName": "TypeName", 12 | "placeholderValues": { 13 | "Placeholder": "PlaceholderValue" 14 | } 15 | } 16 | ] 17 | }, 18 | "errors": { 19 | "TypeName": [ 20 | "ErrorMessageWithPlaceholders" 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Http/TypelyValidationResultMiddlewareTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | 3 | namespace Typely.AspNetCore.Tests.Http; 4 | 5 | public class TypelyValidationResultMiddlewareTests 6 | { 7 | [Fact] 8 | public async Task Middleware_ShouldReturn_ErrorResponse() 9 | { 10 | var server = new TestServerFixture().WithTypelyMiddlewareAndValidationError().Create(); 11 | var response = await server.CreateClient().GetAsync("/"); 12 | var jsonContent = await response.Content.ReadAsStringAsync(); 13 | var formattedJson = JsonNode.Parse(jsonContent)!.ToString(); 14 | 15 | await Verify(formattedJson); 16 | } 17 | 18 | [Fact] 19 | public async Task Middleware_Should_PassThrough() 20 | { 21 | var server = new TestServerFixture().WithTypelyMiddleware().Create(); 22 | var response = await server.CreateClient().GetAsync("/"); 23 | Assert.True(response.IsSuccessStatusCode); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/ModelBinderFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using Typely.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 5 | 6 | public class ModelBinderFixture 7 | { 8 | private Type _modelType = typeof(int); 9 | 10 | public ModelBinderFixture WithModelType(Type modelType) 11 | { 12 | _modelType = modelType; 13 | return this; 14 | } 15 | 16 | public IModelBinder? Build() 17 | { 18 | var binderProviderContext = new ModelBinderProviderContextFixture() 19 | .WithModelType(_modelType) 20 | .Build(); 21 | 22 | return new TypelyValueModelBinderProvider().GetBinder(binderProviderContext); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/ModelBinderProviderContextFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; 3 | using Moq; 4 | 5 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 6 | 7 | public class ModelBinderProviderContextFixture 8 | { 9 | private Type? _modelType; 10 | 11 | public ModelBinderProviderContextFixture WithModelType(Type modelType) 12 | { 13 | _modelType = modelType; 14 | return this; 15 | } 16 | 17 | public TestModelBinderProviderContext Build() 18 | { 19 | var metadetailsProvider = new Mock(); 20 | var modelMetadataProvider = new Mock(); 21 | var modelMetadata = 22 | new DefaultModelMetadataProvider(metadetailsProvider.Object) 23 | .GetMetadataForType(_modelType!); 24 | return new TestModelBinderProviderContext(modelMetadata, modelMetadataProvider.Object); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/ModelBindingContextFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Primitives; 7 | using Moq; 8 | using System.Globalization; 9 | 10 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 11 | 12 | public class ModelBindingContextFixture 13 | { 14 | private Type? _modelType; 15 | private string? _value; 16 | 17 | public ModelBindingContextFixture WithModelType(Type modelType) 18 | { 19 | _modelType = modelType; 20 | return this; 21 | } 22 | 23 | public ModelBindingContextFixture WithValue(string? value) 24 | { 25 | _value = value; 26 | return this; 27 | } 28 | 29 | public ModelBindingContext Build() 30 | { 31 | var metadetailsProvider = new Mock(); 32 | var modelMetadataProvider = new DefaultModelMetadataProvider(metadetailsProvider.Object); 33 | 34 | return new DefaultModelBindingContext 35 | { 36 | ModelMetadata = modelMetadataProvider.GetMetadataForType(_modelType!), 37 | ModelName = "SampleTypelyValue", 38 | ModelState = new ModelStateDictionary(), 39 | ValueProvider = new CompositeValueProvider 40 | { 41 | new QueryStringValueProvider(BindingSource.Query, new QueryCollection( 42 | new Dictionary { { "SampleTypelyValue", new StringValues(_value) } }) 43 | , CultureInfo.InvariantCulture) 44 | }, 45 | ActionContext = new ActionContext 46 | { 47 | HttpContext = new DefaultHttpContext 48 | { 49 | RequestServices = new ServiceCollection().BuildServiceProvider() 50 | } 51 | } 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/TestModelBinderProviderContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | 3 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 4 | 5 | public class TestModelBinderProviderContext : ModelBinderProviderContext 6 | { 7 | public TestModelBinderProviderContext(ModelMetadata metadata, IModelMetadataProvider metadataProvider) 8 | { 9 | Metadata = metadata; 10 | MetadataProvider = metadataProvider; 11 | } 12 | 13 | public override BindingInfo BindingInfo { get; } = new(); 14 | public override ModelMetadata Metadata { get; } 15 | public override IModelMetadataProvider MetadataProvider { get; } 16 | 17 | public override IModelBinder CreateBinder(ModelMetadata metadata) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/TypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 5 | 6 | public class TypelySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfString().For("Code").Length(4); 11 | builder.OfInt().For("Id").GreaterThan(0); 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/ModelBinding/TypelyValueModelBinderProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.AspNetCore.Mvc.ModelBinding; 2 | using Typely.Core; 3 | 4 | namespace Typely.AspNetCore.Tests.Mvc.ModelBinding; 5 | 6 | public class TypelyValueModelBinderProviderTests 7 | { 8 | [Fact] 9 | public void GetBinder_ReturnsModelBinder_WhenModelTypeIsTypelyValue() 10 | { 11 | var binderProviderContext = new ModelBinderProviderContextFixture() 12 | .WithModelType(typeof(Code)) 13 | .Build(); 14 | 15 | var binder = new TypelyValueModelBinderProvider().GetBinder(binderProviderContext); 16 | 17 | Assert.NotNull(binder); 18 | } 19 | 20 | [Fact] 21 | public void GetBinder_ReturnsNull_WhenModelTypeIsNotTypelyValue() 22 | { 23 | var binderProviderContext = new ModelBinderProviderContextFixture() 24 | .WithModelType(typeof(string)) 25 | .Build(); 26 | 27 | var binder = new TypelyValueModelBinderProvider().GetBinder(binderProviderContext); 28 | 29 | Assert.Null(binder); 30 | } 31 | 32 | [Fact] 33 | public void GetBinder_ReturnsNull_WhenModelTypeIsNotTypelyValueGeneric() 34 | { 35 | var binderProviderContext = new ModelBinderProviderContextFixture() 36 | .WithModelType(typeof(ITypelyValue<,>)) 37 | .Build(); 38 | 39 | var binder = new TypelyValueModelBinderProvider().GetBinder(binderProviderContext); 40 | 41 | Assert.Null(binder); 42 | } 43 | 44 | [Fact] 45 | public void GetBinder_ThrowsArgumentNullException_WhenContextIsNull() 46 | { 47 | Assert.Throws(() => new TypelyValueModelBinderProvider().GetBinder(null!)); 48 | } 49 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Mvc/MvcOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Typely.AspNetCore.Mvc; 3 | using Typely.AspNetCore.Mvc.ModelBinding; 4 | 5 | namespace Typely.AspNetCore.Tests.Mvc; 6 | 7 | public class MvcOptionsTests 8 | { 9 | [Fact] 10 | public void UseTypelyModelBinderProvider_Should_InsertTypelyValueModelBinderProvider() 11 | { 12 | var options = new MvcOptions(); 13 | options.UseTypelyModelBinderProvider(); 14 | Assert.IsType(options.ModelBinderProviders[0]); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Typely.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/Typely.AspNetCore.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Common/MyDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Typely.EfCore.Conventions; 3 | 4 | namespace Typely.EfCore.Tests.Common; 5 | 6 | public class MyDbContext : DbContext 7 | { 8 | public DbSet Persons { get; set; } = null!; 9 | 10 | public MyDbContext(DbContextOptions options) : base(options) 11 | { 12 | } 13 | 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | var person = modelBuilder.Entity(); 17 | 18 | person.Property(x => x.Id); 19 | person.Property(x => x.Email); 20 | person.Property(x => x.FirstName); 21 | } 22 | 23 | protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) 24 | { 25 | configurationBuilder.Conventions.AddTypelyConventions(); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Common/Person.cs: -------------------------------------------------------------------------------- 1 | namespace Typely.EfCore.Tests.Common; 2 | 3 | public class Person 4 | { 5 | public PersonId Id { get; set; } 6 | public required FirstName FirstName { get; set; } 7 | public Email? Email { get; set; } 8 | } -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Common/TypelySpecificationOfPerson.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Typely.Core; 3 | using Typely.Core.Builders; 4 | 5 | namespace Typely.EfCore.Tests.Common; 6 | 7 | public class TypelySpecificationOfPerson : ITypelySpecification 8 | { 9 | public void Create(ITypelyBuilder builder) 10 | { 11 | builder.OfInt().For("PersonId").NotEmpty(); 12 | builder.OfString().For("FirstName").NotEmpty(); 13 | builder.OfString().For("Email").NotEmpty().MaxLength(100) 14 | .Matches(new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Conventions/ConventionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Typely.EfCore.Tests.Common; 4 | 5 | namespace Typely.EfCore.Tests.Conventions; 6 | 7 | public class ConventionTests 8 | { 9 | [Fact] 10 | public void SaveChanges_ShouldWork_WithConventions() 11 | { 12 | using var serviceProvider = CreateServiceProvider(); 13 | using var context = serviceProvider.GetRequiredService(); 14 | 15 | var person = new Person 16 | { 17 | Id = PersonId.From(1), 18 | Email = Email.From("a@a.com"), 19 | FirstName = FirstName.From("John") 20 | }; 21 | context.Persons.Add(person); 22 | context.SaveChanges(); 23 | 24 | Assert.Equal(1, context.Persons.Count()); 25 | } 26 | 27 | private static ServiceProvider CreateServiceProvider() => new ServiceCollection() 28 | .AddDbContext(options => options.UseInMemoryDatabase("MyDb")) 29 | .BuildServiceProvider(); 30 | } -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Typely.EfCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/TypelyValueConverterTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.EfCore.Tests; 5 | 6 | public class TypelySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfString().For("MyString"); 11 | } 12 | } 13 | 14 | public class TypelyValueConverterTests 15 | { 16 | private readonly TypelyValueConverter converter = new(); 17 | const string expectedString = "MyValue"; 18 | readonly MyString expected = MyString.From(expectedString); 19 | 20 | [Fact] 21 | public void ConvertFromProvider() => 22 | Assert.Equal(expected, converter.ConvertFromProvider(expectedString)); 23 | 24 | [Fact] 25 | public void ConvertToProvider() => 26 | Assert.Equal(expectedString, converter.ConvertToProvider(expected)); 27 | } -------------------------------------------------------------------------------- /tests/Typely.EfCore.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Analysers/TypelySpecificationAnalyserFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.Diagnostics; 3 | using System.Collections.Immutable; 4 | // ReSharper disable InconsistentNaming 5 | 6 | namespace Typely.Generators.Tests.Analysers; 7 | 8 | internal static class AnalyserRunner 9 | { 10 | private const string CS5001_ProgramDoesNotContainValidEntryPointId = "CS5001"; 11 | private const string CS0012_TypeIsDefinedInAnAssemblyThatIsNotReferenced = "CS0012"; 12 | 13 | private static readonly string[] DisabledDiagnostics = { 14 | CS5001_ProgramDoesNotContainValidEntryPointId, 15 | CS0012_TypeIsDefinedInAnAssemblyThatIsNotReferenced 16 | }; 17 | 18 | public static async Task> GetDiagnostics() 19 | where TAnalyser : DiagnosticAnalyzer, new() 20 | { 21 | var compilation = 22 | new CompilationWithAnalysersFixture() 23 | .WithSpecification() 24 | .WithAnalyser() 25 | .Create(); 26 | 27 | var diagnostics = await compilation.GetAllDiagnosticsAsync(); 28 | return diagnostics.Where(x => !DisabledDiagnostics.Contains(x.Id)).ToImmutableArray(); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/BaseFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | 4 | namespace Typely.Generators.Tests; 5 | 6 | internal class BaseFixture 7 | { 8 | protected IFixture Fixture { get; } 9 | 10 | public BaseFixture() 11 | { 12 | Fixture = new Fixture().Customize(new AutoMoqCustomization()); 13 | } 14 | 15 | public T Create() => Fixture.Create(); 16 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Comparers/ImmutableArrayComparerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Typely.Generators.Comparers; 3 | 4 | namespace Typely.Generators.Tests.Comparers; 5 | 6 | public class ImmutableArrayComparerTests 7 | { 8 | [Fact] 9 | public void TwoArray_WithTheSameValues_ShouldBe_Equal() 10 | { 11 | var array1 = new[] { "Value1", "Value2" }.ToImmutableArray(); 12 | var array2 = new[] { "Value1", "Value2" }.ToImmutableArray(); 13 | 14 | Assert.True(ImmutableArrayComparer.Default.Equals(array1, array2)); 15 | Assert.Equal(ImmutableArrayComparer.Default.GetHashCode(array1), ImmutableArrayComparer.Default.GetHashCode(array2)); 16 | } 17 | 18 | [Fact] 19 | public void TwoArray_WithDiffLength_ShouldNotBe_Equal() 20 | { 21 | var array1 = new[] { "Value1", "Value2" }.ToImmutableArray(); 22 | var array2 = new[] { "Value1" }.ToImmutableArray(); 23 | 24 | Assert.False(ImmutableArrayComparer.Default.Equals(array1, array2)); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/CompilationWithAnalysersFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Microsoft.CodeAnalysis.Diagnostics; 3 | using System.Collections.Immutable; 4 | 5 | namespace Typely.Generators.Tests; 6 | 7 | internal class CompilationWithAnalysersFixture : CompilationFixture 8 | { 9 | private ImmutableArray _diagnosticAnalyzers; 10 | 11 | public CompilationWithAnalysersFixture() 12 | { 13 | Fixture.Register(() => CreateCompilation().WithAnalyzers(_diagnosticAnalyzers)); 14 | } 15 | 16 | public CompilationWithAnalysersFixture WithAnalysers(ImmutableArray diagnosticAnalyzers) 17 | { 18 | _diagnosticAnalyzers = diagnosticAnalyzers; 19 | return this; 20 | } 21 | 22 | public CompilationWithAnalysersFixture WithAnalyser() where TAnalyser : DiagnosticAnalyzer, new() 23 | { 24 | _diagnosticAnalyzers = ImmutableArray.Create(new TAnalyser()); 25 | return this; 26 | } 27 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Extensions/ClassDeclarationSyntaxExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using Typely.Generators.Extensions; 4 | 5 | namespace Typely.Generators.Tests.Extensions; 6 | public class ClassDeclarationSyntaxExtensionsTests 7 | { 8 | [Fact] 9 | public void HasInterface_ShoudReturn_False_WhenNotBaseList() 10 | { 11 | var syntaxTree = CSharpSyntaxTree.ParseText("public class Test {}"); 12 | var classSyntax = syntaxTree.GetRoot().DescendantNodes().OfType().Single(); 13 | 14 | Assert.False(classSyntax.HasInterface("ABC")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Typely.Generators.Tests; 4 | 5 | public static class ModuleInitializer 6 | { 7 | [ModuleInitializer] 8 | public static void Init() 9 | { 10 | VerifySourceGenerators.Initialize(); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/TestExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Immutable; 3 | 4 | namespace Typely.Generators.Tests; 5 | 6 | public static class TestExtensions 7 | { 8 | internal static void ShouldContainExactlyNDiagnosticsWithId(this ImmutableArray diagnostics, int n, string diagnosticId) 9 | { 10 | Assert.Equal(n, diagnostics.Count(x => x.Id == diagnosticId)); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Parsing/EmittableRuleTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Typely.Generators.Typely.Parsing; 3 | 4 | namespace Typely.Generators.Tests.Typely.Parsing; 5 | 6 | public class EmittableRuleTests 7 | { 8 | [Fact] 9 | public void TwoRulesWithTheSameProperties_ShouldBe_Equal() 10 | { 11 | var rule1 = new EmittableRule("string", "Name", "Name", 12 | new Dictionary { { "MaxLength", "10" } }.ToImmutableDictionary()); 13 | 14 | var rule2 = new EmittableRule("string", "Name", "Name", 15 | new Dictionary { { "MaxLength", "10" } }.ToImmutableDictionary()); 16 | 17 | Assert.True(rule1.Equals(rule2)); 18 | Assert.Equal(rule1.GetHashCode(), rule2.GetHashCode()); 19 | } 20 | 21 | [Fact] 22 | public void TwoRulesWithOneDiffObject_ShouldNotBe_Equal() 23 | { 24 | var rule1 = new EmittableRule("string", "Name", "Name", 25 | new Dictionary { { "MaxLength", "10" } }.ToImmutableDictionary()); 26 | 27 | // ReSharper disable once SuspiciousTypeConversion.Global 28 | Assert.False(rule1.Equals("value")); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Parsing/EmittableTypeBuilderFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Generators.Typely.Parsing; 2 | using Typely.Generators.Typely.Parsing.TypeBuilders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Parsing; 5 | 6 | public class EmittableTypeBuilderFactoryTests 7 | { 8 | [Fact] 9 | public void UnsupportedType_Should_Throw() 10 | { 11 | var statement = new ParsedStatement(null!) { Root = "builder", }; 12 | statement.Invocations.Add(new ParsedInvocation(null!, "OfUnsupportedType")); 13 | 14 | Assert.Throws(() => EmittableTypeBuilderFactory.Create("namespace", statement)); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Parsing/ParenthesizedDeclarationSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Parsing; 5 | 6 | public class ParenthesizedDeclarationSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var (a, b) = (1, 2); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Parsing/ParserContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | 4 | namespace Typely.Generators.Tests.Typely.Parsing; 5 | 6 | public record ParserContext(ClassDeclarationSyntax ClassDeclarationSyntax, SemanticModel SemanticModel); -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Parsing/TypePropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Generators.Comparers; 2 | using Typely.Generators.Typely.Parsing; 3 | 4 | namespace Typely.Generators.Tests.Typely.Parsing; 5 | 6 | public class TypePropertiesTests 7 | { 8 | [Fact] 9 | public void TwoTypePropertiesWithSameMaxLengthAreEqual() 10 | { 11 | var typeProperties1 = new TypeProperties(); 12 | typeProperties1.SetMaxLength(10); 13 | 14 | var typeProperties2 = new TypeProperties(); 15 | typeProperties2.SetMaxLength(10); 16 | 17 | Assert.True(DictionaryComparer.Default.Equals(typeProperties1, typeProperties2)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Snapshots/TypelyGeneratorSnapshotTests.MissingName_Should_OutputDiagnostic.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Diagnostics: [ 3 | { 4 | Id: TYPLY0002, 5 | Title: Name missing., 6 | Severity: Error, 7 | WarningLevel: 0, 8 | Location: : (0,0)-(0,0), 9 | Description: , 10 | HelpLink: , 11 | MessageFormat: A type is declared without a user friendly name in the namespace '{0}'., 12 | Message: A type is declared without a user friendly name in the namespace 'Typely.Generators.Tests.Typely.Configurations'., 13 | Category: TypelyGenerator 14 | }, 15 | { 16 | Id: TYPLY0001, 17 | Title: Type name missing., 18 | Severity: Error, 19 | WarningLevel: 0, 20 | Location: : (0,0)-(0,0), 21 | Description: , 22 | HelpLink: , 23 | MessageFormat: A type is declared without a name in the namespace '{0}'., 24 | Message: A type is declared without a name in the namespace 'Typely.Generators.Tests.Typely.Configurations'., 25 | Category: TypelyGenerator 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Snapshots/TypelyGeneratorSnapshotTests.MissingTypeName_Should_Continue.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Diagnostics: [ 3 | { 4 | Id: TYPLY0002, 5 | Title: Name missing., 6 | Severity: Error, 7 | WarningLevel: 0, 8 | Location: : (0,0)-(0,0), 9 | Description: , 10 | HelpLink: , 11 | MessageFormat: A type is declared without a user friendly name in the namespace '{0}'., 12 | Message: A type is declared without a user friendly name in the namespace 'Typely.Generators.Tests.Typely.Configurations'., 13 | Category: TypelyGenerator 14 | }, 15 | { 16 | Id: TYPLY0001, 17 | Title: Type name missing., 18 | Severity: Error, 19 | WarningLevel: 0, 20 | Location: : (0,0)-(0,0), 21 | Description: , 22 | HelpLink: , 23 | MessageFormat: A type is declared without a name in the namespace '{0}'., 24 | Message: A type is declared without a name in the namespace 'Typely.Generators.Tests.Typely.Configurations'., 25 | Category: TypelyGenerator 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/A/CustomLocalization.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | text/microsoft-resx 11 | 12 | 13 | 1.3 14 | 15 | 16 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17 | 18 | 19 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 20 | 21 | 22 | Value 23 | 24 | -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/A/MultipleSpecificationA.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.A; 5 | 6 | internal class MultipleSpecificationA : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("MultiConfigDiffNamespace"); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/B/MultipleSpecificationB.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.B; 5 | 6 | internal class MultipleSpecificationB : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("MultiConfigDiffNamespace"); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/BoolSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class BoolSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfBool() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(false); 20 | 21 | vote.Must(x => x == true).WithMessage(() => LocalizedMessages.CustomMessage) 22 | .Must((x) => !x.Equals(10)).WithMessage(() => A.CustomLocalization.Value); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/ByteSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class ByteSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfByte() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/CharSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class CharSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfChar() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual('a'); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan('a').WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo('f').WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan('b') 26 | .LessThanOrEqualTo('c'); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DateOnlySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DateOnlySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfDateOnly() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(new DateOnly(2021, 1, 1)); 20 | 21 | vote.Must(x => x == new DateOnly(2022, 1, 1)) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(new DateOnly(2022, 1, 1)).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(new DateOnly(2022, 1, 1)).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(new DateOnly(2022, 1, 1)) 26 | .LessThanOrEqualTo(new DateOnly(2022, 1, 1)); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DateTimeOffsetSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DateTimeOffsetSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfDateTimeOffset() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(new DateTimeOffset(new DateTime(2022, 1, 1))); 20 | 21 | vote.Must(x => x == new DateTimeOffset(new DateTime(2022, 1, 1))) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(new DateTimeOffset(new DateTime(2022, 1, 1))) 24 | .WithMessage(() => LocalizedMessages.CustomMessage) 25 | .GreaterThanOrEqualTo(new DateTimeOffset(new DateTime(2022, 1, 1))) 26 | .WithMessage(() => A.CustomLocalization.Value) 27 | .LessThan(new DateTimeOffset(new DateTime(2022, 1, 1))) 28 | .LessThanOrEqualTo(new DateTimeOffset(new DateTime(2022, 1, 1))); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DateTimeSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DateTimeSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfDateTime() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(new DateTime(2021, 1, 1)); 20 | 21 | vote.Must(x => x == new DateTime(2022, 1, 1)) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(new DateTime(2022, 1, 1)).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(new DateTime(2022, 1, 1)).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(new DateTime(2022, 1, 1)) 26 | .LessThanOrEqualTo(new DateTime(2022, 1, 1)); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DecimalSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DecimalSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfDecimal() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(-1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/Diagnostics/UnsupportedFieldsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.Diagnostics; 5 | 6 | public class UnsupportedFieldsSpecification: ITypelySpecification 7 | { 8 | private const string Field1 = "Field1"; 9 | private string Field2 = "Field2"; 10 | 11 | public void Create(ITypelyBuilder builder) 12 | { 13 | builder.OfString().For(Field1); 14 | builder.OfString().For(Field2); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/Diagnostics/UnsupportedMethodsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.Diagnostics; 5 | 6 | public class UnsupportedMethodsSpecification: ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfString().For("Unsupported").UnsupportedExtension(); 11 | 12 | CreateTypeFromUnsupportedCall(builder); 13 | 14 | var unsupported = CreateTypeFromUnsupportedCall(builder); 15 | unsupported.NotEmpty(); 16 | } 17 | 18 | public IRuleBuilderOfInt CreateTypeFromUnsupportedCall(ITypelyBuilder builder) => 19 | builder.OfInt().For("AddressId").GreaterThan(0); 20 | } 21 | 22 | internal static class TypelyBuilderExtensions 23 | { 24 | public static void UnsupportedExtension(this ITypelyBuilderOfString builder) => 25 | builder.MinLength(3).MaxLength(20).Normalize(x => x.ToUpper()); 26 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/Diagnostics/UnsupportedPropertiesSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.Diagnostics; 5 | 6 | public class UnsupportedPropertiesSpecification : ITypelySpecification 7 | { 8 | private string TypeName => "MyType"; 9 | 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | builder.OfString().For(TypeName); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/Diagnostics/UnsupportedTypesSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.Diagnostics; 5 | 6 | public class UnsupportedTypesSpecification : ITypelySpecification 7 | { 8 | public class A 9 | { 10 | } 11 | 12 | public record B 13 | { 14 | } 15 | 16 | private struct C 17 | { 18 | } 19 | 20 | protected enum D { } 21 | 22 | public void Create(ITypelyBuilder builder) 23 | { 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/Diagnostics/UnsupportedVariablesSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications.Diagnostics; 5 | 6 | public class UnsupportedVariablesSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfString().AsFactory(); 11 | factory.For("BasicType"); 12 | 13 | const string constVar1 = "constVar1"; 14 | string var2 = "var2"; 15 | int i = 0; 16 | 17 | builder.OfString().For(constVar1); 18 | builder.OfString().For(var2); 19 | builder.OfString().For(i.ToString()); 20 | 21 | var number = builder.OfInt().LessThan(100); 22 | number.For("A"); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DiagnosticsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DiagnosticsSpecification : ITypelySpecification 7 | { 8 | private const string TypeName = "MyType"; 9 | 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | const string myVar1 = "MyVar1"; 13 | string myVar2 = "MyVar2"; 14 | 15 | builder.OfString().For(myVar1); 16 | builder.OfString().For(myVar2); 17 | builder.OfString().For(TypeName); 18 | builder.OfString().For("Unsupported").UnsupportedExtension(); 19 | 20 | CreateTypeFromUnsupportedCall(builder); 21 | 22 | var unsupported = CreateTypeFromUnsupportedCall(builder); 23 | unsupported.NotEmpty(); 24 | 25 | builder.OfString().For("ShouldGenerate"); 26 | } 27 | 28 | public IRuleBuilderOfInt CreateTypeFromUnsupportedCall(ITypelyBuilder builder) => 29 | builder.OfInt().For("AddressId").GreaterThan(0); 30 | } 31 | 32 | internal static class TypelyBuilderExtensions 33 | { 34 | public static ITypelyBuilderOfString UnsupportedExtension(this ITypelyBuilderOfString builder) => 35 | builder.MinLength(3).MaxLength(20).Normalize(x => x.ToUpper()); 36 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/DoubleSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class DoubleSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfDouble() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(-1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/EmptySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class EmptySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/FloatSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class FloatSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfFloat() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(-1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/GuidSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class GuidSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfGuid() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(Guid.Empty); 20 | 21 | vote.Must(x => x == Guid.Parse("bf820b37-c090-4d51-8a69-07db3d2f42ea")) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(Guid.Parse("bf820b37-c090-4d51-8a69-07db3d2f42ea")) 24 | .WithMessage(() => LocalizedMessages.CustomMessage) 25 | .GreaterThanOrEqualTo(Guid.Parse("bf820b37-c090-4d51-8a69-07db3d2f42ea")) 26 | .WithMessage(() => A.CustomLocalization.Value) 27 | .LessThan(Guid.Parse("bf820b37-c090-4d51-8a69-07db3d2f42ea")) 28 | .LessThanOrEqualTo(Guid.Parse("bf820b37-c090-4d51-8a69-07db3d2f42ea")); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/IntSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfInt() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .Normalize(abc => abc + 1) 20 | .NotEqual(-1); 21 | 22 | vote.Must(x => x == 122) 23 | .Must((x) => !x.Equals(10)) 24 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 25 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 26 | .LessThan(20) 27 | .LessThanOrEqualTo(20); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/LongSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class LongSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfLong() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(-1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/NamespaceSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications 5 | { 6 | internal class NamespaceSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/NoNamespaceSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | // ReSharper disable CheckNamespace 5 | 6 | namespace Typely.Generators.Tests.Typely.Specifications; 7 | 8 | internal class NoNamespaceSpecification : ITypelySpecification 9 | { 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/SByteSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class SByteSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfSByte() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/ShortSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class ShortSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfShort() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(-1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/StringSpecification.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.RegularExpressions; 3 | using Typely.Core; 4 | using Typely.Core.Builders; 5 | 6 | namespace Typely.Generators.Tests.Typely.Specifications; 7 | 8 | internal class StringSpecification : ITypelySpecification 9 | { 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | builder.OfString().For("Code") 13 | .AsClass() 14 | .WithName(() => "Code") 15 | .Length(21) 16 | .Length(4, 20) 17 | .NotEqual("0000") 18 | .Matches(new Regex(".+")) 19 | .GreaterThan("A") 20 | .GreaterThanOrEqualTo("A") 21 | .LessThan("A") 22 | .MinLength(2) 23 | .LessThanOrEqualTo("A") 24 | .Normalize(x => x.Trim().ToLower()); 25 | 26 | var sf = builder.OfString().AsFactory(); 27 | var sf2 = sf.WithName("Username").AsFactory(); 28 | sf2.For("UserId") 29 | .WithNamespace("UserAggregate") 30 | .WithName("Owner identifier") 31 | .NotEmpty() 32 | .NotEqual("0").WithMessage("{Name} cannot be equal to {ComparisonValue}.").WithErrorCode("ERR001") 33 | .MaxLength(20) 34 | .Must(x => x != "1" && x.ToLower() == "12") 35 | .Normalize((x) => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(x)); 36 | } 37 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/TimeOnlySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class TimeOnlySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfTimeOnly() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(new TimeOnly(2021, 1, 1)); 20 | 21 | vote.Must(x => x == new TimeOnly(2022, 1, 1)) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(new TimeOnly(2022, 1, 1)).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(new TimeOnly(2022, 1, 1)).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(new TimeOnly(2022, 1, 1)) 26 | .LessThanOrEqualTo(new TimeOnly(2022, 1, 1)); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/TimeSpanSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class TimeSpanSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfTimeSpan() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(new TimeSpan(2021, 1, 1)); 20 | 21 | vote.Must(x => x == new TimeSpan(2022, 1, 1)) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(new TimeSpan(2022, 1, 1)).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(new TimeSpan(2022, 1, 1)).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(new TimeSpan(2022, 1, 1)) 26 | .LessThanOrEqualTo(new TimeSpan(2022, 1, 1)); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/UIntSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class UIntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfUInt() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/ULongSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class ULongSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfULong() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/UShortSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications; 5 | 6 | internal class UShortSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | var factory = builder.OfUShort() 11 | .AsStruct() 12 | .WithNamespace("Election") 13 | .NotEmpty().WithMessage("The value cannot be empty").WithErrorCode("ERR-001") 14 | .AsFactory(); 15 | 16 | var vote = factory 17 | .For("Votes") 18 | .WithName("Presidency vote") 19 | .NotEqual(1); 20 | 21 | vote.Must(x => x == 122) 22 | .Must((x) => !x.Equals(10)) 23 | .GreaterThan(10).WithMessage(() => LocalizedMessages.CustomMessage) 24 | .GreaterThanOrEqualTo(10).WithMessage(() => A.CustomLocalization.Value) 25 | .LessThan(20) 26 | .LessThanOrEqualTo(20); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/Specifications/WrappedNamespaceSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Generators.Tests.Typely.Specifications 5 | { 6 | internal class ParentClass 7 | { 8 | internal class WrappedNamespaceSpecification : ITypelySpecification 9 | { 10 | public void Create(ITypelyBuilder builder) 11 | { 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Typely/TypelyGeneratorDriver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Typely.Generators.Typely; 4 | 5 | namespace Typely.Generators.Tests.Typely; 6 | 7 | internal class TypelyGeneratorDriver 8 | { 9 | private readonly Compilation _compilation; 10 | 11 | public TypelyGeneratorDriver(Compilation compilation) 12 | { 13 | _compilation = compilation; 14 | } 15 | 16 | public GeneratorDriver Run() 17 | { 18 | // Directly create an instance of the generator 19 | // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime) 20 | var generator = new TypelyGenerator(); 21 | // Create the driver that will control the generation, passing in our generator 22 | GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); 23 | 24 | // Run the generation pass 25 | // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) 26 | return driver.RunGenerators(_compilation); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Typely.Generators.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.Isolated.Tests/Typely.Isolated.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/Typely.Isolated.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.Isolated.Tests/ValidationErrorFactory.Tests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | 3 | namespace Typely.Isolated.Tests; 4 | 5 | public class ValidationErrorFactoryTests 6 | { 7 | [Fact] 8 | public void EnableSensitiveDataLogging_Should_AddValueToPlaceholders() 9 | { 10 | TypelyOptions.Instance.EnableSensitiveDataLogging(); 11 | var validationError = ValidationErrorFactory.Create("value123", "code", "message", "typeName", null); 12 | TypelyOptions.Instance.EnableSensitiveDataLogging(false); 13 | 14 | Assert.Equal("value123", validationError.PlaceholderValues[ValidationPlaceholders.Value]); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/Asserts.cs: -------------------------------------------------------------------------------- 1 | using CsCheck; 2 | using Typely.Core; 3 | 4 | namespace Typely.Tests; 5 | 6 | /// 7 | /// Common asserts. 8 | /// 9 | internal static class Asserts 10 | { 11 | /// 12 | /// Test many possibilities that should not return an error and one case that does. 13 | /// 14 | /// Type generated by Typely. 15 | /// Underlying type of . 16 | /// Generator of the underlying value. 17 | /// Predicate that should create a validation error. 18 | /// Value that always causes a validation error. 19 | public static void ValidationMatchPredicate(Gen gen, 20 | Func predicate, TUnderlyingValue valueThatReturnError) 21 | where TTypelyValue : ITypelyValue 22 | { 23 | gen.Sample(x => predicate(x) == (TTypelyValue.Validate(x) != null)); 24 | Assert.NotNull(TTypelyValue.Validate(valueThatReturnError)); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/CompleteTypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests; 5 | 6 | public class CompleteTypelySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("TypelyOptionTestsType").NotEmpty(); 11 | builder.OfInt().For("ValidationErrorTestsType").NotEmpty(); 12 | var factory = builder.OfString().AsFactory(); 13 | factory.For("BasicType"); 14 | factory.For("BasicType2"); 15 | 16 | builder 17 | .OfString() 18 | .AsClass() 19 | .For("UserId") 20 | .WithNamespace("UserAggregate1") 21 | .WithName("Owner identifier") 22 | .AsStruct() 23 | .NotEmpty().WithMessage("'Name' cannot be empty.").WithErrorCode("ERR001") 24 | .NotEqual("1"); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/Converters/TypelyJsonConverterTests.cs: -------------------------------------------------------------------------------- 1 | using CsCheck; 2 | using System.Text.Json; 3 | 4 | namespace Typely.Tests.Converters; 5 | 6 | public class TypelyJsonConverterTests 7 | { 8 | internal class BasicClass 9 | { 10 | public StringSerializationTestsType? Prop { get; set; } 11 | } 12 | 13 | [Fact] 14 | public void SystemTextJson_Serialize_RoundTrip() => 15 | Gen.String.Sample(x => 16 | { 17 | var obj = StringSerializationTestsType.From(x); 18 | var serialized = JsonSerializer.Serialize(obj); 19 | return JsonSerializer.Deserialize(serialized)!.Equals(obj); 20 | }); 21 | 22 | [Fact] 23 | public void SystemTextJson_Should_DeserializeEmpty() 24 | { 25 | var obj = "{\"Prop\":null}"; 26 | 27 | var actual = JsonSerializer.Deserialize(obj)!; 28 | 29 | Assert.Null(actual.Prop?.Value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Typely.Tests/Converters/TypelySpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.Converters; 5 | 6 | public class TypelySpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfString().For("StringSerializationTestsType"); 11 | builder.OfInt().For("IntTypeConverterTestsType"); 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/Converters/TypelyTypeConverterTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core.Converters; 2 | 3 | namespace Typely.Tests.Converters; 4 | 5 | public class TypelyTypeConverterTests 6 | { 7 | [Fact] 8 | public void CanConvertFromInt_ReturnsTrue_WhenUnderlyingTypeIsInt() 9 | { 10 | var converter = new TypelyTypeConverter(); 11 | Assert.True(converter.CanConvertFrom(null, typeof(int))); 12 | } 13 | 14 | [Fact] 15 | public void ConvertFromInt_ReturnsTypelyType_WhenUnderlyingTypeIsInt() 16 | { 17 | var converter = new TypelyTypeConverter(); 18 | var result = converter.ConvertFrom(null, null, 1); 19 | Assert.IsType(result); 20 | } 21 | 22 | [Fact] 23 | public void ConvertFromInt_ReturnsTypelyType_WhenUnderlyingTypeIsString() 24 | { 25 | var converter = new TypelyTypeConverter(); 26 | var result = converter.ConvertFrom(null, null, "1"); 27 | Assert.IsType(result); 28 | } 29 | 30 | [Fact] 31 | public void CanConvertToInt_ReturnsTrue_WhenUnderlyingTypeIsInt() 32 | { 33 | var converter = new TypelyTypeConverter(); 34 | Assert.True(converter.CanConvertTo(null, typeof(int))); 35 | } 36 | 37 | [Fact] 38 | public void ConvertToInt_ReturnsTrue_WhenTypeIsTypelyTypeOfInt() 39 | { 40 | var converter = new TypelyTypeConverter(); 41 | var value = IntTypeConverterTestsType.From(1); 42 | var result = converter.ConvertTo(null, null, value, typeof(int)); 43 | Assert.IsType(result); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/BoolType/BoolSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.BoolType; 5 | 6 | public class BoolSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfBool().For("BasicType"); 11 | builder.OfBool().For("NotEmptyType").NotEmpty(); 12 | builder.OfBool().For("NotEqualType").NotEqual(false); 13 | builder.OfBool().For("MustType").Must((x) => !x.Equals(10)); 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/BoolType/BoolTests.cs: -------------------------------------------------------------------------------- 1 | using CsCheck; 2 | using Typely.Core; 3 | 4 | namespace Typely.Tests.TypeGeneration.BoolType; 5 | 6 | public class BoolTests 7 | { 8 | [Fact] public void Equals_ShouldBe_True() => GenTrueEquals.Sample((x, y) => x.Equals(y)); 9 | [Fact] public void Equals_ShouldBe_False() => GenFalseEquals.Sample((x, y) => !x.Equals(y)); 10 | [Fact] public void OperatorEqual_ShouldBe_True() => GenTrueEquals.Sample((x, y) => x == y); 11 | [Fact] public void OperatorEqual_ShouldBe_False() => GenFalseEquals.Sample((x, y) => !(x == y)); 12 | [Fact] public void OperatorNotEqual_ShouldBe_True() => GenTrueEquals.Sample((x, y) => !(x != y)); 13 | [Fact] public void OperatorNotEqual_ShouldBe_False() => GenFalseEquals.Sample((x, y) => x != y); 14 | [Fact] public void CompareTo() => GenComparable.Sample((x) => x.primitive.CompareTo(x.randomObj.Value) == x.valueObject.CompareTo(x.randomObj)); 15 | [Fact] public void CompareToObject() => GenComparable.Sample((x) => x.primitive.CompareTo((object)x.randomObj.Value) == x.valueObject.CompareTo((object)x.randomObj)); 16 | [Fact] public void NotEmpty() => Assert.Throws(() => NotEmptyType.From(default)); 17 | //[Fact] public void NotEqual() => Asserts.ValidationMatchPredicate(Gen.Bool, (s) => s.Equals(true), false); 18 | //[Fact] public void Must() => Asserts.ValidationMatchPredicate(Gen.Bool, (s) => s.Equals(true), false); 19 | 20 | private Gen<(BasicType, BasicType)> GenTrueEquals => Gen.Bool.Select(x => (BasicType.From(x), BasicType.From(x))); 21 | private Gen<(BasicType, BasicType)> GenFalseEquals => Gen.Bool.Select(x => (BasicType.From(x), BasicType.From(!x))); 22 | private Gen<(bool primitive, BasicType valueObject, BasicType randomObj)> GenComparable => 23 | Gen.Select(Gen.Bool, Gen.Bool, (x, y) => (x, BasicType.From(x), BasicType.From(y))); 24 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/ByteType/ByteSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.ByteType; 5 | 6 | public class ByteSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfByte().For("BasicType"); 11 | builder.OfByte().For("NotEmptyType").NotEmpty(); 12 | builder.OfByte().For("NotEqualType").NotEqual(10); 13 | builder.OfByte().For("GreaterThanType").GreaterThan(10); 14 | builder.OfByte().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfByte().For("LessThanType").LessThan(10); 16 | builder.OfByte().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfByte().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/CharType/CharConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.CharType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfChar().For("BasicType"); 11 | builder.OfChar().For("NotEmptyType").NotEmpty(); 12 | builder.OfChar().For("NotEqualType").NotEqual('A'); 13 | builder.OfChar().For("GreaterThanType").GreaterThan('B'); 14 | builder.OfChar().For("GreaterThanOrEqualType").GreaterThanOrEqualTo('B'); 15 | builder.OfChar().For("LessThanType").LessThan('A'); 16 | builder.OfChar().For("LessThanOrEqualType").LessThanOrEqualTo('A'); 17 | builder.OfChar().For("MustType").Must((x) => !x.Equals('A')); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/DateOnlyType/DateOnlyConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.DateOnlyType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/DateTimeOffsetType/DateTimeOffsetConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.DateTimeOffsetType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/DateTimeType/DateTimeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.DateTimeType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfDateTime().For("BasicType"); 11 | builder.OfDateTime().For("NotEmptyType").NotEmpty(); 12 | builder.OfDateTime().For("NotEqualType").NotEqual(new DateTime(2023,04,26)); 13 | builder.OfDateTime().For("GreaterThanType").GreaterThan(new DateTime(2023,04,26)); 14 | builder.OfDateTime().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(new DateTime(2023,04,26)); 15 | builder.OfDateTime().For("LessThanType").LessThan(new DateTime(2023,04,26)); 16 | builder.OfDateTime().For("LessThanOrEqualType").LessThanOrEqualTo(new DateTime(2023,04,26)); 17 | builder.OfDateTime().For("MustType").Must((x) => !x.Equals(new DateTime(2023,04,26))); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/DecimalType/DecimalConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.DecimalType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/DoubleType/DoubleConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.DoubleType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/FloatType/FloatConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.FloatType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/GuidType/GuidConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.GuidType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/IntType/IntSpecification.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.IntType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/LongType/LongConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.LongType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/SByteType/SByteConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.SByteType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/ShortType/ShortConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.ShortType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/StringType/StringSpecification.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Typely.Core; 3 | using Typely.Core.Builders; 4 | 5 | namespace Typely.Tests.TypeGeneration.StringType; 6 | 7 | public class StringSpecification : ITypelySpecification 8 | { 9 | public void Create(ITypelyBuilder builder) 10 | { 11 | builder.OfString().For("BasicType"); 12 | builder.OfString().For("NotEmptyType").NotEmpty(); 13 | builder.OfString().For("NotEqualType").NotEqual("value"); 14 | builder.OfString().For("GreaterThanType").GreaterThan("value"); 15 | builder.OfString().For("GreaterThanOrEqualType").GreaterThanOrEqualTo("value"); 16 | builder.OfString().For("LessThanType").LessThan("value"); 17 | builder.OfString().For("LessThanOrEqualType").LessThanOrEqualTo("value"); 18 | builder.OfString().For("MinLengthType").MinLength(5); 19 | builder.OfString().For("MaxLengthType").MaxLength(5); 20 | builder.OfString().For("LengthType").Length(5, 10); 21 | builder.OfString().For("MatchesType").Matches(new Regex("[0-9]{1}")); 22 | builder.OfString().For("MustType").Must((x) => !x.Equals("A")); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/TimeOnlyType/TimeOnlyConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.TimeOnlyType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/TimeSpanType/TimeSpanConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.TimeSpanType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/UIntType/UIntConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.UIntType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/ULongType/ULongConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.ULongType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/TypeGeneration/UShortType/UShortConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | using Typely.Core.Builders; 3 | 4 | namespace Typely.Tests.TypeGeneration.UShortType; 5 | 6 | public class IntSpecification : ITypelySpecification 7 | { 8 | public void Create(ITypelyBuilder builder) 9 | { 10 | builder.OfInt().For("BasicType"); 11 | builder.OfInt().For("NotEmptyType").NotEmpty(); 12 | builder.OfInt().For("NotEqualType").NotEqual(10); 13 | builder.OfInt().For("GreaterThanType").GreaterThan(10); 14 | builder.OfInt().For("GreaterThanOrEqualType").GreaterThanOrEqualTo(10); 15 | builder.OfInt().For("LessThanType").LessThan(10); 16 | builder.OfInt().For("LessThanOrEqualType").LessThanOrEqualTo(10); 17 | builder.OfInt().For("MustType").Must((x) => !x.Equals(10)); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/Typely.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/Typely.Tests/TypelyOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | 3 | namespace Typely.Tests; 4 | 5 | public class TypelyOptionsTests 6 | { 7 | [Fact] 8 | public void DisabledSensitiveDataLogging_ShouldNot_OutputCurrentValue() 9 | { 10 | TypelyOptions.Instance.EnableSensitiveDataLogging(false); 11 | var validationError = TypelyOptionTestsType.Validate(0)!; 12 | 13 | Assert.False( validationError.PlaceholderValues.ContainsKey(ValidationPlaceholders.Value)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Typely.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/Typely.Tests/ValidationErrorTests.Validate_ShouldReturn_ValidationError.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ErrorCode: NotEmpty, 3 | ErrorMessage: 'ValidationErrorTestsType' must not be empty., 4 | ErrorMessageWithPlaceholders: '{Name}' must not be empty., 5 | TypeName: ValidationErrorTestsType, 6 | PlaceholderValues: { 7 | Name: ValidationErrorTestsType 8 | } 9 | } -------------------------------------------------------------------------------- /tests/Typely.Tests/ValidationErrorTests.cs: -------------------------------------------------------------------------------- 1 | using Typely.Core; 2 | 3 | namespace Typely.Tests; 4 | 5 | public class ValidationErrorTests 6 | { 7 | [Fact] 8 | public Task Validate_ShouldReturn_ValidationError() => Verify(ValidationErrorTestsType.Validate(default)); 9 | 10 | [Fact] 11 | public void Exception_ShouldBe_ValidationException() 12 | { 13 | try 14 | { 15 | ValidationErrorTestsType.From(default); 16 | } 17 | catch (ValidationException ex) 18 | { 19 | Assert.NotNull(ex.ValidationError); 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------