diff --git a/samples/Newtonsoft.Json.AspNetCore.Samples/Controllers/DemoController.cs b/samples/Newtonsoft.Json.AspNetCore.Samples/Controllers/DemoController.cs index b99ea6d6..902386bf 100644 --- a/samples/Newtonsoft.Json.AspNetCore.Samples/Controllers/DemoController.cs +++ b/samples/Newtonsoft.Json.AspNetCore.Samples/Controllers/DemoController.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Thinktecture.SmartEnums; @@ -77,6 +77,17 @@ public IActionResult RoundTrip(ProductName name) return Json(name); } + [HttpPost("boundary")] + public IActionResult RoundTrip([FromBody] BoundaryWithJsonConverter boundary) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + _logger.LogInformation("Round trip test with {Type}: {Boundary}", boundary.GetType().Name, boundary); + + return Json(boundary); + } + private IActionResult RoundTripValidatableEnum(T value) where T : IValidatableEnum, IEnum where TKey : notnull diff --git a/samples/Newtonsoft.Json.AspNetCore.Samples/Program.cs b/samples/Newtonsoft.Json.AspNetCore.Samples/Program.cs index f8817c54..96d0238e 100644 --- a/samples/Newtonsoft.Json.AspNetCore.Samples/Program.cs +++ b/samples/Newtonsoft.Json.AspNetCore.Samples/Program.cs @@ -23,19 +23,6 @@ public static async Task Main() var loggerFactory = CreateLoggerFactory(); var server = StartServerAsync(loggerFactory); - // calls - // http://localhost:5000/api/category/fruits - // http://localhost:5000/api/categoryWithConverter/fruits - // http://localhost:5000/api/group/1 - // http://localhost:5000/api/group/42 - // http://localhost:5000/api/groupWithConverter/1 - // http://localhost:5000/api/groupWithConverter/42 - // http://localhost:5000/api/productType/groceries - // http://localhost:5000/api/productType/invalid - // http://localhost:5000/api/productTypeWithJsonConverter/groceries - // http://localhost:5000/api/productTypeWithJsonConverter/invalid - // http://localhost:5000/api/productName/bread - // http://localhost:5000/api/productName/a await DoHttpRequestsAsync(loggerFactory.CreateLogger()); await server; @@ -59,6 +46,8 @@ private static async Task DoHttpRequestsAsync(ILogger logger) await DoRequestAsync(logger, client, "productTypeWithJsonConverter/invalid"); // invalid await DoRequestAsync(logger, client, "productName/bread"); await DoRequestAsync(logger, client, "productName/a"); // invalid + await DoRequestAsync(logger, client, "boundary", BoundaryWithJsonConverter.Create(1, 2)); + await DoRequestAsync(logger, client, "boundary", jsonBody: "{ \"lower\": 2, \"upper\": 1 }"); } private static async Task DoRequestAsync(ILogger logger, HttpClient client, string url, object? body = null, string? jsonBody = null) diff --git a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Controllers/DemoController.cs b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Controllers/DemoController.cs index cb59bd2c..196be1f2 100644 --- a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Controllers/DemoController.cs +++ b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Controllers/DemoController.cs @@ -52,6 +52,17 @@ public IActionResult RoundTripWithQueryString(ProductType productType) return RoundTrip(productType); } + [HttpGet("boundaryWithFactories/{boundary}")] + public IActionResult RoundTrip(BoundaryWithFactories boundary) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + _logger.LogInformation("Round trip test with {Type}: {Boundary}", boundary.GetType().Name, boundary); + + return Json(boundary); + } + [HttpPost("productType")] public IActionResult RoundTripPost([FromBody] ProductType productType) { diff --git a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/DateOnlyConverter.cs b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/DateOnlyConverter.cs deleted file mode 100644 index 176ac990..00000000 --- a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/DateOnlyConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Thinktecture; - -public class DateOnlyConverter : JsonConverter -{ - public override DateOnly Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - var value = reader.GetString(); - return DateOnly.Parse(value!); - } - - public override void Write( - Utf8JsonWriter writer, - DateOnly value, - JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString("yyyy-MM-dd")); - } -} diff --git a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Program.cs b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Program.cs index a08ae31f..9b6e844a 100644 --- a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Program.cs +++ b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Program.cs @@ -35,20 +35,6 @@ public static async Task Main() await app; - // calls - // http://localhost:5000/api/category/fruits - // http://localhost:5000/api/categoryWithConverter/fruits - // http://localhost:5000/api/group/1 - // http://localhost:5000/api/group/42 - // http://localhost:5000/api/groupWithConverter/1 - // http://localhost:5000/api/groupWithConverter/42 - // http://localhost:5000/api/productType/groceries - // http://localhost:5000/api/productType/invalid - // http://localhost:5000/api/productTypeWithJsonConverter/groceries - // http://localhost:5000/api/productTypeWithJsonConverter/invalid - // http://localhost:5000/api/productName/bread - // http://localhost:5000/api/productName/a - // http://localhost:5000/api/boundary await DoHttpRequestsAsync(loggerFactory.CreateLogger(), startMinimalWebApi); await Task.Delay(5000); @@ -69,6 +55,8 @@ private static async Task DoHttpRequestsAsync(ILogger logger, bool forMinimalWeb await DoRequestAsync(logger, client, "productType?productType=groceries"); await DoRequestAsync(logger, client, "productType", "groceries"); await DoRequestAsync(logger, client, "productType/invalid"); // invalid + await DoRequestAsync(logger, client, "boundaryWithFactories/1:2"); // uses custom factory "[ValueObjectFactory]" + await DoRequestAsync(logger, client, "boundaryWithFactories/invalid"); // invalid if (forMinimalWebApi) await DoRequestAsync(logger, client, "productTypeWithFilter?productType=invalid"); // invalid @@ -143,7 +131,6 @@ private static Task StartServerAsync(ILoggerFactory loggerFactory) .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new ValueObjectJsonConverterFactory()); - options.JsonSerializerOptions.Converters.Add(new DateOnlyConverter()); }); }) .Build(); @@ -159,7 +146,6 @@ private static Task StartMinimalWebApiAsync(ILoggerFactory loggerFactory) .ConfigureHttpJsonOptions(options => { options.SerializerOptions.Converters.Add(new ValueObjectJsonConverterFactory()); - options.SerializerOptions.Converters.Add(new DateOnlyConverter()); }); var app = builder.Build(); @@ -182,6 +168,7 @@ private static Task StartMinimalWebApiAsync(ILoggerFactory loggerFactory) return next(context); }); + routeGroup.MapGet("boundaryWithFactories/{boundary}", (BoundaryWithFactories boundary) => boundary); routeGroup.MapPost("productType", ([FromBody] ProductType productType) => productType); routeGroup.MapPost("productTypeWrapper", ([FromBody] ProductTypeWrapper productType) => productType); routeGroup.MapGet("productTypeWithJsonConverter/{productType}", (ProductTypeWithJsonConverter productType) => productType); diff --git a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Validation/BoundValueObject.cs b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Validation/BoundValueObject.cs index 58baf4a7..63317893 100644 --- a/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Validation/BoundValueObject.cs +++ b/samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Validation/BoundValueObject.cs @@ -4,7 +4,7 @@ namespace Thinktecture.Validation; public class BoundValueObject : IBoundParam - where T : IKeyedValueObject + where T : IValueObjectFactory where TKey : IParsable { private readonly T? _item; @@ -30,7 +30,7 @@ public static bool TryParse(string s, IFormatProvider? formatProvider, out Bound } else { - var validationResult = T.Validate(key, out var item); + var validationResult = T.Validate(key, formatProvider, out var item); if (validationResult is null || item is IValidatableEnum) { @@ -47,7 +47,7 @@ public static bool TryParse(string s, IFormatProvider? formatProvider, out Bound } public class BoundValueObject : IBoundParam - where T : IKeyedValueObject + where T : IValueObjectFactory { private readonly T? _item; public T? Value => Error is null ? _item : throw new ValidationException(Error); @@ -66,7 +66,7 @@ private BoundValueObject(string error) public static bool TryParse(string s, IFormatProvider? formatProvider, out BoundValueObject value) { - var validationResult = T.Validate(s, out var item); + var validationResult = T.Validate(s, formatProvider, out var item); if (validationResult is null || item is IValidatableEnum) { diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/BenchmarkContext.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/BenchmarkContext.cs index a8346b88..e790c892 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/BenchmarkContext.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/BenchmarkContext.cs @@ -16,7 +16,7 @@ public BenchmarkContext() var services = new ServiceCollection() .AddLogging(builder => builder.AddConsole()) .AddDbContext(builder => builder - .UseSqlServer("Server=localhost;Database=TT_RE_Benchmarking;Integrated Security=true;") + .UseSqlServer("Server=localhost;Database=TT_RE_Benchmarking;Integrated Security=true;TrustServerCertificate=true;") .UseLoggerFactory(NullLoggerFactory.Instance) .UseValueObjectValueConverter()); diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/ItemSearch.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/ItemSearch.cs index 4d1f8b4f..a8edf2fc 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/ItemSearch.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/ItemSearch.cs @@ -8,7 +8,6 @@ namespace Thinktecture.Benchmarks; -[MemoryDiagnoser] public class ItemSearch { public class SmartEnum diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingSmartEnums.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingSmartEnums.cs index eb5e0f25..efb0104e 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingSmartEnums.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingSmartEnums.cs @@ -9,7 +9,6 @@ namespace Thinktecture.Benchmarks; // ReSharper disable InconsistentNaming -[MemoryDiagnoser] public class LoadingSmartEnums { private BenchmarkContext? _benchmarkContext; diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingValueObjects.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingValueObjects.cs index ed6060fe..c7a1f239 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingValueObjects.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/LoadingValueObjects.cs @@ -8,7 +8,6 @@ namespace Thinktecture.Benchmarks; // ReSharper disable InconsistentNaming -[MemoryDiagnoser] public class LoadingValueObjects { private BenchmarkContext? _benchmarkContext; @@ -19,6 +18,7 @@ public class LoadingValueObjects private readonly Entity_with_ValueObjects[] _Entity_with_ValueObjects = Enumerable.Range(1, _NUMBER_OF_ENTITIES).Select(i => new Entity_with_ValueObjects(i)).ToArray(); private readonly Entity_without_ValueObjects[] _Entity_without_ValueObjects = Enumerable.Range(1, _NUMBER_OF_ENTITIES).Select(i => new Entity_without_ValueObjects(i)).ToArray(); + private readonly Entity_with_StructValueObjects[] _Entity_with_StructValueObjects = Enumerable.Range(1, _NUMBER_OF_ENTITIES).Select(i => new Entity_with_StructValueObjects(i)).ToArray(); [GlobalSetup] public void Initialize() @@ -36,6 +36,9 @@ public void Initialize() _dbContext.RemoveRange(_dbContext.Entity_without_ValueObjects); _dbContext.Entity_without_ValueObjects.AddRange(_Entity_without_ValueObjects); + _dbContext.RemoveRange(_dbContext.Entity_with_StructValueObjects); + _dbContext.Entity_with_StructValueObjects.AddRange(_Entity_with_StructValueObjects); + _dbContext.SaveChanges(); } @@ -63,4 +66,10 @@ public async Task Entity_without_ValueObjects() { await _dbContext!.Entity_without_ValueObjects.ToListAsync(); } + + [Benchmark] + public async Task Entity_with_StructValueObjects() + { + await _dbContext!.Entity_with_StructValueObjects.ToListAsync(); + } } diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemCollectionBenchmarks.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemCollectionBenchmarks.cs index 4dd26aed..0b7636c0 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemCollectionBenchmarks.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemCollectionBenchmarks.cs @@ -3,7 +3,6 @@ namespace Thinktecture.Benchmarks; -[MemoryDiagnoser] public class SingleItemCollectionBenchmarks { private readonly IReadOnlyList _singleItemCollection = SingleItem.Collection(42); diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemSetBenchmarks.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemSetBenchmarks.cs index 0a7d2ca2..97e492d8 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemSetBenchmarks.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Benchmarks/SingleItemSetBenchmarks.cs @@ -3,7 +3,6 @@ namespace Thinktecture.Benchmarks; -[MemoryDiagnoser] public class SingleItemSetBenchmarks { private readonly IReadOnlySet _singleItemSet = SingleItem.Set(42); diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/BenchmarkDbContext.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/BenchmarkDbContext.cs index 954d0fbe..91d1c4eb 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/BenchmarkDbContext.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/BenchmarkDbContext.cs @@ -14,6 +14,7 @@ public class BenchmarkDbContext : DbContext public DbSet Entity_with_ValueObjects { get; set; } = null!; public DbSet Entity_without_ValueObjects { get; set; } = null!; + public DbSet Entity_with_StructValueObjects { get; set; } = null!; public BenchmarkDbContext(DbContextOptions options) : base(options) @@ -66,5 +67,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) builder.Property(e => e.Name).HasMaxLength(100); builder.Property(e => e.Description).HasMaxLength(200); }); + + modelBuilder.Entity(builder => + { + builder.Property(e => e.Id).ValueGeneratedNever(); + builder.Property(e => e.Name).HasMaxLength(100); + builder.Property(e => e.Description).HasMaxLength(200); + }); } } diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Description.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Description.cs index 9ed37ed4..9c8a280f 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Description.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Description.cs @@ -12,6 +12,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Description cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/DescriptionStruct.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/DescriptionStruct.cs new file mode 100644 index 00000000..98630d1e --- /dev/null +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/DescriptionStruct.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Database; + +[ValueObject] +public readonly partial struct DescriptionStruct +{ + private readonly string _value; + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref string value) + { + if (String.IsNullOrWhiteSpace(value)) + { + value = null!; + validationResult = new ValidationResult("Description cannot be empty."); + return; + } + + value = value.Trim(); + + if (value.Length < 2) + validationResult = new ValidationResult("Description cannot be less than 2 characters."); + } +} diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Entity_with_StructValueObjects.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Entity_with_StructValueObjects.cs new file mode 100644 index 00000000..9f308e3a --- /dev/null +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Entity_with_StructValueObjects.cs @@ -0,0 +1,24 @@ +namespace Thinktecture.Database; + +// ReSharper disable InconsistentNaming +public class Entity_with_StructValueObjects +{ + public int Id { get; set; } + public NameStruct Name { get; set; } + public DescriptionStruct Description { get; set; } + + // ReSharper disable once UnusedMember.Local + private Entity_with_StructValueObjects(int id, NameStruct name, DescriptionStruct description) + { + Id = id; + Name = name; + Description = description; + } + + public Entity_with_StructValueObjects(int index) + { + Id = index; + Name = NameStruct.Create($"Name {index}"); + Description = DescriptionStruct.Create($"Description {index}"); + } +} diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Name.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Name.cs index 3c4e109e..9caafd74 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Name.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Name.cs @@ -12,6 +12,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Name cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/NameStruct.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/NameStruct.cs new file mode 100644 index 00000000..b5e50628 --- /dev/null +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/NameStruct.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Database; + +[ValueObject] +public readonly partial struct NameStruct +{ + private readonly string _value; + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref string value) + { + if (String.IsNullOrWhiteSpace(value)) + { + value = null!; + validationResult = new ValidationResult("Name cannot be empty."); + return; + } + + value = value.Trim(); + + if (value.Length < 2) + validationResult = new ValidationResult("Name cannot be less than 2 characters."); + } +} diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Program.cs b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Program.cs index d0b19578..dd321144 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Program.cs +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Program.cs @@ -1,8 +1,16 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using Thinktecture.Benchmarks; -// BenchmarkRunner.Run(); -// BenchmarkRunner.Run(); -// BenchmarkRunner.Run(); -// BenchmarkRunner.Run(); -BenchmarkRunner.Run(); +var config = ManualConfig.CreateMinimumViable() + .AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig())) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core70)); + +// BenchmarkRunner.Run(config); +// BenchmarkRunner.Run(config); +// BenchmarkRunner.Run(config); +// BenchmarkRunner.Run(config); +BenchmarkRunner.Run(config); diff --git a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Thinktecture.Runtime.Extensions.Benchmarking.csproj b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Thinktecture.Runtime.Extensions.Benchmarking.csproj index ff5605a0..9ad471af 100644 --- a/samples/Thinktecture.Runtime.Extensions.Benchmarking/Thinktecture.Runtime.Extensions.Benchmarking.csproj +++ b/samples/Thinktecture.Runtime.Extensions.Benchmarking/Thinktecture.Runtime.Extensions.Benchmarking.csproj @@ -7,13 +7,14 @@ + - + diff --git a/samples/Thinktecture.Runtime.Extensions.MessagePack.Samples/ProductNameWithMessagePackFormatter.cs b/samples/Thinktecture.Runtime.Extensions.MessagePack.Samples/ProductNameWithMessagePackFormatter.cs index f0d02f79..a9bb0988 100644 --- a/samples/Thinktecture.Runtime.Extensions.MessagePack.Samples/ProductNameWithMessagePackFormatter.cs +++ b/samples/Thinktecture.Runtime.Extensions.MessagePack.Samples/ProductNameWithMessagePackFormatter.cs @@ -13,6 +13,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Product name cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/SmartEnums/SmartEnumDemos.cs b/samples/Thinktecture.Runtime.Extensions.Samples/SmartEnums/SmartEnumDemos.cs index 2c175d2d..c62f809f 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/SmartEnums/SmartEnumDemos.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/SmartEnums/SmartEnumDemos.cs @@ -103,7 +103,7 @@ private static void DemoForNonValidatableEnum(ILogger logger) if (ProductType.TryGet("Housewares", out var housewares)) logger.Information("Product type {Type} with TryGet found", housewares); - var validationResult = ProductType.Validate("Groceries", out var groceries); + var validationResult = ProductType.Validate("Groceries", null, out var groceries); if (validationResult == ValidationResult.Success) { diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountStruct.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountClass.cs similarity index 92% rename from samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountStruct.cs rename to samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountClass.cs index 45145122..cec62742 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountStruct.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/AmountClass.cs @@ -1,19 +1,19 @@ -using System.ComponentModel.DataAnnotations; - -namespace Thinktecture.ValueObjects; - -[ValueObject(ComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - AdditionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - SubtractionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - MultiplyOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - DivisionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] -public readonly partial struct AmountStruct -{ - private readonly int _value; - - static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref int value) - { - if (value < 0) - validationResult = new ValidationResult("Amount must be positive."); - } -} +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.ValueObjects; + +[ValueObject(ComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + AdditionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + SubtractionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + MultiplyOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + DivisionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] +public sealed partial class AmountClass +{ + private readonly int _value; + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref int value) + { + if (value < 0) + validationResult = new ValidationResult("Amount must be positive."); + } +} diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/BoundaryWithFactories.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/BoundaryWithFactories.cs new file mode 100644 index 00000000..bf9e99a1 --- /dev/null +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/BoundaryWithFactories.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.ValueObjects; + +[ValueObject] +[ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] +[ValueObjectFactory>] +public sealed partial class BoundaryWithFactories +{ + public decimal Lower { get; } + public decimal Upper { get; } + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref decimal lower, ref decimal upper) + { + if (lower <= upper) + return; + + validationResult = new ValidationResult($"Lower boundary '{lower}' must be less than upper boundary '{upper}'", + new[] { nameof(Lower), nameof(Upper) }); + } + + /// + /// Custom implementation of "IValueObjectFactory<Boundary, string>" + /// requested by "ValueObjectFactory<string>". + /// + public static ValidationResult? Validate(string? value, IFormatProvider? provider, out BoundaryWithFactories? item) + { + item = null; + + if (value is null) + return ValidationResult.Success; + + var parts = value.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + return new ValidationResult("Invalid format."); + + if (!Decimal.TryParse(parts[0], provider, out var lower) || !Decimal.TryParse(parts[1], provider, out var upper)) + return new ValidationResult("The provided values are not numbers."); + + return Validate(lower, upper, out item); + } + + /// + /// Custom implementation of "IValueObjectConverter&lt;string&gt;" + /// requested by "ValueObjectFactory<string>". + /// + public string ToValue() + { + return $"{Lower}:{Upper}"; + } + + /// + /// Custom implementation of "IValueObjectFactory<Boundary, (int, int)>" + /// + public static ValidationResult? Validate((int, int) value, IFormatProvider? provider, out BoundaryWithFactories? item) + { + return Validate(value.Item1, value.Item2, out item); + } +} diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/OtherProductName.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/OtherProductName.cs index 316ca49b..1283075f 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/OtherProductName.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/OtherProductName.cs @@ -13,6 +13,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Product name cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductName.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductName.cs index cd9ce9e5..a0772ec2 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductName.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductName.cs @@ -14,6 +14,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Product name cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductNameStruct.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductNameStruct.cs index fa595511..039d4223 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductNameStruct.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductNameStruct.cs @@ -14,6 +14,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(value)) { + value = null!; validationResult = new ValidationResult("Product name cannot be empty."); return; } diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs index 666c4969..1382155d 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs @@ -21,9 +21,9 @@ private static void DemoForSimpleValueObjects(ILogger logger) logger.Information(""" -==== Demo for Simple Value Objects ==== + ==== Demo for Simple Value Objects ==== -"""); + """); var bread = ProductName.Create("Bread"); logger.Information("Product name: {Bread}", bread); @@ -41,7 +41,7 @@ private static void DemoForSimpleValueObjects(ILogger logger) logger.Information("ValidationException is thrown because a product name cannot be an empty string."); } - var validationResult = ProductName.Validate("Milk", out var milk); + var validationResult = ProductName.Validate("Milk", null, out var milk); if (validationResult == ValidationResult.Success) logger.Information("Product name '{Name}' created with 'TryCreate'.", milk); @@ -60,7 +60,7 @@ private static void DemoForSimpleValueObjects(ILogger logger) var otherNullProductName2 = OtherProductName.Create(" "); logger.Information("Null-Product name: {NullProduct}", otherNullProductName2); - var nullValidationResult = ProductName.Validate(null, out nullProduct); + var nullValidationResult = ProductName.Validate(null, null, out nullProduct); if (nullValidationResult == ValidationResult.Success) logger.Information("Null-Product name: {NullProduct}", nullProduct); @@ -76,9 +76,9 @@ private static void DemosForAmount(ILogger logger) logger.Information(""" -==== Demo for Amount ==== + ==== Demo for Amount ==== -"""); + """); var formattedValue = Amount.Create(42.1m).ToString("000.00", CultureInfo.InvariantCulture); // "042.10" logger.Information("Formatted: {Formatted}", formattedValue); @@ -106,9 +106,9 @@ private static void DemoForEndDate(ILogger logger) logger.Information(""" -==== Demo for End Date ==== + ==== Demo for End Date ==== -"""); + """); DateOnly today = DateOnly.FromDateTime(DateTime.Now); EndDate endDate = (EndDate)today; @@ -134,9 +134,9 @@ private static void DemoForComplexValueObjects(ILogger logger) logger.Information(""" -==== Demo for Complex Value Objects ==== + ==== Demo for Complex Value Objects ==== -"""); + """); Boundary boundaryWithCreate = Boundary.Create(lower: 1, upper: 2); logger.Information("Boundary with Create: {Boundary}", boundaryWithCreate); @@ -157,5 +157,29 @@ private static void DemoForComplexValueObjects(ILogger logger) var equal = boundaryWithCreate.Equals(boundaryWithCreate); logger.Information("Boundaries are equal: {Equal}", equal); + + // Custom implementation of IValueObjectFactory + validationResult = BoundaryWithFactories.Validate("3:4", null, out var boundaryFromString); + + if (validationResult == ValidationResult.Success) + { + logger.Information("BoundaryWithFactories '{Boundary}' created from string", boundaryFromString); + } + else + { + logger.Warning("Failed to create BoundaryWithFactories from string. Validation result: {ValidationResult}", validationResult!.ErrorMessage); + } + + // Custom implementation of IValueObjectFactory + validationResult = BoundaryWithFactories.Validate((5, 6), null, out var boundaryFromTuple); + + if (validationResult == ValidationResult.Success) + { + logger.Information("BoundaryWithFactories '{Boundary}' created from tuple", boundaryFromTuple); + } + else + { + logger.Warning("Failed to create BoundaryWithFactories from tuple. Validation result: {ValidationResult}", validationResult!.ErrorMessage); + } } } diff --git a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/TrimmingSmartEnumModelBinder.cs b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/TrimmingSmartEnumModelBinder.cs index c0f8846f..0fd43d65 100644 --- a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/TrimmingSmartEnumModelBinder.cs +++ b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/TrimmingSmartEnumModelBinder.cs @@ -8,7 +8,7 @@ namespace Thinktecture.AspNetCore.ModelBinding; /// /// Type of the value object. public sealed class TrimmingSmartEnumModelBinder : ValueObjectModelBinderBase - where T : IKeyedValueObject + where T : IValueObjectFactory { /// /// Initializes a new instance of . diff --git a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinder.cs b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinder.cs index 08833ed0..638ab70a 100644 --- a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinder.cs +++ b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinder.cs @@ -9,7 +9,7 @@ namespace Thinktecture.AspNetCore.ModelBinding; /// Type of the value object. /// Type of the key member. public sealed class ValueObjectModelBinder : ValueObjectModelBinderBase - where T : IKeyedValueObject + where T : IValueObjectFactory where TKey : notnull { /// diff --git a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderBase.cs b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderBase.cs index 5eb10028..a7b2ae44 100644 --- a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderBase.cs +++ b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderBase.cs @@ -12,7 +12,7 @@ namespace Thinktecture.AspNetCore.ModelBinding; /// Type of the value object. /// Type of the key member. public abstract class ValueObjectModelBinderBase : SimpleTypeModelBinder - where T : IKeyedValueObject + where T : IValueObjectFactory where TKey : notnull { private static readonly bool _mayReturnInvalidObjects = typeof(IValidatableEnum).IsAssignableFrom(typeof(T)); @@ -37,7 +37,7 @@ protected override void CheckModel(ModelBindingContext bindingContext, ValueProv } key = Prepare(key); - var validationResult = T.Validate(key, out var obj); + var validationResult = T.Validate(key, valueProviderResult.Culture, out var obj); if (validationResult == ValidationResult.Success || _mayReturnInvalidObjects) { diff --git a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderProvider.cs b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderProvider.cs index 5a13311c..3d7f466a 100644 --- a/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderProvider.cs +++ b/src/Thinktecture.Runtime.Extensions.AspNetCore/AspNetCore/ModelBinding/ValueObjectModelBinderProvider.cs @@ -11,14 +11,19 @@ namespace Thinktecture.AspNetCore.ModelBinding; public sealed class ValueObjectModelBinderProvider : IModelBinderProvider { private readonly bool _trimStringBasedEnums; + private readonly bool _skipBindingFromBody; /// /// Initializes new instance of . /// /// Indication whether to trim string-values before parsing them. - public ValueObjectModelBinderProvider(bool trimStringBasedEnums = true) + /// Indication whether to skip model binding if the raw value comes from request body. + public ValueObjectModelBinderProvider( + bool trimStringBasedEnums = true, + bool skipBindingFromBody = true) { _trimStringBasedEnums = trimStringBasedEnums; + _skipBindingFromBody = skipBindingFromBody; } /// @@ -31,23 +36,42 @@ public ValueObjectModelBinderProvider(bool trimStringBasedEnums = true) return null; var metadata = KeyedValueObjectMetadataLookup.Find(context.Metadata.ModelType); + Type type; + Type keyType; - if (metadata is null) + if (typeof(IValueObjectFactory).IsAssignableFrom(context.Metadata.ModelType)) + { + type = context.Metadata.ModelType; + keyType = typeof(string); + } + else if (metadata is not null) + { + type = metadata.Type; + keyType = metadata.KeyType; + } + else + { return null; + } var loggerFactory = context.Services.GetRequiredService(); - var modelBinderType = _trimStringBasedEnums && metadata.IsEnumeration && metadata.KeyType == typeof(string) - ? typeof(TrimmingSmartEnumModelBinder<>).MakeGenericType(metadata.Type) - : typeof(ValueObjectModelBinder<,>).MakeGenericType(metadata.Type, metadata.KeyType); + var modelBinderType = _trimStringBasedEnums && metadata?.IsEnumeration == true && keyType == typeof(string) + ? typeof(TrimmingSmartEnumModelBinder<>).MakeGenericType(type) + : typeof(ValueObjectModelBinder<,>).MakeGenericType(type, keyType); var modelBinder = Activator.CreateInstance(modelBinderType, loggerFactory) ?? throw new Exception($"Could not create an instance of type '{modelBinderType.Name}'."); return (IModelBinder)modelBinder; } - private static bool SkipModelBinding(ModelBinderProviderContext context) + private bool SkipModelBinding(ModelBinderProviderContext context) { - return context.BindingInfo.BindingSource != null && - (context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body) || context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Services)); + if (context.BindingInfo.BindingSource == null) + return false; + + if (context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Services)) + return true; + + return _skipBindingFromBody && context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body); } } diff --git a/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Storage/ValueConversion/ValueObjectValueConverterFactory.cs b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Storage/ValueConversion/ValueObjectValueConverterFactory.cs index d69c9c37..23075557 100644 --- a/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Storage/ValueConversion/ValueObjectValueConverterFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Storage/ValueConversion/ValueObjectValueConverterFactory.cs @@ -17,7 +17,7 @@ public sealed class ValueObjectValueConverterFactory /// Type of the key. /// An instance of > public static ValueConverter Create(bool useConstructorForRead = true) - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { return new ValueObjectValueConverter(useConstructorForRead); @@ -31,7 +31,7 @@ public static ValueConverter Create(bool useConstructorForRead /// Type of the key. /// An instance of > public static ValueConverter CreateForValidatableEnum(bool validateOnWrite) - where TEnum : IValidatableEnum, IEnum + where TEnum : IValidatableEnum, IValueObjectFactory, IValueObjectConverter where TKey : notnull { return new ValidatableEnumValueConverter(validateOnWrite); @@ -81,7 +81,7 @@ internal static ValueConverter Create( } private static Expression> GetConverterFromKey(bool useConstructor) - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { var metadata = KeyedValueObjectMetadataLookup.Find(typeof(T)); @@ -97,17 +97,17 @@ private static Expression> GetConverterFromKey(bool useCo } private sealed class ValueObjectValueConverter : ValueConverter - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { public ValueObjectValueConverter(bool useConstructor) - : base(static o => o.GetKey(), GetConverterFromKey(useConstructor)) + : base(static o => o.ToValue(), GetConverterFromKey(useConstructor)) { } } private sealed class ValidatableEnumValueConverter : ValueConverter - where TEnum : IValidatableEnum, IEnum + where TEnum : IValidatableEnum, IValueObjectFactory, IValueObjectConverter where TKey : notnull { public ValidatableEnumValueConverter(bool validateOnWrite) @@ -119,13 +119,13 @@ private static Expression> GetKeyProvider(bool validateOnWrite { return validateOnWrite ? static item => GetKeyIfValid(item) - : static item => item.GetKey(); + : static item => item.ToValue(); } private static TKey GetKeyIfValid(TEnum item) { item.EnsureValid(); - return item.GetKey(); + return item.ToValue(); } } } diff --git a/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverter.cs b/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverter.cs index baa0c55b..fac443dc 100644 --- a/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverter.cs +++ b/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverter.cs @@ -9,7 +9,7 @@ namespace Thinktecture.Text.Json.Serialization; /// Type of the value object. /// Type of the key. public sealed class ValueObjectJsonConverter : JsonConverter - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { private static readonly bool _mayReturnInvalidObjects = typeof(IValidatableEnum).IsAssignableFrom(typeof(T)); @@ -35,7 +35,7 @@ public ValueObjectJsonConverter(JsonSerializerOptions options) if (key is null) return default; - var validationResult = T.Validate(key, out var obj); + var validationResult = T.Validate(key, null, out var obj); if (validationResult is not null && !_mayReturnInvalidObjects) throw new JsonException(validationResult.ErrorMessage ?? "JSON deserialization failed."); @@ -49,6 +49,6 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions if (value is null) throw new ArgumentNullException(nameof(value)); - _keyConverter.Write(writer, value.GetKey(), options); + _keyConverter.Write(writer, value.ToValue(), options); } } diff --git a/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverterFactory.cs b/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverterFactory.cs index 7e32a901..8bff0a00 100644 --- a/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverterFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverterFactory.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Thinktecture.Internal; @@ -8,7 +9,7 @@ namespace Thinktecture.Text.Json.Serialization; /// Factory for creation of . /// public sealed class ValueObjectJsonConverterFactory : JsonConverterFactory - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { /// @@ -35,7 +36,8 @@ public sealed class ValueObjectJsonConverterFactory : JsonConverterFactory /// public override bool CanConvert(Type typeToConvert) { - return KeyedValueObjectMetadataLookup.Find(typeToConvert) is not null; + return KeyedValueObjectMetadataLookup.Find(typeToConvert) is not null + || typeToConvert.GetCustomAttributes().Any(a => a.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)); } /// @@ -45,11 +47,28 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer ArgumentNullException.ThrowIfNull(options); var metadata = KeyedValueObjectMetadataLookup.Find(typeToConvert); + var customFactory = typeToConvert.GetCustomAttributes() + .LastOrDefault(a => a.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)); - if (metadata is null) + Type type; + Type keyType; + + if (customFactory is not null) + { + type = typeToConvert; + keyType = customFactory.Type; + } + else if (metadata is not null) + { + type = metadata.Type; + keyType = metadata.KeyType; + } + else + { throw new InvalidOperationException($"No metadata for provided type '{typeToConvert.Name}' found."); + } - var converterType = typeof(ValueObjectJsonConverter<,>).MakeGenericType(metadata.Type, metadata.KeyType); + var converterType = typeof(ValueObjectJsonConverter<,>).MakeGenericType(type, keyType); var converter = Activator.CreateInstance(converterType, options); return (JsonConverter)(converter ?? throw new Exception($"Could not create converter of type '{converterType.Name}'.")); diff --git a/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/StructValueObjectMessagePackFormatter.cs b/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/StructValueObjectMessagePackFormatter.cs index a54aa16e..618cc8c5 100644 --- a/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/StructValueObjectMessagePackFormatter.cs +++ b/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/StructValueObjectMessagePackFormatter.cs @@ -11,7 +11,7 @@ namespace Thinktecture.Formatters; /// Type of the value object. /// Type of the key. public sealed class StructValueObjectMessagePackFormatter : IMessagePackFormatter, IMessagePackFormatter - where T : struct, IKeyedValueObject + where T : struct, IValueObjectFactory, IValueObjectConverter where TKey : notnull { private static readonly bool _mayReturnInvalidObjects = typeof(IValidatableEnum).IsAssignableFrom(typeof(T)); @@ -20,7 +20,7 @@ public sealed class StructValueObjectMessagePackFormatter : IMessagePac public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) { var formatter = options.Resolver.GetFormatterWithVerify(); - formatter.Serialize(ref writer, value.GetKey(), options); + formatter.Serialize(ref writer, value.ToValue(), options); } /// @@ -73,7 +73,7 @@ private static bool TryReadKey( private static T Deserialize(TKey key) { - var validationResult = T.Validate(key, out var obj); + var validationResult = T.Validate(key, null, out var obj); if (validationResult is not null && !_mayReturnInvalidObjects) throw new ValidationException(validationResult.ErrorMessage ?? "MessagePack deserialization failed."); diff --git a/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/ValueObjectMessagePackFormatter.cs b/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/ValueObjectMessagePackFormatter.cs index 9ab3ad81..9ddda59c 100644 --- a/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/ValueObjectMessagePackFormatter.cs +++ b/src/Thinktecture.Runtime.Extensions.MessagePack/Formatters/ValueObjectMessagePackFormatter.cs @@ -10,7 +10,7 @@ namespace Thinktecture.Formatters; /// Type of the value object. /// Type of the key. public sealed class ValueObjectMessagePackFormatter : IMessagePackFormatter - where T : class, IKeyedValueObject + where T : class, IValueObjectFactory, IValueObjectConverter where TKey : notnull { private static readonly bool _mayReturnInvalidObjects = typeof(IValidatableEnum).IsAssignableFrom(typeof(T)); @@ -25,7 +25,7 @@ public void Serialize(ref MessagePackWriter writer, T? value, MessagePackSeriali else { var formatter = options.Resolver.GetFormatterWithVerify(); - formatter.Serialize(ref writer, value.GetKey(), options); + formatter.Serialize(ref writer, value.ToValue(), options); } } @@ -41,7 +41,7 @@ public void Serialize(ref MessagePackWriter writer, T? value, MessagePackSeriali if (key is null) return default; - var validationResult = T.Validate(key, out var obj); + var validationResult = T.Validate(key, null, out var obj); if (validationResult is not null && !_mayReturnInvalidObjects) throw new ValidationException(validationResult.ErrorMessage ?? "MessagePack deserialization failed."); diff --git a/src/Thinktecture.Runtime.Extensions.MessagePack/ValueObjectMessageFormatterResolver.cs b/src/Thinktecture.Runtime.Extensions.MessagePack/ValueObjectMessageFormatterResolver.cs index fe6ff840..990ed6de 100644 --- a/src/Thinktecture.Runtime.Extensions.MessagePack/ValueObjectMessageFormatterResolver.cs +++ b/src/Thinktecture.Runtime.Extensions.MessagePack/ValueObjectMessageFormatterResolver.cs @@ -1,3 +1,4 @@ +using System.Reflection; using MessagePack; using MessagePack.Formatters; using Thinktecture.Formatters; @@ -40,14 +41,31 @@ static Cache() { var type = typeof(T); var metadata = KeyedValueObjectMetadataLookup.Find(type); + var customFactory = type.GetCustomAttributes() + .LastOrDefault(a => a.UseForSerialization.HasFlag(SerializationFrameworks.MessagePack)); - if (metadata is null) + Type modelType; + Type keyType; + + if (customFactory is not null) + { + modelType = type; + keyType = customFactory.Type; + } + else if (metadata is not null) + { + modelType = metadata.Type; + keyType = metadata.KeyType; + } + else + { return; + } - var formatterTypeDefinition = metadata.Type.IsClass + var formatterTypeDefinition = modelType.IsClass ? typeof(ValueObjectMessagePackFormatter<,>) : typeof(StructValueObjectMessagePackFormatter<,>); - var formatterType = formatterTypeDefinition.MakeGenericType(metadata.Type, metadata.KeyType); + var formatterType = formatterTypeDefinition.MakeGenericType(modelType, keyType); var formatter = Activator.CreateInstance(formatterType); diff --git a/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter.cs b/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter.cs index 1fc072de..76cbb343 100644 --- a/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter.cs +++ b/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Reflection; using Newtonsoft.Json; using Thinktecture.Internal; @@ -14,7 +15,9 @@ public sealed class ValueObjectNewtonsoftJsonConverter : JsonConverter /// public override bool CanConvert(Type objectType) { - return _cache.ContainsKey(objectType) || KeyedValueObjectMetadataLookup.Find(objectType) is not null; + return _cache.ContainsKey(objectType) + || KeyedValueObjectMetadataLookup.Find(objectType) is not null + || objectType.GetCustomAttributes().Any(a => a.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson)); } /// @@ -44,11 +47,28 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer private static JsonConverter CreateConverter(Type type) { var metadata = KeyedValueObjectMetadataLookup.Find(type); + var customFactory = type.GetCustomAttributes() + .LastOrDefault(a => a.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson)); - if (metadata is null) + Type modelType; + Type keyType; + + if (customFactory is not null) + { + modelType = type; + keyType = customFactory.Type; + } + else if (metadata is not null) + { + modelType = metadata.Type; + keyType = metadata.KeyType; + } + else + { throw new InvalidOperationException($"The provided type is not serializable by the '{nameof(ValueObjectNewtonsoftJsonConverter)}'. Type: {type.FullName}"); + } - var converterType = typeof(ValueObjectNewtonsoftJsonConverter<,>).MakeGenericType(metadata.Type, metadata.KeyType); + var converterType = typeof(ValueObjectNewtonsoftJsonConverter<,>).MakeGenericType(modelType, keyType); var converter = Activator.CreateInstance(converterType); return (JsonConverter)(converter ?? throw new Exception($"Could not create converter of type '{converterType.Name}'.")); diff --git a/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter_T2.cs b/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter_T2.cs index 73ff91fb..872f92e2 100644 --- a/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter_T2.cs +++ b/src/Thinktecture.Runtime.Extensions.Newtonsoft.Json/Json/ValueObjectNewtonsoftJsonConverter_T2.cs @@ -10,7 +10,7 @@ namespace Thinktecture.Json; /// Type of the value object. /// Type of the key. public sealed class ValueObjectNewtonsoftJsonConverter : JsonConverter - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { private static readonly Type _type = typeof(T); @@ -29,7 +29,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer } else { - serializer.Serialize(writer, ((T)value).GetKey()); + serializer.Serialize(writer, ((T)value).ToValue()); } } @@ -60,7 +60,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer if (key is null) return null; - var validationResult = T.Validate(key, out var obj); + var validationResult = T.Validate(key, null, out var obj); if (validationResult != ValidationResult.Success && !_mayReturnInvalidObjects) throw new JsonSerializationException(validationResult!.ErrorMessage ?? "JSON deserialization failed."); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ArgumentName.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ArgumentName.cs new file mode 100644 index 00000000..01c3e766 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ArgumentName.cs @@ -0,0 +1,3 @@ +namespace Thinktecture.CodeAnalysis; + +public readonly record struct ArgumentName(string Raw, string Escaped); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/AttributeInfo.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/AttributeInfo.cs index 1e7d46a8..989e0a03 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/AttributeInfo.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/AttributeInfo.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using Microsoft.CodeAnalysis; namespace Thinktecture.CodeAnalysis; @@ -8,6 +9,7 @@ namespace Thinktecture.CodeAnalysis; public bool HasJsonConverterAttribute { get; } public bool HasNewtonsoftJsonConverterAttribute { get; } public bool HasMessagePackFormatterAttribute { get; } + public IReadOnlyList DesiredFactories { get; } public AttributeInfo(INamedTypeSymbol type) { @@ -15,6 +17,7 @@ public AttributeInfo(INamedTypeSymbol type) HasJsonConverterAttribute = default; HasNewtonsoftJsonConverterAttribute = default; HasMessagePackFormatterAttribute = default; + var valueObjectFactories = ImmutableArray.Empty; foreach (var attribute in type.GetAttributes()) { @@ -37,7 +40,17 @@ public AttributeInfo(INamedTypeSymbol type) { HasMessagePackFormatterAttribute = true; } + else if (attribute.AttributeClass.IsValueObjectFactoryAttribute()) + { + var useForSerialization = attribute.FindUseForSerialization(); + var desiredFactory = new DesiredFactory(attribute.AttributeClass.TypeArguments[0], useForSerialization); + + valueObjectFactories = valueObjectFactories.RemoveAll(f => f.TypeFullyQualified == desiredFactory.TypeFullyQualified); + valueObjectFactories = valueObjectFactories.Add(desiredFactory); + } } + + DesiredFactories = valueObjectFactories; } public override bool Equals(object? obj) @@ -50,7 +63,8 @@ public bool Equals(AttributeInfo other) return HasStructLayoutAttribute == other.HasStructLayoutAttribute && HasJsonConverterAttribute == other.HasJsonConverterAttribute && HasNewtonsoftJsonConverterAttribute == other.HasNewtonsoftJsonConverterAttribute - && HasMessagePackFormatterAttribute == other.HasMessagePackFormatterAttribute; + && HasMessagePackFormatterAttribute == other.HasMessagePackFormatterAttribute + && DesiredFactories.EqualsTo(other.DesiredFactories); } public override int GetHashCode() @@ -61,6 +75,7 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ HasJsonConverterAttribute.GetHashCode(); hashCode = (hashCode * 397) ^ HasNewtonsoftJsonConverterAttribute.GetHashCode(); hashCode = (hashCode * 397) ^ HasMessagePackFormatterAttribute.GetHashCode(); + hashCode = (hashCode * 397) ^ DesiredFactories.ComputeHashCode(); return hashCode; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparableCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparableCodeGenerator.cs index 3286a23e..3262ad09 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparableCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparableCodeGenerator.cs @@ -16,14 +16,14 @@ public ComparableCodeGenerator(string? comparerAccessor) _comparerAccessor = comparerAccessor; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" global::System.IComparable, - global::System.IComparable<").Append(type.TypeFullyQualified).Append(">"); + global::System.IComparable<").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" /// @@ -32,17 +32,17 @@ public int CompareTo(object? obj) if(obj is null) return 1; - if(obj is not ").Append(type.TypeFullyQualified).Append(@" item) - throw new global::System.ArgumentException(""Argument must be of type \""").Append(type.TypeMinimallyQualified).Append(@"\""."", nameof(obj)); + if(obj is not ").Append(state.Type.TypeFullyQualified).Append(@" item) + throw new global::System.ArgumentException(""Argument must be of type \""").Append(state.Type.TypeMinimallyQualified).Append(@"\""."", nameof(obj)); return this.CompareTo(item); } /// - public int CompareTo(").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj) + public int CompareTo(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" obj) {"); - if (type.IsReferenceType) + if (state.Type.IsReferenceType) { sb.Append(@" if(obj is null) @@ -53,12 +53,12 @@ public int CompareTo(").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" o if (_comparerAccessor is null) { sb.Append(@" - return this.").Append(keyMember.Name).Append(".CompareTo(obj.").Append(keyMember.Name).Append(");"); + return this.").Append(state.KeyMember.Name).Append(".CompareTo(obj.").Append(state.KeyMember.Name).Append(");"); } else { sb.Append(@" - return ").Append(_comparerAccessor).Append(".Comparer.Compare(this.").Append(keyMember.Name).Append(", obj.").Append(keyMember.Name).Append(");"); + return ").Append(_comparerAccessor).Append(".Comparer.Compare(this.").Append(state.KeyMember.Name).Append(", obj.").Append(state.KeyMember.Name).Append(");"); } sb.Append(@" diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs index e18cc406..ff2fa62f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs @@ -57,44 +57,43 @@ private ComparisonOperatorsCodeGenerator( _comparerAccessor = comparerAccessor; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { // If we have a custom comparer then we don't care whether the key member has the operators or not if (!_keyMemberOperators.HasOperator(ImplementedComparisonOperators.All) && _comparerAccessor is null) return; sb.Append(@" - global::System.Numerics.IComparisonOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", bool>"); + global::System.Numerics.IComparisonOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", bool>"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.IComparisonOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", bool>"); + global::System.Numerics.IComparisonOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", bool>"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - var leftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var rightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var leftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var rightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_comparerAccessor is null) { - GenerateUsingOperators(sb, type, keyMember, _keyMemberOperators, leftNullCheck, rightNullCheck); + GenerateUsingOperators(sb, state, _keyMemberOperators, leftNullCheck, rightNullCheck); } else { - GenerateUsingComparer(sb, type, keyMember, _comparerAccessor, leftNullCheck, rightNullCheck); + GenerateUsingComparer(sb, state, _comparerAccessor, leftNullCheck, rightNullCheck); } if (_withKeyTypeOverloads) - GenerateOverloadsForKeyType(sb, type, keyMember); + GenerateOverloadsForKeyType(sb, state); } private static void GenerateUsingOperators( StringBuilder sb, - ITypeInformation type, - IMemberInformation keyMember, + InterfaceCodeGeneratorState state, ImplementedComparisonOperators keyMemberOperators, string? leftNullCheck, string? rightNullCheck) @@ -103,9 +102,9 @@ private static void GenerateUsingOperators( { sb.Append(@" /// - public static bool operator <(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(keyMember.Name).Append(" < right.").Append(keyMember.Name).Append(@"; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(" < right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -114,9 +113,9 @@ private static void GenerateUsingOperators( sb.Append(@" /// - public static bool operator <=(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(keyMember.Name).Append(" <= right.").Append(keyMember.Name).Append(@"; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(" <= right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -125,9 +124,9 @@ private static void GenerateUsingOperators( sb.Append(@" /// - public static bool operator >(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(keyMember.Name).Append(" > right.").Append(keyMember.Name).Append(@"; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(" > right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -136,17 +135,16 @@ private static void GenerateUsingOperators( sb.Append(@" /// - public static bool operator >=(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(keyMember.Name).Append(" >= right.").Append(keyMember.Name).Append(@"; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(" >= right.").Append(state.KeyMember.Name).Append(@"; }"); } } private static void GenerateUsingComparer( StringBuilder sb, - ITypeInformation type, - IMemberInformation keyMember, + InterfaceCodeGeneratorState state, string comparerAccessor, string? leftNullCheck, string? rightNullCheck) @@ -154,51 +152,50 @@ private static void GenerateUsingComparer( sb.Append(@" /// - public static bool operator <(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(", right.").Append(keyMember.Name).Append(@") < 0; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(", right.").Append(state.KeyMember.Name).Append(@") < 0; } /// - public static bool operator <=(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(", right.").Append(keyMember.Name).Append(@") <= 0; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(", right.").Append(state.KeyMember.Name).Append(@") <= 0; } /// - public static bool operator >(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(", right.").Append(keyMember.Name).Append(@") > 0; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(", right.").Append(state.KeyMember.Name).Append(@") > 0; } /// - public static bool operator >=(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(", right.").Append(keyMember.Name).Append(@") >= 0; + ").Append(leftNullCheck).Append(rightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(", right.").Append(state.KeyMember.Name).Append(@") >= 0; }"); } - private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + private void GenerateOverloadsForKeyType(StringBuilder sb, InterfaceCodeGeneratorState state) { - var typeLeftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var typeRightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; - var memberLeftNullCheck = keyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; - var memberRightNullCheck = keyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var typeLeftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var typeRightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var memberLeftNullCheck = state.KeyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; + var memberRightNullCheck = state.KeyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_comparerAccessor is null) { - GenerateKeyOverloadsUsingOperators(sb, type, keyMember, _keyMemberOperators, typeLeftNullCheck, memberRightNullCheck, memberLeftNullCheck, typeRightNullCheck); + GenerateKeyOverloadsUsingOperators(sb, state, _keyMemberOperators, typeLeftNullCheck, memberRightNullCheck, memberLeftNullCheck, typeRightNullCheck); } else { - GenerateKeyOverloadsUsingComparer(sb, type, keyMember, _comparerAccessor, typeLeftNullCheck, memberRightNullCheck, memberLeftNullCheck, typeRightNullCheck); + GenerateKeyOverloadsUsingComparer(sb, state, _comparerAccessor, typeLeftNullCheck, memberRightNullCheck, memberLeftNullCheck, typeRightNullCheck); } } private static void GenerateKeyOverloadsUsingOperators( StringBuilder sb, - ITypeInformation type, - IMemberInformation keyMember, + InterfaceCodeGeneratorState state, ImplementedComparisonOperators keyMemberOperators, string? typeLeftNullCheck, string? memberRightNullCheck, @@ -210,15 +207,15 @@ private static void GenerateKeyOverloadsUsingOperators( sb.Append(@" /// - public static bool operator <(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(keyMember.Name).Append(@" < right; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(@" < right; } /// - public static bool operator <(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left < right.").Append(keyMember.Name).Append(@"; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left < right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -227,15 +224,15 @@ private static void GenerateKeyOverloadsUsingOperators( sb.Append(@" /// - public static bool operator <=(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(keyMember.Name).Append(@" <= right; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(@" <= right; } /// - public static bool operator <=(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left <= right.").Append(keyMember.Name).Append(@"; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left <= right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -244,15 +241,15 @@ private static void GenerateKeyOverloadsUsingOperators( sb.Append(@" /// - public static bool operator >(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(keyMember.Name).Append(@" > right; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(@" > right; } /// - public static bool operator >(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left > right.").Append(keyMember.Name).Append(@"; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left > right.").Append(state.KeyMember.Name).Append(@"; }"); } @@ -261,23 +258,22 @@ private static void GenerateKeyOverloadsUsingOperators( sb.Append(@" /// - public static bool operator >=(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(keyMember.Name).Append(@" >= right; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return left.").Append(state.KeyMember.Name).Append(@" >= right; } /// - public static bool operator >=(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left >= right.").Append(keyMember.Name).Append(@"; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return left >= right.").Append(state.KeyMember.Name).Append(@"; }"); } } private static void GenerateKeyOverloadsUsingComparer( StringBuilder sb, - ITypeInformation type, - IMemberInformation keyMember, + InterfaceCodeGeneratorState state, string comparerAccessor, string? typeLeftNullCheck, string? memberRightNullCheck, @@ -287,51 +283,51 @@ private static void GenerateKeyOverloadsUsingComparer( sb.Append(@" /// - public static bool operator <(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(@", right) < 0; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(@", right) < 0; } /// - public static bool operator <(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(keyMember.Name).Append(@") < 0; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(state.KeyMember.Name).Append(@") < 0; } /// - public static bool operator <=(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(@", right) <= 0; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(@", right) <= 0; } /// - public static bool operator <=(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator <=(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(keyMember.Name).Append(@") <= 0; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(state.KeyMember.Name).Append(@") <= 0; } /// - public static bool operator >(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(@", right) > 0; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(@", right) > 0; } /// - public static bool operator >(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(keyMember.Name).Append(@") > 0; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(state.KeyMember.Name).Append(@") > 0; } /// - public static bool operator >=(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(keyMember.Name).Append(@", right) >= 0; + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left.").Append(state.KeyMember.Name).Append(@", right) >= 0; } /// - public static bool operator >=(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static bool operator >=(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(keyMember.Name).Append(@") >= 0; + ").Append(memberLeftNullCheck).Append(typeRightNullCheck).Append("return ").Append(comparerAccessor).Append(".Comparer.Compare(left, right.").Append(state.KeyMember.Name).Append(@") >= 0; }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs index aa7c1988..4107448d 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs @@ -7,7 +7,7 @@ public sealed class DefaultMemberState : IMemberState, IEquatable _typedMemberState.SpecialType; public string TypeFullyQualified => _typedMemberState.TypeFullyQualified; @@ -19,7 +19,7 @@ public sealed class DefaultMemberState : IMemberState, IEquatable _typedMemberState.IsParsable; public ImplementedComparisonOperators ComparisonOperators => _typedMemberState.ComparisonOperators; - public DefaultMemberState(ITypedMemberState typedMemberState, string name, string argumentName) + public DefaultMemberState(ITypedMemberState typedMemberState, string name, ArgumentName argumentName) { _typedMemberState = typedMemberState; Name = name; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DesiredFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DesiredFactory.cs new file mode 100644 index 00000000..94ed16bf --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DesiredFactory.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Thinktecture.CodeAnalysis; + +public sealed class DesiredFactory : ITypeFullyQualified, IEquatable, IEquatable +{ + public SpecialType SpecialType { get; } + public string TypeFullyQualified { get; } + public SerializationFrameworks UseForSerialization { get; } + + public DesiredFactory(ITypeSymbol type, SerializationFrameworks useForSerialization) + { + SpecialType = type.SpecialType; + TypeFullyQualified = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + UseForSerialization = useForSerialization; + } + + public override bool Equals(object? obj) + { + return Equals(obj as DesiredFactory); + } + + public bool Equals(DesiredFactory? other) + { + return Equals((ITypeFullyQualified?)other) + && UseForSerialization.Equals(other.UseForSerialization); + } + + public bool Equals([NotNullWhen(true)] ITypeFullyQualified? other) + { + if (ReferenceEquals(null, other)) + return false; + + return TypeFullyQualified == other.TypeFullyQualified; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = TypeFullyQualified.GetHashCode(); + hashCode = (hashCode * 397) ^ UseForSerialization.GetHashCode(); + + return hashCode; + } + } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs index bd0a7b1b..be1927ca 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs @@ -47,41 +47,41 @@ private EqualityComparisonOperatorsCodeGenerator( _equalityComparer = equalityComparer; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" - global::System.Numerics.IEqualityOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", bool>"); + global::System.Numerics.IEqualityOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", bool>"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.IEqualityOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", bool>"); + global::System.Numerics.IEqualityOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", bool>"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - GenerateUsingEquals(sb, type); + GenerateUsingEquals(sb, state); if (_withKeyTypeOverloads) - GenerateKeyOverloads(sb, type, keyMember); + GenerateKeyOverloads(sb, state); } - private static void GenerateUsingEquals(StringBuilder sb, ITypeInformation type) + private static void GenerateUsingEquals(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" /// - /// Compares two instances of . + /// Compares two instances of . /// /// Instance to compare. /// Another instance to compare. /// true if objects are equal; otherwise false. - public static bool operator ==(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" other) + public static bool operator ==(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" other) {"); - if (type.IsReferenceType) + if (state.Type.IsReferenceType) { - if (type.IsEqualWithReferenceEquality) + if (state.Type.IsEqualWithReferenceEquality) { sb.Append(@" return global::System.Object.ReferenceEquals(obj, other);"); @@ -105,12 +105,12 @@ private static void GenerateUsingEquals(StringBuilder sb, ITypeInformation type) } /// - /// Compares two instances of . + /// Compares two instances of . /// /// Instance to compare. /// Another instance to compare. /// false if objects are equal; otherwise true. - public static bool operator !=(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" other) + public static bool operator !=(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" other) { return !(obj == other); }"); @@ -118,17 +118,16 @@ private static void GenerateUsingEquals(StringBuilder sb, ITypeInformation type) private void GenerateKeyOverloads( StringBuilder sb, - ITypeInformation type, - IMemberInformation keyMember) + InterfaceCodeGeneratorState state) { sb.Append(@" - private static bool Equals(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + private static bool Equals(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(state.KeyMember.TypeFullyQualified).Append(@" value) {"); - if (type.IsReferenceType) + if (state.Type.IsReferenceType) { - if (keyMember.IsReferenceType) + if (state.KeyMember.IsReferenceType) { sb.Append(@" if (obj is null) @@ -149,13 +148,13 @@ private static bool Equals(").Append(type.TypeFullyQualifiedNullAnnotated).Appen if (_equalityComparer == null) { - if (keyMember.IsReferenceType) + if (state.KeyMember.IsReferenceType) { - sb.Append("obj.").Append(keyMember.Name).Append(" is null ? value").Append(" is null : obj.").Append(keyMember.Name).Append(".Equals(value").Append(")"); + sb.Append("obj.").Append(state.KeyMember.Name).Append(" is null ? value").Append(" is null : obj.").Append(state.KeyMember.Name).Append(".Equals(value").Append(")"); } else { - sb.Append("obj.").Append(keyMember.Name).Append(".Equals(value)"); + sb.Append("obj.").Append(state.KeyMember.Name).Append(".Equals(value)"); } } else @@ -165,52 +164,52 @@ private static bool Equals(").Append(type.TypeFullyQualifiedNullAnnotated).Appen if (_equalityComparer.Value.IsAccessor) sb.Append(".EqualityComparer"); - sb.Append(".Equals(obj.").Append(keyMember.Name).Append(", value)"); + sb.Append(".Equals(obj.").Append(state.KeyMember.Name).Append(", value)"); } sb.Append(@"; } /// - /// Compares an instance of with . + /// Compares an instance of with . /// /// Instance to compare. /// Value to compare with. /// true if objects are equal; otherwise false. - public static bool operator ==(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + public static bool operator ==(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(state.KeyMember.TypeFullyQualified).Append(@" value) { return Equals(obj, value); } /// - /// Compares an instance of with . + /// Compares an instance of with . /// /// Value to compare. /// Instance to compare with. /// true if objects are equal; otherwise false. - public static bool operator ==(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj) + public static bool operator ==(").Append(state.KeyMember.TypeFullyQualified).Append(" value, ").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" obj) { return Equals(obj, value); } /// - /// Compares an instance of with . + /// Compares an instance of with . /// /// Instance to compare. /// Value to compare with. /// false if objects are equal; otherwise true. - public static bool operator !=(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + public static bool operator !=(").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(state.KeyMember.TypeFullyQualified).Append(@" value) { return !(obj == value); } /// - /// Compares an instance of with . + /// Compares an instance of with . /// /// Value to compare. /// Instance to compare with. /// false if objects are equal; otherwise true. - public static bool operator !=(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj) + public static bool operator !=(").Append(state.KeyMember.TypeFullyQualified).Append(" value, ").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" obj) { return !(obj == value); }"); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/FormattableCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/FormattableCodeGenerator.cs index 9b4de7c0..b432b626 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/FormattableCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/FormattableCodeGenerator.cs @@ -9,19 +9,19 @@ public sealed class FormattableCodeGenerator : IInterfaceCodeGenerator public string CodeGeneratorName => "Formattable-CodeGenerator"; public string FileNameSuffix => ".Formattable"; - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" global::System.IFormattable"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { sb.Append(@" /// public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) { - return this.").Append(keyMember.Name).Append(@".ToString(format, formatProvider); + return this.").Append(state.KeyMember.Name).Append(@".ToString(format, formatProvider); }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IComplexSerializerCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IComplexSerializerCodeGeneratorFactory.cs new file mode 100644 index 00000000..70be0537 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IComplexSerializerCodeGeneratorFactory.cs @@ -0,0 +1,8 @@ +using Thinktecture.CodeAnalysis.ValueObjects; + +namespace Thinktecture.CodeAnalysis; + +public interface IComplexSerializerCodeGeneratorFactory : ICodeGeneratorFactory, IEquatable +{ + bool MustGenerateCode(ComplexSerializerGeneratorState state); +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IInterfaceCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IInterfaceCodeGenerator.cs index 0f60ad11..0a80b911 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IInterfaceCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IInterfaceCodeGenerator.cs @@ -2,11 +2,15 @@ namespace Thinktecture.CodeAnalysis; -public interface IInterfaceCodeGenerator +public interface IInterfaceCodeGenerator : IInterfaceCodeGenerator +{ +} + +public interface IInterfaceCodeGenerator { string CodeGeneratorName { get; } string FileNameSuffix { get; } - void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember); - void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember); + void GenerateBaseTypes(StringBuilder sb, TState state); + void GenerateImplementation(StringBuilder sb, TState state); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IKeyedSerializerCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IKeyedSerializerCodeGeneratorFactory.cs index 9e501455..3e4cfcec 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IKeyedSerializerCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IKeyedSerializerCodeGeneratorFactory.cs @@ -2,5 +2,5 @@ namespace Thinktecture.CodeAnalysis; public interface IKeyedSerializerCodeGeneratorFactory : ICodeGeneratorFactory, IEquatable { - bool MustGenerateCode(AttributeInfo attributeInfo); + bool MustGenerateCode(KeyedSerializerGeneratorState state); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IMemberState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IMemberState.cs index 4cb73bde..58d1082b 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IMemberState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/IMemberState.cs @@ -2,7 +2,7 @@ namespace Thinktecture.CodeAnalysis; public interface IMemberState : IEquatable, IMemberInformation { - string ArgumentName { get; } + ArgumentName ArgumentName { get; } string TypeFullyQualifiedNullAnnotated { get; } string TypeFullyQualifiedWithNullability { get; } bool IsFormattable { get; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformationProvider.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformationProvider.cs new file mode 100644 index 00000000..605b1a55 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformationProvider.cs @@ -0,0 +1,6 @@ +namespace Thinktecture.CodeAnalysis; + +public interface ITypeInformationProvider +{ + ITypeInformation Type { get; } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs index ae3d8982..dd94e93f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs @@ -8,8 +8,8 @@ public sealed class InstanceMemberInfo : IMemberState, IEquatable _argumentName ??= Name.MakeArgumentName(); + private ArgumentName? _argumentName; + public ArgumentName ArgumentName => _argumentName ??= Name.MakeArgumentName(); public string Name { get; } public bool IsStatic { get; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorFactory.cs index 5fc4961d..5dc1572a 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorFactory.cs @@ -3,45 +3,49 @@ namespace Thinktecture.CodeAnalysis; -public class InterfaceCodeGeneratorFactory : ICodeGeneratorFactory<(ITypeInformation Type, IMemberInformation KeyMember)> +public class InterfaceCodeGeneratorFactory : ICodeGeneratorFactory + where T : ITypeInformationProvider { - private static readonly InterfaceCodeGeneratorFactory _parsable = new(ParsableCodeGenerator.Default); - private static readonly InterfaceCodeGeneratorFactory _parsableForValidatableEnum = new(ParsableCodeGenerator.ForValidatableEnum); - private static readonly InterfaceCodeGeneratorFactory _comparable = new(ComparableCodeGenerator.Default); + private readonly IInterfaceCodeGenerator _interfaceCodeGenerator; - public static readonly InterfaceCodeGeneratorFactory Formattable = new(FormattableCodeGenerator.Instance); + public string CodeGeneratorName => _interfaceCodeGenerator.CodeGeneratorName; - public static InterfaceCodeGeneratorFactory Comparable(string? comparerAccessor) + public InterfaceCodeGeneratorFactory(IInterfaceCodeGenerator interfaceCodeGenerator) { - return String.IsNullOrWhiteSpace(comparerAccessor) ? _comparable : new InterfaceCodeGeneratorFactory(new ComparableCodeGenerator(comparerAccessor)); + _interfaceCodeGenerator = interfaceCodeGenerator; } - public static InterfaceCodeGeneratorFactory Parsable(bool forValidatableEnum) + public CodeGeneratorBase Create(T state, StringBuilder stringBuilder) { - return forValidatableEnum ? _parsableForValidatableEnum : _parsable; + return new InterfaceCodeGenerator(_interfaceCodeGenerator, state, stringBuilder); } - public static InterfaceCodeGeneratorFactory Create(IInterfaceCodeGenerator codeGenerator) + public bool Equals(ICodeGeneratorFactory other) { - return new InterfaceCodeGeneratorFactory(codeGenerator); + return ReferenceEquals(this, other); } +} - private readonly IInterfaceCodeGenerator _interfaceCodeGenerator; +public static class InterfaceCodeGeneratorFactory +{ + private static readonly ICodeGeneratorFactory _parsable = new InterfaceCodeGeneratorFactory(ParsableCodeGenerator.Default); + private static readonly ICodeGeneratorFactory _parsableForValidatableEnum = new InterfaceCodeGeneratorFactory(ParsableCodeGenerator.ForValidatableEnum); + private static readonly ICodeGeneratorFactory _comparable = new InterfaceCodeGeneratorFactory(ComparableCodeGenerator.Default); - public string CodeGeneratorName => _interfaceCodeGenerator.CodeGeneratorName; + public static readonly ICodeGeneratorFactory Formattable = new InterfaceCodeGeneratorFactory(FormattableCodeGenerator.Instance); - private InterfaceCodeGeneratorFactory(IInterfaceCodeGenerator interfaceCodeGenerator) + public static ICodeGeneratorFactory Comparable(string? comparerAccessor) { - _interfaceCodeGenerator = interfaceCodeGenerator; + return String.IsNullOrWhiteSpace(comparerAccessor) ? _comparable : new InterfaceCodeGeneratorFactory(new ComparableCodeGenerator(comparerAccessor)); } - public CodeGeneratorBase Create((ITypeInformation Type, IMemberInformation KeyMember) state, StringBuilder stringBuilder) + public static ICodeGeneratorFactory Parsable(bool forValidatableEnum) { - return new InterfaceCodeGenerator(_interfaceCodeGenerator, state.Type, state.KeyMember, stringBuilder); + return forValidatableEnum ? _parsableForValidatableEnum : _parsable; } - public bool Equals(ICodeGeneratorFactory<(ITypeInformation Type, IMemberInformation KeyMember)> other) + public static ICodeGeneratorFactory Create(IInterfaceCodeGenerator codeGenerator) { - return ReferenceEquals(this, other); + return new InterfaceCodeGeneratorFactory(codeGenerator); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorState.cs new file mode 100644 index 00000000..3267c3b5 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InterfaceCodeGeneratorState.cs @@ -0,0 +1,37 @@ +namespace Thinktecture.CodeAnalysis; + +public readonly struct InterfaceCodeGeneratorState : IEquatable, ITypeInformationProvider +{ + public ITypeInformation Type { get; } + public IMemberInformation KeyMember { get; } + + public InterfaceCodeGeneratorState( + ITypeInformation type, + IMemberInformation keyMember) + { + Type = type; + KeyMember = keyMember; + } + + public override bool Equals(object? obj) + { + return obj is InterfaceCodeGeneratorState state && Equals(state); + } + + public bool Equals(InterfaceCodeGeneratorState other) + { + return Type.Equals(other.Type) + && KeyMember.Equals(other.KeyMember); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Type.GetHashCode(); + hashCode = (hashCode * 397) ^ KeyMember.GetHashCode(); + + return hashCode; + } + } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedJsonCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedJsonCodeGenerator.cs index 9176535c..30b867f5 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedJsonCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedJsonCodeGenerator.cs @@ -4,35 +4,38 @@ namespace Thinktecture.CodeAnalysis; public sealed class KeyedJsonCodeGenerator : CodeGeneratorBase { - private readonly ITypeInformation _type; - private readonly ITypeFullyQualified _keyMember; + private readonly KeyedSerializerGeneratorState _state; private readonly StringBuilder _sb; public override string CodeGeneratorName => "Keyed-SystemTextJson-CodeGenerator"; public override string FileNameSuffix => ".Json"; - public KeyedJsonCodeGenerator(ITypeInformation type, ITypeFullyQualified keyMember, StringBuilder stringBuilder) + public KeyedJsonCodeGenerator(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - _type = type; - _keyMember = keyMember; + _state = state; _sb = stringBuilder; } public override void Generate(CancellationToken cancellationToken) { + var customFactory = _state.AttributeInfo + .DesiredFactories + .FirstOrDefault(f => f.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)); + var keyType = customFactory?.TypeFullyQualified ?? _state.KeyMember?.TypeFullyQualified; + _sb.Append(GENERATED_CODE_PREFIX).Append(@" "); - if (_type.Namespace is not null) + if (_state.Type.Namespace is not null) { _sb.Append(@" -namespace ").Append(_type.Namespace).Append(@"; +namespace ").Append(_state.Type.Namespace).Append(@"; "); } _sb.Append(@" -[global::System.Text.Json.Serialization.JsonConverterAttribute(typeof(global::Thinktecture.Text.Json.Serialization.ValueObjectJsonConverterFactory<").Append(_type.TypeFullyQualified).Append(", ").Append(_keyMember.TypeFullyQualified).Append(@">))] -partial ").Append(_type.IsReferenceType ? "class" : "struct").Append(" ").Append(_type.Name).Append(@" +[global::System.Text.Json.Serialization.JsonConverterAttribute(typeof(global::Thinktecture.Text.Json.Serialization.ValueObjectJsonConverterFactory<").Append(_state.Type.TypeFullyQualified).Append(", ").Append(keyType).Append(@">))] +partial ").Append(_state.Type.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Type.Name).Append(@" { } "); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedMessagePackCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedMessagePackCodeGenerator.cs index aca66301..118fa869 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedMessagePackCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedMessagePackCodeGenerator.cs @@ -4,35 +4,38 @@ namespace Thinktecture.CodeAnalysis; public sealed class KeyedMessagePackCodeGenerator : CodeGeneratorBase { - private readonly ITypeInformation _type; - private readonly ITypeFullyQualified _keyMember; + private readonly KeyedSerializerGeneratorState _state; private readonly StringBuilder _sb; public override string CodeGeneratorName => "Keyed-MessagePack-CodeGenerator"; public override string FileNameSuffix => ".MessagePack"; - public KeyedMessagePackCodeGenerator(ITypeInformation type, ITypeFullyQualified keyMember, StringBuilder stringBuilder) + public KeyedMessagePackCodeGenerator(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - _type = type; - _keyMember = keyMember; + _state = state; _sb = stringBuilder; } public override void Generate(CancellationToken cancellationToken) { + var customFactory = _state.AttributeInfo + .DesiredFactories + .FirstOrDefault(f => f.UseForSerialization.HasFlag(SerializationFrameworks.MessagePack)); + var keyType = customFactory?.TypeFullyQualified ?? _state.KeyMember?.TypeFullyQualified; + _sb.Append(GENERATED_CODE_PREFIX).Append(@" "); - if (_type.Namespace is not null) + if (_state.Type.Namespace is not null) { _sb.Append(@" -namespace ").Append(_type.Namespace).Append(@"; +namespace ").Append(_state.Type.Namespace).Append(@"; "); } _sb.Append(@" -[global::MessagePack.MessagePackFormatter(typeof(global::Thinktecture.Formatters.").Append(_type.IsReferenceType ? "ValueObjectMessagePackFormatter" : "StructValueObjectMessagePackFormatter").Append("<").Append(_type.TypeFullyQualified).Append(", ").Append(_keyMember.TypeFullyQualified).Append(@">))] -partial ").Append(_type.IsReferenceType ? "class" : "struct").Append(" ").Append(_type.Name).Append(@" +[global::MessagePack.MessagePackFormatter(typeof(global::Thinktecture.Formatters.").Append(_state.Type.IsReferenceType ? "ValueObjectMessagePackFormatter" : "StructValueObjectMessagePackFormatter").Append("<").Append(_state.Type.TypeFullyQualified).Append(", ").Append(keyType).Append(@">))] +partial ").Append(_state.Type.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Type.Name).Append(@" { } "); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedNewtonsoftJsonCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedNewtonsoftJsonCodeGenerator.cs index b80278da..e0ae0103 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedNewtonsoftJsonCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedNewtonsoftJsonCodeGenerator.cs @@ -4,35 +4,38 @@ namespace Thinktecture.CodeAnalysis; public sealed class KeyedNewtonsoftJsonCodeGenerator : CodeGeneratorBase { - private readonly ITypeInformation _type; - private readonly ITypeFullyQualified _keyMember; + private readonly KeyedSerializerGeneratorState _state; private readonly StringBuilder _sb; public override string CodeGeneratorName => "Keyed-NewtonsoftJson-CodeGenerator"; public override string FileNameSuffix => ".NewtonsoftJson"; - public KeyedNewtonsoftJsonCodeGenerator(ITypeInformation type, ITypeFullyQualified keyMember, StringBuilder stringBuilder) + public KeyedNewtonsoftJsonCodeGenerator(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - _type = type; - _keyMember = keyMember; + _state = state; _sb = stringBuilder; } public override void Generate(CancellationToken cancellationToken) { + var customFactory = _state.AttributeInfo + .DesiredFactories + .FirstOrDefault(f => f.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson)); + var keyType = customFactory?.TypeFullyQualified ?? _state.KeyMember?.TypeFullyQualified; + _sb.Append(GENERATED_CODE_PREFIX).Append(@" "); - if (_type.Namespace is not null) + if (_state.Type.Namespace is not null) { _sb.Append(@" -namespace ").Append(_type.Namespace).Append(@"; +namespace ").Append(_state.Type.Namespace).Append(@"; "); } _sb.Append(@" -[global::Newtonsoft.Json.JsonConverterAttribute(typeof(global::Thinktecture.Json.ValueObjectNewtonsoftJsonConverter<").Append(_type.TypeFullyQualified).Append(", ").Append(_keyMember.TypeFullyQualified).Append(@">))] -partial ").Append(_type.IsReferenceType ? "class" : "struct").Append(" ").Append(_type.Name).Append(@" +[global::Newtonsoft.Json.JsonConverterAttribute(typeof(global::Thinktecture.Json.ValueObjectNewtonsoftJsonConverter<").Append(_state.Type.TypeFullyQualified).Append(", ").Append(keyType).Append(@">))] +partial ").Append(_state.Type.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Type.Name).Append(@" { } "); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedSerializerGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedSerializerGeneratorState.cs index 8f88155e..6c85feb4 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedSerializerGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/KeyedSerializerGeneratorState.cs @@ -3,13 +3,16 @@ namespace Thinktecture.CodeAnalysis; public readonly struct KeyedSerializerGeneratorState : IEquatable, INamespaceAndName { public ITypeInformation Type { get; } - public ITypeFullyQualified KeyMember { get; } + public ITypeFullyQualified? KeyMember { get; } public AttributeInfo AttributeInfo { get; } public string? Namespace => Type.Namespace; public string Name => Type.Name; - public KeyedSerializerGeneratorState(ITypeInformation type, ITypeFullyQualified keyMember, AttributeInfo attributeInfo) + public KeyedSerializerGeneratorState( + ITypeInformation type, + ITypeFullyQualified? keyMember, + AttributeInfo attributeInfo) { Type = type; KeyMember = keyMember; @@ -19,7 +22,7 @@ public KeyedSerializerGeneratorState(ITypeInformation type, ITypeFullyQualified public bool Equals(KeyedSerializerGeneratorState other) { return TypeInformationComparer.Instance.Equals(Type, other.Type) - && KeyMember.TypeFullyQualified == other.KeyMember.TypeFullyQualified + && KeyMember?.TypeFullyQualified == other.KeyMember?.TypeFullyQualified && AttributeInfo.Equals(other.AttributeInfo); } @@ -33,7 +36,7 @@ public override int GetHashCode() unchecked { var hashCode = TypeInformationComparer.Instance.GetHashCode(Type); - hashCode = (hashCode * 397) ^ KeyMember.TypeFullyQualified.GetHashCode(); + hashCode = (hashCode * 397) ^ (KeyMember is null ? 0 : KeyMember.TypeFullyQualified.GetHashCode()); hashCode = (hashCode * 397) ^ AttributeInfo.GetHashCode(); return hashCode; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/MemberInformationComparer.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/MemberInformationComparer.cs index f8af28ff..3b12a4bc 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/MemberInformationComparer.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/MemberInformationComparer.cs @@ -2,7 +2,7 @@ namespace Thinktecture.CodeAnalysis; public sealed class MemberInformationComparer : IEqualityComparer { - public static readonly IEqualityComparer Instance = new MemberInformationComparer(); + public static readonly MemberInformationComparer Instance = new(); public bool Equals(IMemberInformation? x, IMemberInformation? y) { diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableCodeGenerator.cs index af407587..6097e377 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableCodeGenerator.cs @@ -2,10 +2,10 @@ namespace Thinktecture.CodeAnalysis; -public sealed class ParsableCodeGenerator : IInterfaceCodeGenerator +public sealed class ParsableCodeGenerator : IInterfaceCodeGenerator { - public static readonly IInterfaceCodeGenerator Default = new ParsableCodeGenerator(false); - public static readonly IInterfaceCodeGenerator ForValidatableEnum = new ParsableCodeGenerator(true); + public static readonly IInterfaceCodeGenerator Default = new ParsableCodeGenerator(false); + public static readonly IInterfaceCodeGenerator ForValidatableEnum = new ParsableCodeGenerator(true); private readonly bool _isForValidatableEnum; @@ -17,36 +17,48 @@ private ParsableCodeGenerator(bool isForValidatableEnum) _isForValidatableEnum = isForValidatableEnum; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, ParsableGeneratorState state) { sb.Append(@" - global::System.IParsable<").Append(type.TypeFullyQualified).Append(">"); + global::System.IParsable<").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, ParsableGeneratorState state) { - GenerateParse(sb, type, keyMember); + GenerateValidate(sb, state); + GenerateParse(sb, state); + GenerateTryParse(sb, state); + } - GenerateTryParse(sb, type, keyMember); + private static void GenerateValidate(StringBuilder sb, ParsableGeneratorState state) + { + var keyType = state.KeyMember?.IsString() == true || state.HasStringBasedValidateMethod ? "string" : state.KeyMember?.TypeFullyQualified; + sb.Append(@" + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(").Append(keyType).Append(" key, global::System.IFormatProvider? provider, out ").Append(state.Type.TypeFullyQualifiedNullAnnotated).Append(@" result) + where T : global::Thinktecture.IValueObjectFactory<").Append(state.Type.TypeFullyQualified).Append(", ").Append(keyType).Append(@"> + { + return T.Validate(key, provider, out result); + }"); } - private void GenerateParse(StringBuilder sb, ITypeInformation type, IMemberInformation member) + private void GenerateParse(StringBuilder sb, ParsableGeneratorState state) { sb.Append(@" + /// - public static ").Append(type.TypeFullyQualified).Append(@" Parse(string s, global::System.IFormatProvider? provider) + public static ").Append(state.Type.TypeFullyQualified).Append(@" Parse(string s, global::System.IFormatProvider? provider) {"); - if (member.IsString()) + if (state.KeyMember?.IsString() == true || state.HasStringBasedValidateMethod) { sb.Append(@" - var validationResult = ").Append(type.TypeFullyQualified).Append(".Validate(s, out var result);"); + var validationResult = Validate<").Append(state.Type.TypeFullyQualified).Append(">(s, provider, out var result);"); } - else + else if (state.KeyMember is not null) { sb.Append(@" - var key = ").Append(member.TypeFullyQualified).Append(@".Parse(s, provider); - var validationResult = ").Append(type.TypeFullyQualified).Append(".Validate(key, out var result);"); + var key = ").Append(state.KeyMember.TypeFullyQualified).Append(@".Parse(s, provider); + var validationResult = Validate<").Append(state.Type.TypeFullyQualified).Append(">(key, provider, out var result);"); } if (_isForValidatableEnum) @@ -67,7 +79,7 @@ private void GenerateParse(StringBuilder sb, ITypeInformation type, IMemberInfor } } - private void GenerateTryParse(StringBuilder sb, ITypeInformation type, IMemberInformation member) + private void GenerateTryParse(StringBuilder sb, ParsableGeneratorState state) { sb.Append(@" @@ -75,7 +87,7 @@ private void GenerateTryParse(StringBuilder sb, ITypeInformation type, IMemberIn public static bool TryParse( string? s, global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").Append(type.TypeFullyQualified).Append(@" result) + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").Append(state.Type.TypeFullyQualified).Append(@" result) { if(s is null) { @@ -83,23 +95,23 @@ public static bool TryParse( return false; }"); - if (member.IsString()) + if (state.KeyMember?.IsString() == true || state.HasStringBasedValidateMethod) { sb.Append(@" - var validationResult = ").Append(type.TypeFullyQualified).Append(".Validate(s, out result!);"); + var validationResult = Validate<").Append(state.Type.TypeFullyQualified).Append(">(s, provider, out result!);"); } - else + else if (state.KeyMember is not null) { sb.Append(@" - if(!").Append(member.TypeFullyQualified).Append(@".TryParse(s, provider, out var key)) + if(!").Append(state.KeyMember.TypeFullyQualified).Append(@".TryParse(s, provider, out var key)) { result = default; return false; } - var validationResult = ").Append(type.TypeFullyQualified).Append(".Validate(key, out result!);"); + var validationResult = Validate<").Append(state.Type.TypeFullyQualified).Append(">(key, provider, out result!);"); } if (_isForValidatableEnum) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableGeneratorState.cs index b829a342..8e33fbad 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ParsableGeneratorState.cs @@ -1,25 +1,28 @@ namespace Thinktecture.CodeAnalysis; -public readonly struct ParsableGeneratorState : IEquatable +public readonly struct ParsableGeneratorState : IEquatable, ITypeInformationProvider { public ITypeInformation Type { get; } - public IMemberInformation KeyMember { get; } + public IMemberInformation? KeyMember { get; } public bool SkipIParsable { get; } public bool IsKeyMemberParsable { get; } public bool IsValidatableEnum { get; } + public bool HasStringBasedValidateMethod { get; } public ParsableGeneratorState( ITypeInformation type, - IMemberInformation keyMember, + IMemberInformation? keyMember, bool skipIParsable, bool isKeyMemberParsable, - bool isValidatableEnum) + bool isValidatableEnum, + bool hasStringBasedValidateMethod) { Type = type; KeyMember = keyMember; SkipIParsable = skipIParsable; IsKeyMemberParsable = isKeyMemberParsable; IsValidatableEnum = isValidatableEnum; + HasStringBasedValidateMethod = hasStringBasedValidateMethod; } public bool Equals(ParsableGeneratorState other) @@ -28,7 +31,8 @@ public bool Equals(ParsableGeneratorState other) && MemberInformationComparer.Instance.Equals(KeyMember, other.KeyMember) && SkipIParsable == other.SkipIParsable && IsKeyMemberParsable == other.IsKeyMemberParsable - && IsValidatableEnum == other.IsValidatableEnum; + && IsValidatableEnum == other.IsValidatableEnum + && HasStringBasedValidateMethod == other.HasStringBasedValidateMethod; } public override bool Equals(object? obj) @@ -41,10 +45,11 @@ public override int GetHashCode() unchecked { var hashCode = TypeInformationComparer.Instance.GetHashCode(Type); - hashCode = (hashCode * 397) ^ MemberInformationComparer.Instance.GetHashCode(KeyMember); + hashCode = (hashCode * 397) ^ (KeyMember is null ? 0 : MemberInformationComparer.Instance.GetHashCode(KeyMember)); hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode(); hashCode = (hashCode * 397) ^ IsKeyMemberParsable.GetHashCode(); hashCode = (hashCode * 397) ^ IsValidatableEnum.GetHashCode(); + hashCode = (hashCode * 397) ^ HasStringBasedValidateMethod.GetHashCode(); return hashCode; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SerializationFrameworks.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SerializationFrameworks.cs new file mode 100644 index 00000000..4289939d --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SerializationFrameworks.cs @@ -0,0 +1,12 @@ +namespace Thinktecture.CodeAnalysis; + +[Flags] +public enum SerializationFrameworks +{ + None = 0, + SystemTextJson = 1 << 0, + NewtonsoftJson = 1 << 1, + Json = SystemTextJson | NewtonsoftJson, + MessagePack = 1 << 2, + All = Json | MessagePack +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/AllEnumSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/AllEnumSettings.cs new file mode 100644 index 00000000..9215745f --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/AllEnumSettings.cs @@ -0,0 +1,78 @@ +using Microsoft.CodeAnalysis; + +namespace Thinktecture.CodeAnalysis.SmartEnums; + +public sealed class AllEnumSettings : IEquatable +{ + public string? KeyPropertyName { get; } + public bool IsValidatable { get; } + public bool SkipIComparable { get; } + public bool SkipIParsable { get; } + public OperatorsGeneration ComparisonOperators { get; } + public OperatorsGeneration EqualityComparisonOperators { get; } + public bool SkipIFormattable { get; } + public bool SkipToString { get; } + public bool SkipSwitchMethods { get; } + public bool SkipMapMethods { get; } + + public AllEnumSettings(AttributeData? attribute) + { + KeyPropertyName = attribute?.FindKeyPropertyName().TrimAndNullify(); + IsValidatable = attribute?.FindIsValidatable() ?? false; + SkipIComparable = attribute?.FindSkipIComparable() ?? false; + SkipIParsable = attribute?.FindSkipIParsable() ?? false; + ComparisonOperators = attribute?.FindComparisonOperators() ?? OperatorsGeneration.Default; + EqualityComparisonOperators = attribute?.FindEqualityComparisonOperators() ?? OperatorsGeneration.Default; + SkipIFormattable = attribute?.FindSkipIFormattable() ?? false; + SkipToString = attribute?.FindSkipToString() ?? false; + SkipSwitchMethods = attribute?.FindSkipSwitchMethods() ?? false; + SkipMapMethods = attribute?.FindSkipMapMethods() ?? false; + + // Comparison operators depend on the equality comparison operators + if (ComparisonOperators > EqualityComparisonOperators) + EqualityComparisonOperators = ComparisonOperators; + } + + public override bool Equals(object? obj) + { + return obj is AllEnumSettings enumSettings && Equals(enumSettings); + } + + public bool Equals(AllEnumSettings? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + + return KeyPropertyName == other.KeyPropertyName + && IsValidatable == other.IsValidatable + && SkipIComparable == other.SkipIComparable + && SkipIParsable == other.SkipIParsable + && ComparisonOperators == other.ComparisonOperators + && EqualityComparisonOperators == other.EqualityComparisonOperators + && SkipIFormattable == other.SkipIFormattable + && SkipToString == other.SkipToString + && SkipSwitchMethods == other.SkipSwitchMethods + && SkipMapMethods == other.SkipMapMethods; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = KeyPropertyName?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ IsValidatable.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipIComparable.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode(); + hashCode = (hashCode * 397) ^ ComparisonOperators.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipIFormattable.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipToString.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipSwitchMethods.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipMapMethods.GetHashCode(); + + return hashCode; + } + } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs index 4817effb..d7711d4f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs @@ -1,36 +1,21 @@ -using Microsoft.CodeAnalysis; - namespace Thinktecture.CodeAnalysis.SmartEnums; -public sealed class EnumSettings : IEquatable +public readonly struct EnumSettings : IEquatable { - public string? KeyPropertyName { get; } - public bool IsValidatable { get; } - public bool SkipIComparable { get; } - public bool SkipIParsable { get; } - public OperatorsGeneration ComparisonOperators { get; } - public OperatorsGeneration EqualityComparisonOperators { get; } - public bool SkipIFormattable { get; } - public bool SkipToString { get; } - public bool SkipSwitchMethods { get; } - public bool SkipMapMethods { get; } + private readonly AllEnumSettings _settings; + private readonly AttributeInfo _attributeInfo; - public EnumSettings(AttributeData? attribute) - { - KeyPropertyName = attribute?.FindKeyPropertyName().TrimAndNullify(); - IsValidatable = attribute?.FindIsValidatable() ?? false; - SkipIComparable = attribute?.FindSkipIComparable() ?? false; - SkipIParsable = attribute?.FindSkipIParsable() ?? false; - ComparisonOperators = attribute?.FindComparisonOperators() ?? OperatorsGeneration.Default; - EqualityComparisonOperators = attribute?.FindEqualityComparisonOperators() ?? OperatorsGeneration.Default; - SkipIFormattable = attribute?.FindSkipIFormattable() ?? false; - SkipToString = attribute?.FindSkipToString() ?? false; - SkipSwitchMethods = attribute?.FindSkipSwitchMethods() ?? false; - SkipMapMethods = attribute?.FindSkipMapMethods() ?? false; + public bool IsValidatable => _settings.IsValidatable; + public bool SkipToString => _settings.SkipToString; + public bool SkipSwitchMethods => _settings.SkipSwitchMethods; + public bool SkipMapMethods => _settings.SkipMapMethods; + public bool HasStructLayoutAttribute => _attributeInfo.HasStructLayoutAttribute; + public IReadOnlyList DesiredFactories => _attributeInfo.DesiredFactories; - // Comparison operators depend on the equality comparison operators - if (ComparisonOperators > EqualityComparisonOperators) - EqualityComparisonOperators = ComparisonOperators; + public EnumSettings(AllEnumSettings settings, AttributeInfo attributeInfo) + { + _settings = settings; + _attributeInfo = attributeInfo; } public override bool Equals(object? obj) @@ -38,39 +23,26 @@ public override bool Equals(object? obj) return obj is EnumSettings enumSettings && Equals(enumSettings); } - public bool Equals(EnumSettings? other) + public bool Equals(EnumSettings other) { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - - return KeyPropertyName == other.KeyPropertyName - && IsValidatable == other.IsValidatable - && SkipIComparable == other.SkipIComparable - && SkipIParsable == other.SkipIParsable - && ComparisonOperators == other.ComparisonOperators - && EqualityComparisonOperators == other.EqualityComparisonOperators - && SkipIFormattable == other.SkipIFormattable + return IsValidatable == other.IsValidatable && SkipToString == other.SkipToString && SkipSwitchMethods == other.SkipSwitchMethods - && SkipMapMethods == other.SkipMapMethods; + && SkipMapMethods == other.SkipMapMethods + && HasStructLayoutAttribute == other.HasStructLayoutAttribute + && DesiredFactories.EqualsTo(other.DesiredFactories); } public override int GetHashCode() { unchecked { - var hashCode = KeyPropertyName?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ IsValidatable.GetHashCode(); - hashCode = (hashCode * 397) ^ SkipIComparable.GetHashCode(); - hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode(); - hashCode = (hashCode * 397) ^ ComparisonOperators.GetHashCode(); - hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode(); - hashCode = (hashCode * 397) ^ SkipIFormattable.GetHashCode(); + var hashCode = IsValidatable.GetHashCode(); hashCode = (hashCode * 397) ^ SkipToString.GetHashCode(); hashCode = (hashCode * 397) ^ SkipSwitchMethods.GetHashCode(); hashCode = (hashCode * 397) ^ SkipMapMethods.GetHashCode(); + hashCode = (hashCode * 397) ^ HasStructLayoutAttribute.GetHashCode(); + hashCode = (hashCode * 397) ^ DesiredFactories.ComputeHashCode(); return hashCode; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs index f9733785..48ac90ea 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs @@ -9,23 +9,19 @@ public sealed class EnumSourceGeneratorState : ITypeInformation, IEquatable !IsValidatable; + public bool IsEqualWithReferenceEquality => !Settings.IsValidatable; public IMemberState KeyProperty { get; } - public bool IsValidatable { get; } + public EnumSettings Settings { get; } public BaseTypeState? BaseType { get; } - public bool SkipToString { get; } - public bool SkipSwitchMethods { get; } - public bool SkipMapMethods { get; } public bool HasCreateInvalidItemImplementation { get; } public bool HasKeyComparerImplementation { get; } - public bool HasStructLayoutAttribute { get; } public bool IsReferenceType { get; } public bool IsAbstract { get; } - private string? _argumentName; - public string ArgumentName => _argumentName ??= Name.MakeArgumentName(); + private ArgumentName? _argumentName; + public ArgumentName ArgumentName => _argumentName ??= Name.MakeArgumentName(); public IReadOnlyList ItemNames { get; } public IReadOnlyList AssignableInstanceFieldsAndProperties { get; } @@ -34,21 +30,13 @@ public EnumSourceGeneratorState( TypedMemberStateFactory factory, INamedTypeSymbol type, IMemberState keyProperty, - bool skipToString, - bool skipSwitchMethods, - bool skipMapMethods, - bool isValidatable, + EnumSettings settings, bool hasCreateInvalidItemImplementation, - bool hasStructLayoutAttribute, CancellationToken cancellationToken) { KeyProperty = keyProperty; - SkipToString = skipToString; - SkipSwitchMethods = skipSwitchMethods; - SkipMapMethods = skipMapMethods; - IsValidatable = isValidatable; + Settings = settings; HasCreateInvalidItemImplementation = hasCreateInvalidItemImplementation; - HasStructLayoutAttribute = hasStructLayoutAttribute; Name = type.Name; Namespace = type.ContainingNamespace?.IsGlobalNamespace == true ? null : type.ContainingNamespace?.ToString(); @@ -91,13 +79,12 @@ public bool Equals(EnumSourceGeneratorState? other) return true; return TypeFullyQualified == other.TypeFullyQualified - && IsValidatable == other.IsValidatable && HasCreateInvalidItemImplementation == other.HasCreateInvalidItemImplementation && HasKeyComparerImplementation == other.HasKeyComparerImplementation && IsReferenceType == other.IsReferenceType && IsAbstract == other.IsAbstract - && HasStructLayoutAttribute == other.HasStructLayoutAttribute && KeyProperty.Equals(other.KeyProperty) + && Settings.Equals(other.Settings) && Equals(BaseType, other.BaseType) && ItemNames.EqualsTo(other.ItemNames) && AssignableInstanceFieldsAndProperties.EqualsTo(other.AssignableInstanceFieldsAndProperties); @@ -108,13 +95,12 @@ public override int GetHashCode() unchecked { var hashCode = TypeFullyQualified.GetHashCode(); - hashCode = (hashCode * 397) ^ IsValidatable.GetHashCode(); hashCode = (hashCode * 397) ^ HasCreateInvalidItemImplementation.GetHashCode(); hashCode = (hashCode * 397) ^ HasKeyComparerImplementation.GetHashCode(); hashCode = (hashCode * 397) ^ IsReferenceType.GetHashCode(); hashCode = (hashCode * 397) ^ IsAbstract.GetHashCode(); - hashCode = (hashCode * 397) ^ HasStructLayoutAttribute.GetHashCode(); hashCode = (hashCode * 397) ^ KeyProperty.GetHashCode(); + hashCode = (hashCode * 397) ^ Settings.GetHashCode(); hashCode = (hashCode * 397) ^ (BaseType?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ ItemNames.ComputeHashCode(); hashCode = (hashCode * 397) ^ AssignableInstanceFieldsAndProperties.ComputeHashCode(); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/InterfaceCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/InterfaceCodeGenerator.cs index f1201d54..d5dbd711 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/InterfaceCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/InterfaceCodeGenerator.cs @@ -2,25 +2,23 @@ namespace Thinktecture.CodeAnalysis.SmartEnums; -public class InterfaceCodeGenerator : CodeGeneratorBase +public class InterfaceCodeGenerator : CodeGeneratorBase + where TState : ITypeInformationProvider { - private readonly IInterfaceCodeGenerator _codeGenerator; - private readonly ITypeInformation _type; - private readonly IMemberInformation _keyMember; + private readonly IInterfaceCodeGenerator _codeGenerator; + private readonly TState _state; private readonly StringBuilder _sb; public override string CodeGeneratorName => _codeGenerator.CodeGeneratorName; public override string FileNameSuffix => _codeGenerator.FileNameSuffix; public InterfaceCodeGenerator( - IInterfaceCodeGenerator codeGenerator, - ITypeInformation type, - IMemberInformation keyMember, + IInterfaceCodeGenerator codeGenerator, + TState state, StringBuilder stringBuilder) { _codeGenerator = codeGenerator; - _type = type; - _keyMember = keyMember; + _state = state; _sb = stringBuilder; } @@ -29,22 +27,22 @@ public override void Generate(CancellationToken cancellationToken) _sb.Append(GENERATED_CODE_PREFIX).Append(@" "); - if (_type.Namespace is not null) + if (_state.Type.Namespace is not null) { _sb.Append(@" -namespace ").Append(_type.Namespace).Append(@"; +namespace ").Append(_state.Type.Namespace).Append(@"; "); } _sb.Append(@" -partial ").Append(_type.IsReferenceType ? "class" : "struct").Append(" ").Append(_type.Name).Append(" :"); +partial ").Append(_state.Type.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Type.Name).Append(" :"); - _codeGenerator.GenerateBaseTypes(_sb, _type, _keyMember); + _codeGenerator.GenerateBaseTypes(_sb, _state); _sb.Append(@" {"); - _codeGenerator.GenerateImplementation(_sb, _type, _keyMember); + _codeGenerator.GenerateImplementation(_sb, _state); _sb.Append(@" } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/JsonSmartEnumCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/JsonSmartEnumCodeGeneratorFactory.cs index 5527874d..6c435c10 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/JsonSmartEnumCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/JsonSmartEnumCodeGeneratorFactory.cs @@ -12,14 +12,15 @@ private JsonSmartEnumCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasJsonConverterAttribute; + return !state.AttributeInfo.HasJsonConverterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson))); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedJsonCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedJsonCodeGenerator(state, stringBuilder); } public bool Equals(IKeyedSerializerCodeGeneratorFactory other) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/MessagePackSmartEnumCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/MessagePackSmartEnumCodeGeneratorFactory.cs index e3918e9b..58b7d693 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/MessagePackSmartEnumCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/MessagePackSmartEnumCodeGeneratorFactory.cs @@ -12,14 +12,15 @@ private MessagePackSmartEnumCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasMessagePackFormatterAttribute; + return !state.AttributeInfo.HasMessagePackFormatterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.MessagePack))); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedMessagePackCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedMessagePackCodeGenerator(state, stringBuilder); } public bool Equals(IKeyedSerializerCodeGeneratorFactory other) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/NewtonsoftJsonSmartEnumCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/NewtonsoftJsonSmartEnumCodeGeneratorFactory.cs index 379664ea..b063e989 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/NewtonsoftJsonSmartEnumCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/NewtonsoftJsonSmartEnumCodeGeneratorFactory.cs @@ -12,14 +12,15 @@ private NewtonsoftJsonSmartEnumCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasNewtonsoftJsonConverterAttribute; + return !state.AttributeInfo.HasNewtonsoftJsonConverterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson))); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedNewtonsoftJsonCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedNewtonsoftJsonCodeGenerator(state, stringBuilder); } public bool Equals(IKeyedSerializerCodeGeneratorFactory other) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs index 758c2173..fa6749e5 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs @@ -46,15 +46,32 @@ namespace ").Append(_state.Namespace).Append(@" private void GenerateEnum(CancellationToken cancellationToken) { - var needCreateInvalidItemImplementation = _state is { IsValidatable: true, HasCreateInvalidItemImplementation: false }; + var needCreateInvalidItemImplementation = _state is { Settings.IsValidatable: true, HasCreateInvalidItemImplementation: false }; _sb.GenerateStructLayoutAttributeIfRequired(_state); _sb.Append(@" [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyProperty.TypeFullyQualified).Append(@">))] - partial ").Append(_state.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Name).Append(" : global::Thinktecture.IEnum<").Append(_state.KeyProperty.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(">,"); + partial ").Append(_state.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Name).Append(" : global::Thinktecture.IEnum<").Append(_state.KeyProperty.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(@">, + global::Thinktecture.IValueObjectFactory<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyProperty.TypeFullyQualified).Append(@">, + global::Thinktecture.IValueObjectConverter<").Append(_state.KeyProperty.TypeFullyQualified).Append(">,"); - if (_state.IsValidatable) + foreach (var desiredFactory in _state.Settings.DesiredFactories) + { + if (desiredFactory.Equals(_state.KeyProperty)) + continue; + + _sb.Append(@" + global::Thinktecture.IValueObjectFactory<").Append(_state.TypeFullyQualified).Append(", ").Append(desiredFactory.TypeFullyQualified).Append(">,"); + + if (desiredFactory.UseForSerialization != SerializationFrameworks.None) + { + _sb.Append(@" + global::Thinktecture.IValueObjectConverter<").Append(desiredFactory.TypeFullyQualified).Append(">,"); + } + } + + if (_state.Settings.IsValidatable) { _sb.Append(@" global::Thinktecture.IValidatableEnum,"); @@ -102,7 +119,7 @@ private void GenerateEnum(CancellationToken cancellationToken) /// public ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.Name).Append(" { get; }"); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" @@ -119,10 +136,10 @@ private void GenerateEnum(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); GenerateConstructors(); - GenerateGetKey(); + GenerateToValue(); GenerateGet(); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) GenerateCreateAndCheckInvalidItem(needCreateInvalidItemImplementation); if (needCreateInvalidItemImplementation && !_state.IsAbstract) @@ -152,10 +169,10 @@ public override int GetHashCode() return _hashCode; }"); - if (!_state.SkipToString) + if (!_state.Settings.SkipToString) GenerateToString(); - if (!_state.SkipSwitchMethods) + if (!_state.Settings.SkipSwitchMethods) { GenerateSwitchForAction(false); GenerateSwitchForAction(true); @@ -163,7 +180,7 @@ public override int GetHashCode() GenerateSwitchForFunc(true); } - if (!_state.SkipMapMethods) + if (!_state.Settings.SkipMapMethods) GenerateMap(); GenerateGetLookup(); @@ -194,7 +211,7 @@ private void GenerateSwitchForAction(bool withContext) /// Executes an action depending on the current item. /// "); - var itemNamePrefix = _state.ArgumentName; + var itemNamePrefix = _state.ArgumentName.Raw; if (withContext) { @@ -282,7 +299,7 @@ private void GenerateSwitchForFunc(bool withContext) /// Context to be passed to the callbacks."); } - var itemNamePrefix = _state.ArgumentName; + var itemNamePrefix = _state.ArgumentName.Raw; for (var i = 0; i < _state.ItemNames.Count; i++) { @@ -358,7 +375,7 @@ private void GenerateMap() /// Maps an item to an instance of type . /// "); - var itemNamePrefix = _state.ArgumentName; + var itemNamePrefix = _state.ArgumentName.Raw; for (var i = 0; i < _state.ItemNames.Count; i++) { @@ -414,13 +431,13 @@ private void GenerateModuleInitializer(IMemberState keyMember) internal static void ModuleInit() { var convertFromKey = new global::System.Func<").Append(keyMember.TypeFullyQualifiedNullAnnotated).Append(", ").Append(enumTypeNullAnnotated).Append(">(").Append(enumType).Append(@".Get); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static ").Append(keyMember.ArgumentName).Append(" => ").Append(enumType).Append(".Get(").Append(keyMember.ArgumentName).Append(@"); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static ").Append(keyMember.ArgumentName.Escaped).Append(" => ").Append(enumType).Append(".Get(").Append(keyMember.ArgumentName.Escaped).Append(@"); var convertToKey = new global::System.Func<").Append(enumType).Append(", ").Append(keyMember.TypeFullyQualified).Append(">(static item => item.").Append(keyMember.Name).Append(@"); global::System.Linq.Expressions.Expression> convertToKeyExpression = static item => item.").Append(keyMember.Name).Append(@"; var enumType = typeof(").Append(enumType).Append(@"); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(").Append(keyMember.TypeFullyQualified).Append("), true, ").Append(_state.IsValidatable ? "true" : "false").Append(@", convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(").Append(keyMember.TypeFullyQualified).Append("), true, ").Append(_state.Settings.IsValidatable ? "true" : "false").Append(@", convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression); global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(enumType, metadata); }"); @@ -431,18 +448,18 @@ private void GenerateTryGet() _sb.Append(@" /// - /// Gets a valid enumeration item for provided if a valid item exists. + /// Gets a valid enumeration item for provided if a valid item exists. /// - /// The identifier to return an enumeration item for. + /// The identifier to return an enumeration item for. /// A valid instance of ; otherwise null. - /// true if a valid item with provided exists; false otherwise. - public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(", [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").Append(_state.TypeFullyQualified).Append(@" item) + /// true if a valid item with provided exists; false otherwise. + public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(", [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").Append(_state.TypeFullyQualified).Append(@" item) {"); if (_state.KeyProperty.IsReferenceType) { _sb.Append(@" - if (").Append(_state.KeyProperty.ArgumentName).Append(@" is null) + if (").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@" is null) { item = default; return false; @@ -450,19 +467,19 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] ") "); } - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" - if(_itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName).Append(@", out item)) + if(_itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@", out item)) return true; - item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName).Append(@"); + item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@"); return false;"); } else { _sb.Append(@" - return _itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName).Append(", out item);"); + return _itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(", out item);"); } _sb.Append(@" @@ -471,40 +488,43 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] ") private void GenerateValidate() { + var providerArgumentName = _state.KeyProperty.ArgumentName.Escaped == "provider" ? "formatProvider" : "provider"; + _sb.Append(@" /// - /// Validates the provided and returns a valid enumeration item if found. + /// Validates the provided and returns a valid enumeration item if found. /// - /// The identifier to return an enumeration item for. + /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. - /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(", [global::System.Diagnostics.CodeAnalysis.MaybeNull] out ").Append(_state.TypeFullyQualified).Append(@" item) + /// if a valid item with provided exists; with an error message otherwise. + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(", global::System.IFormatProvider? ").Append(providerArgumentName).Append(", [global::System.Diagnostics.CodeAnalysis.MaybeNull] out ").Append(_state.TypeFullyQualified).Append(@" item) { - if(").Append(_state.TypeFullyQualified).Append(".TryGet(").Append(_state.KeyProperty.ArgumentName).Append(@", out item)) + if(").Append(_state.TypeFullyQualified).Append(".TryGet(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@", out item)) { return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; } else {"); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { if (_state.KeyProperty.IsReferenceType) { _sb.Append(@" - if(").Append(_state.KeyProperty.ArgumentName).Append(@" is not null) - item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName).Append(");"); + if(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@" is not null) + item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(");"); } else { _sb.Append(@" - item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName).Append(");"); + item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(");"); } } _sb.Append(@" - return new global::System.ComponentModel.DataAnnotations.ValidationResult($""There is no item of type '").Append(_state.TypeMinimallyQualified).Append("' with the identifier '{").Append(_state.KeyProperty.ArgumentName).Append(@"}'."", global::Thinktecture.SingleItem.Collection(nameof(").Append(_state.TypeFullyQualified).Append(".").Append(_state.KeyProperty.Name).Append(@"))); + return new global::System.ComponentModel.DataAnnotations.ValidationResult($""There is no item of type '").Append(_state.TypeMinimallyQualified).Append("' with the identifier '{").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@"}'."", global::Thinktecture.SingleItem.Collection(nameof(").Append(_state.TypeFullyQualified).Append(".").Append(_state.KeyProperty.Name).Append(@"))); } }"); } @@ -544,12 +564,12 @@ private void GenerateExplicitConversion() /// /// Explicit conversion from the type . /// - /// Value to covert. - /// An instance of if the is a known item or implements . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyProperty.ArgumentName).Append(@""")] - public static explicit operator ").Append(_state.TypeFullyQualifiedNullAnnotated).Append("(").Append(_state.KeyProperty.TypeFullyQualifiedNullAnnotated).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(@") + /// Value to covert. + /// An instance of if the is a known item or implements . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@""")] + public static explicit operator ").Append(_state.TypeFullyQualifiedNullAnnotated).Append("(").Append(_state.KeyProperty.TypeFullyQualifiedNullAnnotated).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@") { - return ").Append(_state.TypeFullyQualified).Append(".Get(").Append(_state.KeyProperty.ArgumentName).Append(@"); + return ").Append(_state.TypeFullyQualified).Append(".Get(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@"); }"); } @@ -561,7 +581,7 @@ private void GenerateEquals() public bool Equals(").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" other) {"); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { if (_state.IsReferenceType) { @@ -629,7 +649,7 @@ void AddItem(").Append(_state.TypeFullyQualified).Append(@" item, string itemNam "); } - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" if (!item.IsValid) @@ -677,7 +697,7 @@ public void EnsureValid() }"); } - private void GenerateGetKey() + private void GenerateToValue() { _sb.Append(@" @@ -685,7 +705,7 @@ private void GenerateGetKey() /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - ").Append(_state.KeyProperty.TypeFullyQualified).Append(" global::Thinktecture.IKeyedValueObject<").Append(_state.KeyProperty.TypeFullyQualified).Append(@">.GetKey() + ").Append(_state.KeyProperty.TypeFullyQualified).Append(" global::Thinktecture.IValueObjectConverter<").Append(_state.KeyProperty.TypeFullyQualified).Append(@">.ToValue() { return this.").Append(_state.KeyProperty.Name).Append(@"; }"); @@ -696,43 +716,43 @@ private void GenerateGet() _sb.Append(@" /// - /// Gets an enumeration item for provided . + /// Gets an enumeration item for provided . /// - /// The identifier to return an enumeration item for. - /// An instance of if is not null; otherwise null."); + /// The identifier to return an enumeration item for. + /// An instance of if is not null; otherwise null."); - if (!_state.IsValidatable) + if (!_state.Settings.IsValidatable) { _sb.Append(@" - /// If there is no item with the provided ."); + /// If there is no item with the provided ."); } _sb.Append(@" - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyProperty.ArgumentName).Append(@""")] - public static ").Append(_state.KeyProperty.IsReferenceType ? _state.TypeFullyQualifiedNullAnnotated : _state.TypeFullyQualified).Append(" Get(").Append(_state.KeyProperty.TypeFullyQualifiedNullAnnotated).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(@") + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@""")] + public static ").Append(_state.KeyProperty.IsReferenceType ? _state.TypeFullyQualifiedNullAnnotated : _state.TypeFullyQualified).Append(" Get(").Append(_state.KeyProperty.TypeFullyQualifiedNullAnnotated).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@") {"); if (_state.KeyProperty.IsReferenceType) { _sb.Append(@" - if (").Append(_state.KeyProperty.ArgumentName).Append(@" is null) + if (").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@" is null) return default; "); } _sb.Append(@" - if (!_itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName).Append(@", out var item)) + if (!_itemsLookup.Value.TryGetValue(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@", out var item)) {"); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" - item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName).Append(");"); + item = CreateAndCheckInvalidItem(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(");"); } else { _sb.Append(@" - throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(").Append(_state.TypeFullyQualified).Append("), ").Append(_state.KeyProperty.ArgumentName).Append(");"); + throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(").Append(_state.TypeFullyQualified).Append("), ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(");"); } _sb.Append(@" @@ -746,7 +766,7 @@ private void GenerateCreateAndCheckInvalidItem(bool needsCreateInvalidItemImplem { _sb.Append(@" - private static ").Append(_state.TypeFullyQualified).Append(" CreateAndCheckInvalidItem(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(@") + private static ").Append(_state.TypeFullyQualified).Append(" CreateAndCheckInvalidItem(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@") { var item = "); @@ -756,7 +776,7 @@ private void GenerateCreateAndCheckInvalidItem(bool needsCreateInvalidItemImplem } else { - _sb.Append(Constants.Methods.CREATE_INVALID_ITEM).Append("(").Append(_state.KeyProperty.ArgumentName).Append(")"); + _sb.Append(Constants.Methods.CREATE_INVALID_ITEM).Append("(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(")"); } _sb.Append(@"; @@ -792,9 +812,9 @@ private void GenerateCreateInvalidItem() { _sb.Append(@" - private static ").Append(_state.TypeFullyQualified).Append(" ").Append(Constants.Methods.CREATE_INVALID_ITEM).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(@") + private static ").Append(_state.TypeFullyQualified).Append(" ").Append(Constants.Methods.CREATE_INVALID_ITEM).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@") { - return new ").Append(_state.TypeFullyQualified).Append("(").Append(_state.KeyProperty.ArgumentName).Append(", false"); + return new ").Append(_state.TypeFullyQualified).Append("(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(", false"); foreach (var member in _state.AssignableInstanceFieldsAndProperties) { @@ -826,16 +846,16 @@ private void GenerateConstructors() return ctor.Arguments .Select(a => { - var argName = a.ArgumentName; + var argName = a.ArgumentName.Escaped; var counter = 0; - while (_state.KeyProperty.ArgumentName == argName || ContainsArgument(ownCtorArgs, argName)) + while (_state.KeyProperty.ArgumentName.Escaped == argName || _state.KeyProperty.ArgumentName.Raw == argName || ContainsArgument(ownCtorArgs, argName)) { counter++; - argName = $"{a.ArgumentName}{counter}"; // rename the argument name if it collides with another argument + argName = $"{a.ArgumentName.Raw}{counter}"; // rename the argument name if it collides with another argument } - return new ConstructorArgument(a.TypeFullyQualifiedWithNullability, argName); + return new ConstructorArgument(a.TypeFullyQualifiedWithNullability, new ArgumentName(argName, argName)); }).ToList(); }) .Distinct(ConstructorArgumentsComparer.Instance) @@ -852,7 +872,7 @@ private static bool ContainsArgument(List ownCtorArgs, stri { for (var i = 0; i < ownCtorArgs.Count; i++) { - if (ownCtorArgs[i].ArgumentName == argName) + if (ownCtorArgs[i].ArgumentName.Escaped == argName) return true; } @@ -863,24 +883,24 @@ private void GenerateConstructor( IReadOnlyList ctorArgs, IReadOnlyList baseCtorArgs) { - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" - private ").Append(_state.Name).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName); + private ").Append(_state.Name).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped); for (var i = 0; i < ctorArgs.Count; i++) { var member = ctorArgs[i]; - _sb.Append(", ").Append(member.TypeFullyQualifiedWithNullability).Append(" ").Append(member.ArgumentName); + _sb.Append(", ").Append(member.TypeFullyQualifiedWithNullability).Append(" ").Append(member.ArgumentName.Escaped); } _sb.Append(@") - : this(").Append(_state.KeyProperty.ArgumentName).Append(", true"); + : this(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(", true"); for (var i = 0; i < ctorArgs.Count; i++) { - _sb.Append(", ").Append(ctorArgs[i].ArgumentName); + _sb.Append(", ").Append(ctorArgs[i].ArgumentName.Escaped); } _sb.Append(@") @@ -890,14 +910,14 @@ private void GenerateConstructor( _sb.Append(@" - private ").Append(_state.Name).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName); + private ").Append(_state.Name).Append("(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) _sb.Append(", bool isValid"); foreach (var member in ctorArgs) { - _sb.Append(", ").Append(member.TypeFullyQualifiedWithNullability).Append(" ").Append(member.ArgumentName); + _sb.Append(", ").Append(member.TypeFullyQualifiedWithNullability).Append(" ").Append(member.ArgumentName.Escaped); } _sb.Append(")"); @@ -912,7 +932,7 @@ private void GenerateConstructor( if (i != 0) _sb.Append(", "); - _sb.Append(baseCtorArgs[i].ArgumentName); + _sb.Append(baseCtorArgs[i].ArgumentName.Escaped); } _sb.Append(")"); @@ -922,14 +942,14 @@ private void GenerateConstructor( {"); _sb.Append(@" - ValidateConstructorArguments(ref ").Append(_state.KeyProperty.ArgumentName); + ValidateConstructorArguments(ref ").Append(_state.KeyProperty.ArgumentName.Escaped); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) _sb.Append(", isValid"); foreach (var members in ctorArgs) { - _sb.Append(", ref ").Append(members.ArgumentName); + _sb.Append(", ref ").Append(members.ArgumentName.Escaped); } _sb.Append(@"); @@ -938,15 +958,15 @@ private void GenerateConstructor( if (_state.KeyProperty.IsReferenceType) { _sb.Append(@" - if (").Append(_state.KeyProperty.ArgumentName).Append(@" is null) - throw new global::System.ArgumentNullException(nameof(").Append(_state.KeyProperty.ArgumentName).Append(@")); + if (").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@" is null) + throw new global::System.ArgumentNullException(nameof(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@")); "); } _sb.Append(@" - this.").Append(_state.KeyProperty.Name).Append(" = ").Append(_state.KeyProperty.ArgumentName).Append(";"); + this.").Append(_state.KeyProperty.Name).Append(" = ").Append(_state.KeyProperty.ArgumentName.Escaped).Append(";"); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) { _sb.Append(@" this.IsValid = isValid;"); @@ -955,27 +975,27 @@ private void GenerateConstructor( foreach (var memberInfo in _state.AssignableInstanceFieldsAndProperties) { _sb.Append(@" - this.").Append(memberInfo.Name).Append(" = ").Append(memberInfo.ArgumentName).Append(";"); + this.").Append(memberInfo.Name).Append(" = ").Append(memberInfo.ArgumentName.Escaped).Append(";"); } _sb.Append(@" - this._hashCode = global::System.HashCode.Combine(typeof(").Append(_state.TypeFullyQualified).Append("), ").Append(Constants.KEY_EQUALITY_COMPARER_NAME).Append(".GetHashCode(").Append(_state.KeyProperty.ArgumentName).Append(@")); + this._hashCode = global::System.HashCode.Combine(typeof(").Append(_state.TypeFullyQualified).Append("), ").Append(Constants.KEY_EQUALITY_COMPARER_NAME).Append(".GetHashCode(").Append(_state.KeyProperty.ArgumentName.Escaped).Append(@")); } - static partial void ValidateConstructorArguments(ref ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName); + static partial void ValidateConstructorArguments(ref ").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName.Escaped); - if (_state.IsValidatable) + if (_state.Settings.IsValidatable) _sb.Append(", bool isValid"); foreach (var members in ctorArgs) { - _sb.Append(", ref ").Append(members.TypeFullyQualifiedWithNullability).Append(" ").Append(members.ArgumentName); + _sb.Append(", ref ").Append(members.TypeFullyQualifiedWithNullability).Append(" ").Append(members.ArgumentName.Escaped); } _sb.Append(");"); } - private readonly record struct ConstructorArgument(string TypeFullyQualifiedWithNullability, string ArgumentName); + private readonly record struct ConstructorArgument(string TypeFullyQualifiedWithNullability, ArgumentName ArgumentName); private sealed class ConstructorArgumentsComparer : IEqualityComparer> { diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs index 8a049b14..87387e27 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs @@ -73,7 +73,8 @@ private void InitializeParsableCodeGenerator(IncrementalGeneratorInitializationC state.KeyMember, state.Settings.SkipIParsable, state.KeyMember.IsParsable, - state.State.IsValidatable)); + state.State.Settings.IsValidatable, + state.AttributeInfo.DesiredFactories.Any(t => t.SpecialType == SpecialType.System_String))); InitializeParsableCodeGenerator(context, parsables, options); } @@ -132,7 +133,7 @@ private void InitializeSerializerGenerators(IncrementalGeneratorInitializationCo .SelectMany((tuple, _) => ImmutableArray.CreateRange(tuple.Right, (factory, state) => (State: state, Factory: factory), tuple.Left)) .Where(tuple => { - if (tuple.Factory.MustGenerateCode(tuple.State.AttributeInfo)) + if (tuple.Factory.MustGenerateCode(tuple.State)) { Logger.LogDebug("Code generator must generate code.", namespaceAndName: tuple.State, factory: tuple.Factory); return true; @@ -309,15 +310,13 @@ private bool IsEnumCandidate(TypeDeclarationSyntax typeDeclaration) if (factory is null) return new SourceGenContext(new SourceGenError("Could not fetch type information for code generation of a smart enum", tds)); - var settings = new EnumSettings(context.Attributes[0]); + var attributeInfo = new AttributeInfo(type); + var settings = new AllEnumSettings(context.Attributes[0]); var keyTypedMemberState = factory.Create(keyMemberType); var keyProperty = settings.CreateKeyProperty(keyTypedMemberState); var hasCreateInvalidItemImplementation = settings.IsValidatable && type.HasCreateInvalidItemImplementation(keyMemberType, cancellationToken); - var attributeInfo = new AttributeInfo(type); - - var enumState = new EnumSourceGeneratorState(factory, type, keyProperty, settings.SkipToString, settings.SkipSwitchMethods, settings.SkipMapMethods, - settings.IsValidatable, hasCreateInvalidItemImplementation, attributeInfo.HasStructLayoutAttribute, cancellationToken); + var enumState = new EnumSourceGeneratorState(factory, type, keyProperty, new EnumSettings(settings, attributeInfo), hasCreateInvalidItemImplementation, cancellationToken); var derivedTypes = new SmartEnumDerivedTypes(enumState.Namespace, enumState.Name, enumState.TypeFullyQualified, enumState.IsReferenceType, FindDerivedTypes(type)); Logger.LogDebug("The type declaration is a valid smart enum", namespaceAndName: enumState); @@ -351,7 +350,7 @@ private static IReadOnlyList FindDerivedTypes(INamedTypeSymbol type) private readonly record struct ValidSourceGenState(EnumSourceGeneratorState State, SmartEnumDerivedTypes DerivedTypes, - EnumSettings Settings, + AllEnumSettings Settings, IMemberState KeyMember, AttributeInfo AttributeInfo); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs index f26f361a..4159b0c1 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs @@ -155,7 +155,7 @@ protected void InitializeFormattableCodeGenerator( .WithComparer(new SetComparer()) .SelectMany((states, _) => states); - context.RegisterSourceOutput(formattables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, (state.Left.Type, state.Left.KeyMember), state.Right, InterfaceCodeGeneratorFactory.Formattable)); + context.RegisterSourceOutput(formattables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, new InterfaceCodeGeneratorState(state.Left.Type, state.Left.KeyMember), state.Right, InterfaceCodeGeneratorFactory.Formattable)); } protected void InitializeComparableCodeGenerator( @@ -172,7 +172,7 @@ protected void InitializeComparableCodeGenerator( .WithComparer(new SetComparer()) .SelectMany((states, _) => states); - context.RegisterSourceOutput(comparables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, (state.Left.Type, state.Left.KeyMember), state.Right, InterfaceCodeGeneratorFactory.Comparable(state.Left.ComparerAccessor))); + context.RegisterSourceOutput(comparables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, new InterfaceCodeGeneratorState(state.Left.Type, state.Left.KeyMember), state.Right, InterfaceCodeGeneratorFactory.Comparable(state.Left.ComparerAccessor))); } protected void InitializeParsableCodeGenerator( @@ -181,7 +181,7 @@ protected void InitializeParsableCodeGenerator( IncrementalValueProvider options) { parsables = parsables - .Where(state => !state.SkipIParsable && (state.KeyMember.IsString() || state.IsKeyMemberParsable)) + .Where(state => !state.SkipIParsable && (state.KeyMember?.IsString() == true || state.IsKeyMemberParsable || state.HasStringBasedValidateMethod)) .Collect() .Select(static (states, _) => states.IsDefaultOrEmpty ? ImmutableArray.Empty @@ -189,7 +189,7 @@ protected void InitializeParsableCodeGenerator( .WithComparer(new SetComparer()) .SelectMany((states, _) => states); - context.RegisterSourceOutput(parsables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, (state.Left.Type, state.Left.KeyMember), state.Right, InterfaceCodeGeneratorFactory.Parsable(state.Left.IsValidatableEnum))); + context.RegisterSourceOutput(parsables.Combine(options), (ctx, state) => GenerateCode(ctx, state.Left.Type.Namespace, state.Left.Type.Name, state.Left, state.Right, InterfaceCodeGeneratorFactory.Parsable(state.Left.IsValidatableEnum))); } protected void InitializeComparisonOperatorsCodeGenerator( @@ -218,7 +218,7 @@ protected void InitializeComparisonOperatorsCodeGenerator( var state = tuple.Left.State; var generator = tuple.Left.CodeGenerator; - GenerateCode(ctx, state.Type.Namespace, state.Type.Name, (state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); + GenerateCode(ctx, state.Type.Namespace, state.Type.Name, new InterfaceCodeGeneratorState(state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); }); } @@ -248,7 +248,7 @@ protected void InitializeEqualityComparisonOperatorsCodeGenerator( var state = tuple.Left.State; var generator = tuple.Left.CodeGenerator; - GenerateCode(ctx, state.Type.Namespace, state.Type.Name, (state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); + GenerateCode(ctx, state.Type.Namespace, state.Type.Name, new InterfaceCodeGeneratorState(state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); }); } @@ -279,7 +279,7 @@ protected void InitializeOperatorsCodeGenerator( var state = tuple.Left.State; var generator = tuple.Left.CodeGenerator; - GenerateCode(ctx, state.Type.Namespace, state.Type.Name, (state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); + GenerateCode(ctx, state.Type.Namespace, state.Type.Name, new InterfaceCodeGeneratorState(state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); }); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeInformationComparer.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeInformationComparer.cs index 96da2475..49fb23cd 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeInformationComparer.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeInformationComparer.cs @@ -2,7 +2,7 @@ namespace Thinktecture.CodeAnalysis; public sealed class TypeInformationComparer : IEqualityComparer { - public static readonly IEqualityComparer Instance = new TypeInformationComparer(); + public static readonly TypeInformationComparer Instance = new(); public bool Equals(ITypeInformation? x, ITypeInformation? y) { diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AdditionOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AdditionOperatorsCodeGenerator.cs index b6ff60f9..b70abd13 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AdditionOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AdditionOperatorsCodeGenerator.cs @@ -53,33 +53,33 @@ private AdditionOperatorsCodeGenerator( _withKeyTypeOverloads = withKeyTypeOverloads; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { if (!_keyMemberOperators.HasOperator(ImplementedOperators.All)) return; sb.Append(@" - global::System.Numerics.IAdditionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IAdditionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.IAdditionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IAdditionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - var typeLeftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var typeLightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var typeLeftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var typeLightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator +(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator +(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(" + right.").Append(keyMember.Name).Append(@"); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(" + right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -88,35 +88,35 @@ public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMem sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked +(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked +(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(" + right.").Append(keyMember.Name).Append(@")); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(" + right.").Append(state.KeyMember.Name).Append(@")); }"); } if (_withKeyTypeOverloads) - GenerateOverloadsForKeyType(sb, type, keyMember, typeLeftNullCheck, typeLightNullCheck); + GenerateOverloadsForKeyType(sb, state, typeLeftNullCheck, typeLightNullCheck); } - private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember, string? typeLeftNullCheck, string? typeLightNullCheck) + private void GenerateOverloadsForKeyType(StringBuilder sb, InterfaceCodeGeneratorState state, string? typeLeftNullCheck, string? typeLightNullCheck) { - var memberLeftNullCheck = keyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; - var memberRightNullCheck = keyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var memberLeftNullCheck = state.KeyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; + var memberRightNullCheck = state.KeyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator +(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator +(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(@" + right); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(@" + right); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator +(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator +(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left + right.").Append(keyMember.Name).Append(@"); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left + right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -125,15 +125,15 @@ private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked +(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked +(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(@" + right)); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(@" + right)); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked +(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked +(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left + right.").Append(keyMember.Name).Append(@")); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left + right.").Append(state.KeyMember.Name).Append(@")); }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectJsonCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectJsonCodeGenerator.cs index 685cb34f..e9303e44 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectJsonCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectJsonCodeGenerator.cs @@ -49,11 +49,11 @@ public sealed class ValueObjectJsonConverter : global::System.Text.Json.Serializ if (needsConverter) { _sb.Append(@" - private readonly global::System.Text.Json.Serialization.JsonConverter<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("> _").Append(memberInfo.ArgumentName).Append("Converter;"); + private readonly global::System.Text.Json.Serialization.JsonConverter<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("> _").Append(memberInfo.ArgumentName.Raw).Append("Converter;"); } _sb.Append(@" - private readonly string _").Append(memberInfo.ArgumentName).Append("PropertyName;"); + private readonly string _").Append(memberInfo.ArgumentName.Raw).Append("PropertyName;"); } _sb.Append(@" @@ -76,11 +76,11 @@ public ValueObjectJsonConverter(global::System.Text.Json.JsonSerializerOptions o if (needsConverter) { _sb.Append(@" - this._").Append(memberInfo.ArgumentName).Append("Converter = (global::System.Text.Json.Serialization.JsonConverter<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(">)options.GetConverter(typeof(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("));"); + this._").Append(memberInfo.ArgumentName.Raw).Append("Converter = (global::System.Text.Json.Serialization.JsonConverter<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(">)options.GetConverter(typeof(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("));"); } _sb.Append(@" - this._").Append(memberInfo.ArgumentName).Append("PropertyName = namingPolicy?.ConvertName(\"").Append(memberInfo.Name).Append(@""") ?? """).Append(memberInfo.Name).Append(@""";"); + this._").Append(memberInfo.ArgumentName.Raw).Append("PropertyName = namingPolicy?.ConvertName(\"").Append(memberInfo.Name).Append(@""") ?? """).Append(memberInfo.Name).Append(@""";"); } _sb.Append(@" @@ -103,7 +103,7 @@ public ValueObjectJsonConverter(global::System.Text.Json.JsonSerializerOptions o var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - ").Append(memberInfo.TypeFullyQualifiedNullAnnotated).Append(" ").Append(memberInfo.ArgumentName).Append(" = default;"); + ").Append(memberInfo.TypeFullyQualifiedNullAnnotated).Append(" ").Append(memberInfo.ArgumentName.Escaped).Append(" = default;"); } _sb.Append(@" @@ -141,9 +141,9 @@ public ValueObjectJsonConverter(global::System.Text.Json.JsonSerializerOptions o else if "); } - _sb.Append(@"(comparer.Equals(propName, this._").Append(memberInfo.ArgumentName).Append(@"PropertyName)) + _sb.Append("(comparer.Equals(propName, this._").Append(memberInfo.ArgumentName.Raw).Append(@"PropertyName)) { - ").Append(memberInfo.ArgumentName).Append(" = "); + ").Append(memberInfo.ArgumentName.Escaped).Append(" = "); GenerateReadValue(_sb, memberInfo); @@ -172,7 +172,7 @@ public ValueObjectJsonConverter(global::System.Text.Json.JsonSerializerOptions o var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - ").Append(memberInfo.ArgumentName).Append("!,"); + ").Append(memberInfo.ArgumentName.Escaped).Append("!,"); } _sb.Append(@" @@ -200,25 +200,25 @@ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, ").Ap var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - var ").Append(memberInfo.ArgumentName).Append(@"PropertyValue = value.").Append(memberInfo.Name).Append(@"; + var ").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue = value.").Append(memberInfo.Name).Append(@"; "); if (memberInfo.IsReferenceTypeOrNullableStruct) { _sb.Append(@" - if(!ignoreNullValues || ").Append(memberInfo.ArgumentName).Append(@"PropertyValue is not null) + if(!ignoreNullValues || ").Append(memberInfo.ArgumentName.Raw).Append(@"PropertyValue is not null) { "); } else { _sb.Append(@" - if(!ignoreDefaultValues || !").Append(memberInfo.ArgumentName).Append("PropertyValue.Equals(default(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(@"))) + if(!ignoreDefaultValues || !").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue.Equals(default(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(@"))) { "); } - _sb.Append("writer.WritePropertyName(this._").Append(memberInfo.ArgumentName).Append(@"PropertyName); + _sb.Append("writer.WritePropertyName(this._").Append(memberInfo.ArgumentName.Raw).Append(@"PropertyName); "); _sb.Append(" "); @@ -292,13 +292,13 @@ private static bool GenerateWriteValue(StringBuilder? sb, InstanceMemberInfo mem } else { - sb?.Append("this._").Append(memberInfo.ArgumentName).Append("Converter.Write(writer, ").Append(memberInfo.ArgumentName).Append("PropertyValue, options);"); + sb?.Append("this._").Append(memberInfo.ArgumentName.Raw).Append("Converter.Write(writer, ").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue, options);"); return true; } } - sb?.Append("writer.").Append(command).Append("(").Append(memberInfo.ArgumentName).Append("PropertyValue);"); + sb?.Append("writer.").Append(command).Append("(").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue);"); return false; } @@ -342,7 +342,7 @@ private static void GenerateReadValue(StringBuilder sb, InstanceMemberInfo membe } else { - sb.Append("this._").Append(memberInfo.ArgumentName).Append("Converter.Read(ref reader, typeof(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("), options)"); + sb.Append("this._").Append(memberInfo.ArgumentName.Raw).Append("Converter.Read(ref reader, typeof(").Append(memberInfo.TypeFullyQualifiedWithNullability).Append("), options)"); return; } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectMessagePackCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectMessagePackCodeGenerator.cs index 1319cb4b..0b78c059 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectMessagePackCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectMessagePackCodeGenerator.cs @@ -63,7 +63,7 @@ public sealed class ValueObjectMessagePackFormatter : global::MessagePack.Format var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - var ").Append(memberInfo.ArgumentName).Append(" = "); + var ").Append(memberInfo.ArgumentName.Escaped).Append(" = "); GenerateReadValue(_sb, memberInfo); @@ -81,7 +81,7 @@ public sealed class ValueObjectMessagePackFormatter : global::MessagePack.Format var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - ").Append(memberInfo.ArgumentName).Append(","); + ").Append(memberInfo.ArgumentName.Escaped).Append(","); } _sb.Append(@" diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectNewtonsoftJsonCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectNewtonsoftJsonCodeGenerator.cs index 455ef3a7..f577579c 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectNewtonsoftJsonCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ComplexValueObjectNewtonsoftJsonCodeGenerator.cs @@ -65,7 +65,16 @@ public override bool CanConvert(global::System.Type objectType) } if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.StartObject)}\"".""); + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.StartObject)}\""."", + reader.Path, + lineNumber, + linePosition, + null); + } "); for (var i = 0; i < _assignableInstanceFieldsAndProperties.Count; i++) @@ -73,7 +82,7 @@ public override bool CanConvert(global::System.Type objectType) var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - ").Append(memberInfo.TypeFullyQualifiedNullAnnotated).Append(" ").Append(memberInfo.ArgumentName).Append(" = default;"); + ").Append(memberInfo.TypeFullyQualifiedNullAnnotated).Append(" ").Append(memberInfo.ArgumentName.Escaped).Append(" = default;"); } _sb.Append(@" @@ -86,12 +95,30 @@ public override bool CanConvert(global::System.Type objectType) break; if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.PropertyName)}\"".""); + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.PropertyName)}\""."", + reader.Path, + lineNumber, + linePosition, + null); + } var propName = reader.Value!.ToString(); if(!reader.Read()) - throw new global::Newtonsoft.Json.JsonException($""Unexpected end of the JSON message when trying the read the value of \""{propName}\"" during deserialization of \""").Append(_type.TypeMinimallyQualified).Append(@"\"".""); + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $""Unexpected end of the JSON message when trying the read the value of \""{propName}\"" during deserialization of \""").Append(_type.TypeMinimallyQualified).Append(@"\""."", + reader.Path, + lineNumber, + linePosition, + null); + } "); cancellationToken.ThrowIfCancellationRequested(); @@ -111,9 +138,9 @@ public override bool CanConvert(global::System.Type objectType) else if "); } - _sb.Append(@"(comparer.Equals(propName, """).Append(memberInfo.ArgumentName).Append(@""")) + _sb.Append(@"(comparer.Equals(propName, """).Append(memberInfo.ArgumentName.Escaped).Append(@""")) { - ").Append(memberInfo.ArgumentName).Append(" = serializer.Deserialize<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(@">(reader); + ").Append(memberInfo.ArgumentName.Escaped).Append(" = serializer.Deserialize<").Append(memberInfo.TypeFullyQualifiedWithNullability).Append(@">(reader); }"); } @@ -122,7 +149,14 @@ public override bool CanConvert(global::System.Type objectType) _sb.Append(@" else { - throw new global::Newtonsoft.Json.JsonException($""Unknown member \""{propName}\"" encountered when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"".""); + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $""Unknown member \""{propName}\"" encountered when trying to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\""."", + reader.Path, + lineNumber, + linePosition, + null); }"); } @@ -138,14 +172,23 @@ public override bool CanConvert(global::System.Type objectType) var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - ").Append(memberInfo.ArgumentName).Append("!,"); + ").Append(memberInfo.ArgumentName.Escaped).Append("!,"); } _sb.Append(@" out var obj); if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::Newtonsoft.Json.JsonException($""Unable to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Error: {validationResult!.ErrorMessage}.""); + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonSerializationException( + $""Unable to deserialize \""").Append(_type.TypeMinimallyQualified).Append(@"\"". Error: {validationResult!.ErrorMessage}."", + reader.Path, + lineNumber, + linePosition, + null); + } return obj; } @@ -171,13 +214,13 @@ public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object var memberInfo = _assignableInstanceFieldsAndProperties[i]; _sb.Append(@" - var ").Append(memberInfo.ArgumentName).Append(@"PropertyValue = obj.").Append(memberInfo.Name).Append(@"; + var ").Append(memberInfo.ArgumentName.Raw).Append(@"PropertyValue = obj.").Append(memberInfo.Name).Append(@"; "); if (memberInfo.IsReferenceTypeOrNullableStruct) { _sb.Append(@" - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || ").Append(memberInfo.ArgumentName).Append(@"PropertyValue is not null) + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || ").Append(memberInfo.ArgumentName.Raw).Append(@"PropertyValue is not null) { "); } @@ -203,6 +246,20 @@ public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object _sb.Append(@" writer.WriteEndObject(); } + + private static (int Number, int Position) GetLineInfo(global::Newtonsoft.Json.JsonReader reader) + { + var lineInfo = (reader as global::Newtonsoft.Json.IJsonLineInfo); + + if (lineInfo?.HasLineInfo() == true) + { + return (lineInfo.LineNumber, lineInfo.LinePosition); + } + else + { + return (0, 0); + } + } } } "); @@ -243,11 +300,11 @@ private static void GenerateWriteValue(StringBuilder? sb, InstanceMemberInfo mem break; default: - sb?.Append("serializer.Serialize(writer, ").Append(memberInfo.ArgumentName).Append("PropertyValue);"); + sb?.Append("serializer.Serialize(writer, ").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue);"); return; } } - sb?.Append("writer.").Append(command).Append("(").Append(memberInfo.ArgumentName).Append("PropertyValue);"); + sb?.Append("writer.").Append(command).Append("(").Append(memberInfo.ArgumentName.Raw).Append("PropertyValue);"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs index 580b487c..7aab43cd 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs @@ -48,33 +48,33 @@ private DivisionOperatorsCodeGenerator(ImplementedOperators keyMemberOperators, _withKeyTypeOverloads = withKeyTypeOverloads; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { if (!_keyMemberOperators.HasOperator(ImplementedOperators.All)) return; sb.Append(@" - global::System.Numerics.IDivisionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IDivisionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.IDivisionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IDivisionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - var typeLeftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var typeLightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var typeLeftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var typeLightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator /(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator /(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(" / right.").Append(keyMember.Name).Append(@"); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(" / right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -83,35 +83,35 @@ public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMem sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked /(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked /(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(" / right.").Append(keyMember.Name).Append(@")); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(" / right.").Append(state.KeyMember.Name).Append(@")); }"); } if (_withKeyTypeOverloads) - GenerateOverloadsForKeyType(sb, type, keyMember, typeLeftNullCheck, typeLightNullCheck); + GenerateOverloadsForKeyType(sb, state, typeLeftNullCheck, typeLightNullCheck); } - private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember, string? typeLeftNullCheck, string? typeLightNullCheck) + private void GenerateOverloadsForKeyType(StringBuilder sb, InterfaceCodeGeneratorState state, string? typeLeftNullCheck, string? typeLightNullCheck) { - var memberLeftNullCheck = keyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; - var memberRightNullCheck = keyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var memberLeftNullCheck = state.KeyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; + var memberRightNullCheck = state.KeyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator /(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator /(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(@" / right); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(@" / right); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator /(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator /(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left / right.").Append(keyMember.Name).Append(@"); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left / right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -120,15 +120,15 @@ private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked /(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked /(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(@" / right)); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(@" / right)); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked /(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked /(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left / right.").Append(keyMember.Name).Append(@")); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left / right.").Append(state.KeyMember.Name).Append(@")); }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/IValueObjectSerializerCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/IValueObjectSerializerCodeGeneratorFactory.cs index d7c94112..1b03d004 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/IValueObjectSerializerCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/IValueObjectSerializerCodeGeneratorFactory.cs @@ -2,7 +2,7 @@ namespace Thinktecture.CodeAnalysis.ValueObjects; public interface IValueObjectSerializerCodeGeneratorFactory : IKeyedSerializerCodeGeneratorFactory, - ICodeGeneratorFactory, + IComplexSerializerCodeGeneratorFactory, IEquatable { } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/JsonValueObjectCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/JsonValueObjectCodeGeneratorFactory.cs index d1887566..daf914c4 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/JsonValueObjectCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/JsonValueObjectCodeGeneratorFactory.cs @@ -12,14 +12,21 @@ private JsonValueObjectCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasJsonConverterAttribute; + return !state.AttributeInfo.HasJsonConverterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson))); + } + + public bool MustGenerateCode(ComplexSerializerGeneratorState state) + { + return !state.AttributeInfo.HasJsonConverterAttribute + && !state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedJsonCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedJsonCodeGenerator(state, stringBuilder); } public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBuilder stringBuilder) @@ -31,4 +38,5 @@ public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBui public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(IKeyedSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); + public bool Equals(IComplexSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MessagePackValueObjectCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MessagePackValueObjectCodeGeneratorFactory.cs index 6dbf2207..918fd931 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MessagePackValueObjectCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MessagePackValueObjectCodeGeneratorFactory.cs @@ -12,14 +12,21 @@ private MessagePackValueObjectCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasMessagePackFormatterAttribute; + return !state.AttributeInfo.HasMessagePackFormatterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.MessagePack))); + } + + public bool MustGenerateCode(ComplexSerializerGeneratorState state) + { + return !state.AttributeInfo.HasMessagePackFormatterAttribute + && !state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.MessagePack)); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedMessagePackCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedMessagePackCodeGenerator(state, stringBuilder); } public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBuilder stringBuilder) @@ -31,4 +38,5 @@ public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBui public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(IKeyedSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); + public bool Equals(IComplexSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs index e9512ebb..fff5c9e2 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs @@ -48,33 +48,33 @@ private MultiplyOperatorsCodeGenerator(ImplementedOperators keyMemberOperators, _withKeyTypeOverloads = withKeyTypeOverloads; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { if (!_keyMemberOperators.HasOperator(ImplementedOperators.All)) return; sb.Append(@" - global::System.Numerics.IMultiplyOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IMultiplyOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.IMultiplyOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.IMultiplyOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - var typeLeftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var typeLightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var typeLeftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var typeLightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator *(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator *(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(" * right.").Append(keyMember.Name).Append(@"); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(" * right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -83,35 +83,35 @@ public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMem sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked *(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked *(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(" * right.").Append(keyMember.Name).Append(@")); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(" * right.").Append(state.KeyMember.Name).Append(@")); }"); } if (_withKeyTypeOverloads) - GenerateOverloadsForKeyType(sb, type, keyMember, typeLeftNullCheck, typeLightNullCheck); + GenerateOverloadsForKeyType(sb, state, typeLeftNullCheck, typeLightNullCheck); } - private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember, string? typeLeftNullCheck, string? typeLightNullCheck) + private void GenerateOverloadsForKeyType(StringBuilder sb, InterfaceCodeGeneratorState state, string? typeLeftNullCheck, string? typeLightNullCheck) { - var memberLeftNullCheck = keyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; - var memberRightNullCheck = keyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var memberLeftNullCheck = state.KeyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; + var memberRightNullCheck = state.KeyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator *(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator *(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(@" * right); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(@" * right); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator *(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator *(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left * right.").Append(keyMember.Name).Append(@"); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left * right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -120,15 +120,15 @@ private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked *(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked *(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(@" * right)); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(@" * right)); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked *(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked *(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left * right.").Append(keyMember.Name).Append(@")); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left * right.").Append(state.KeyMember.Name).Append(@")); }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/NewtonsoftJsonValueObjectCodeGeneratorFactory.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/NewtonsoftJsonValueObjectCodeGeneratorFactory.cs index c7235ab1..fad941c7 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/NewtonsoftJsonValueObjectCodeGeneratorFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/NewtonsoftJsonValueObjectCodeGeneratorFactory.cs @@ -12,14 +12,21 @@ private NewtonsoftJsonValueObjectCodeGeneratorFactory() { } - public bool MustGenerateCode(AttributeInfo attributeInfo) + public bool MustGenerateCode(KeyedSerializerGeneratorState state) { - return !attributeInfo.HasNewtonsoftJsonConverterAttribute; + return !state.AttributeInfo.HasNewtonsoftJsonConverterAttribute + && (state.KeyMember is not null || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson))); + } + + public bool MustGenerateCode(ComplexSerializerGeneratorState state) + { + return !state.AttributeInfo.HasNewtonsoftJsonConverterAttribute + && !state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization.HasFlag(SerializationFrameworks.NewtonsoftJson)); } public CodeGeneratorBase Create(KeyedSerializerGeneratorState state, StringBuilder stringBuilder) { - return new KeyedNewtonsoftJsonCodeGenerator(state.Type, state.KeyMember, stringBuilder); + return new KeyedNewtonsoftJsonCodeGenerator(state, stringBuilder); } public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBuilder stringBuilder) @@ -31,5 +38,5 @@ public CodeGeneratorBase Create(ComplexSerializerGeneratorState state, StringBui public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(ICodeGeneratorFactory other) => ReferenceEquals(this, other); public bool Equals(IKeyedSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); - + public bool Equals(IComplexSerializerCodeGeneratorFactory other) => ReferenceEquals(this, other); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs index fc0b0c29..d800882b 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs @@ -51,33 +51,33 @@ private SubtractionOperatorsCodeGenerator(ImplementedOperators keyMemberOperator _withKeyTypeOverloads = withKeyTypeOverloads; } - public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateBaseTypes(StringBuilder sb, InterfaceCodeGeneratorState state) { if (!_keyMemberOperators.HasOperator(ImplementedOperators.All)) return; sb.Append(@" - global::System.Numerics.ISubtractionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.ISubtractionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); if (!_withKeyTypeOverloads) return; sb.Append(@", - global::System.Numerics.ISubtractionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); + global::System.Numerics.ISubtractionOperators<").Append(state.Type.TypeFullyQualified).Append(", ").Append(state.KeyMember.TypeFullyQualified).Append(", ").Append(state.Type.TypeFullyQualified).Append(">"); } - public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + public void GenerateImplementation(StringBuilder sb, InterfaceCodeGeneratorState state) { - var typeLeftNullCheck = type.IsReferenceType ? _LEFT_NULL_CHECK : null; - var typeLightNullCheck = type.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var typeLeftNullCheck = state.Type.IsReferenceType ? _LEFT_NULL_CHECK : null; + var typeLightNullCheck = state.Type.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator -(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator -(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(" - right.").Append(keyMember.Name).Append(@"); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(" - right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -86,35 +86,35 @@ public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMem sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked -(").Append(type.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked -(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(" - right.").Append(keyMember.Name).Append(@")); + ").Append(typeLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(" - right.").Append(state.KeyMember.Name).Append(@")); }"); } if (_withKeyTypeOverloads) - GenerateOverloadsForKeyType(sb, type, keyMember, typeLeftNullCheck, typeLightNullCheck); + GenerateOverloadsForKeyType(sb, state, typeLeftNullCheck, typeLightNullCheck); } - private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember, string? typeLeftNullCheck, string? typeLightNullCheck) + private void GenerateOverloadsForKeyType(StringBuilder sb, InterfaceCodeGeneratorState state, string? typeLeftNullCheck, string? typeLightNullCheck) { - var memberLeftNullCheck = keyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; - var memberRightNullCheck = keyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; + var memberLeftNullCheck = state.KeyMember.IsReferenceType ? _LEFT_NULL_CHECK : null; + var memberRightNullCheck = state.KeyMember.IsReferenceType ? _RIGHT_NULL_CHECK : null; if (_keyMemberOperators.HasOperator(ImplementedOperators.Default)) { sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator -(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator -(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(keyMember.Name).Append(@" - right); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(left.").Append(state.KeyMember.Name).Append(@" - right); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator -(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator -(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left - right.").Append(keyMember.Name).Append(@"); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(left - right.").Append(state.KeyMember.Name).Append(@"); }"); } @@ -123,15 +123,15 @@ private void GenerateOverloadsForKeyType(StringBuilder sb, ITypeInformation type sb.Append(@" /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked -(").Append(type.TypeFullyQualified).Append(" left, ").Append(keyMember.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked -(").Append(state.Type.TypeFullyQualified).Append(" left, ").Append(state.KeyMember.TypeFullyQualified).Append(@" right) { - ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(keyMember.Name).Append(@" - right)); + ").Append(typeLeftNullCheck).Append(memberRightNullCheck).Append("return Create(checked(left.").Append(state.KeyMember.Name).Append(@" - right)); } /// - public static ").Append(type.TypeFullyQualified).Append(" operator checked -(").Append(keyMember.TypeFullyQualified).Append(" left, ").Append(type.TypeFullyQualified).Append(@" right) + public static ").Append(state.Type.TypeFullyQualified).Append(" operator checked -(").Append(state.KeyMember.TypeFullyQualified).Append(" left, ").Append(state.Type.TypeFullyQualified).Append(@" right) { - ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left - right.").Append(keyMember.Name).Append(@")); + ").Append(memberLeftNullCheck).Append(typeLightNullCheck).Append("return Create(checked(left - right.").Append(state.KeyMember.Name).Append(@")); }"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs index 9d26e991..bec900ec 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs @@ -1,4 +1,5 @@ using System.Text; +using Microsoft.CodeAnalysis; namespace Thinktecture.CodeAnalysis.ValueObjects; @@ -51,7 +52,7 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c if (_state is { HasKeyMember: true, Settings.SkipFactoryMethods: false }) { _sb.Append(@" - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyMember.Member.TypeFullyQualified).Append(@">))]"); + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyMember.Member.TypeFullyQualified).Append(">))]"); } _sb.Append(@" @@ -60,12 +61,14 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c if (_state.HasKeyMember) { _sb.Append(@", - global::Thinktecture.IKeyedValueObject<").Append(_state.KeyMember.Member.TypeFullyQualifiedWithNullability).Append(">"); + global::Thinktecture.IKeyedValueObject<").Append(_state.KeyMember.Member.TypeFullyQualifiedWithNullability).Append(@">, + global::Thinktecture.IValueObjectConverter<").Append(_state.KeyMember.Member.TypeFullyQualified).Append(">"); if (!_state.Settings.SkipFactoryMethods) { _sb.Append(@", - global::Thinktecture.IKeyedValueObject<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyMember.Member.TypeFullyQualifiedWithNullability).Append(">"); + global::Thinktecture.IKeyedValueObject<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyMember.Member.TypeFullyQualifiedWithNullability).Append(@">, + global::Thinktecture.IValueObjectFactory<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyMember.Member.TypeFullyQualified).Append(">"); } } else @@ -75,6 +78,21 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c global::Thinktecture.IComplexValueObject"); } + foreach (var desiredFactory in _state.Settings.DesiredFactories) + { + if (_state is { HasKeyMember: true, Settings.SkipFactoryMethods: false } && desiredFactory.Equals(_state.KeyMember.Member)) + continue; + + _sb.Append(@", + global::Thinktecture.IValueObjectFactory<").Append(_state.TypeFullyQualified).Append(", ").Append(desiredFactory.TypeFullyQualified).Append(">"); + + if (desiredFactory.UseForSerialization != SerializationFrameworks.None) + { + _sb.Append(@", + global::Thinktecture.IValueObjectConverter<").Append(desiredFactory.TypeFullyQualified).Append(">"); + } + } + _sb.Append(@" {"); @@ -113,7 +131,7 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c if (_state.HasKeyMember) { - GenerateGetKey(_state.KeyMember); + GenerateToValue(_state.KeyMember); GenerateImplicitConversionToKey(_state.KeyMember); GenerateExplicitConversionToKey(_state.KeyMember); @@ -139,7 +157,7 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c }"); } - private void GenerateGetKey(EqualityInstanceMemberInfo keyMember) + private void GenerateToValue(EqualityInstanceMemberInfo keyMember) { _sb.Append(@" @@ -147,7 +165,7 @@ private void GenerateGetKey(EqualityInstanceMemberInfo keyMember) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - ").Append(keyMember.Member.TypeFullyQualified).Append(" global::Thinktecture.IKeyedValueObject<").Append(keyMember.Member.TypeFullyQualified).Append(@">.GetKey() + ").Append(keyMember.Member.TypeFullyQualified).Append(" global::Thinktecture.IValueObjectConverter<").Append(keyMember.Member.TypeFullyQualified).Append(@">.ToValue() { return this.").Append(keyMember.Member.Name).Append(@"; }"); @@ -183,11 +201,11 @@ internal static void ModuleInit() } else { - _sb.Append("static ").Append(keyMember.ArgumentName).Append(" => ").Append(typeFullyQualified).Append(".Create(").Append(keyMember.ArgumentName).Append(")"); + _sb.Append("static ").Append(keyMember.ArgumentName.Escaped).Append(" => ").Append(typeFullyQualified).Append(".Create(").Append(keyMember.ArgumentName.Escaped).Append(")"); } _sb.Append(@"; - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static ").Append(keyMember.ArgumentName).Append(" => new ").Append(typeFullyQualified).Append("(").Append(keyMember.ArgumentName).Append(@"); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static ").Append(keyMember.ArgumentName.Escaped).Append(" => new ").Append(typeFullyQualified).Append("(").Append(keyMember.ArgumentName.Escaped).Append(@"); var convertToKey = new global::System.Func<").Append(typeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualifiedWithNullability).Append(">(static item => item.").Append(keyMember.Name).Append(@"); global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.").Append(keyMember.Name).Append(@"; @@ -309,29 +327,29 @@ private void GenerateExplicitConversion(EqualityInstanceMemberInfo keyMemberInfo /// /// Explicit conversion from the type . /// - /// Value to covert. + /// Value to covert. /// An instance of ."); if (bothAreReferenceTypes && !emptyStringYieldsNull) { _sb.Append(@" - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(keyMember.ArgumentName).Append(@""")]"); + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(keyMember.ArgumentName.Escaped).Append(@""")]"); } _sb.Append(@" - public static explicit operator ").Append(_state.TypeFullyQualified).Append(nullableQuestionMark).Append("(").Append(keyMember.TypeFullyQualified).Append(nullableQuestionMark).Append(" ").Append(keyMember.ArgumentName).Append(@") + public static explicit operator ").Append(_state.TypeFullyQualified).Append(nullableQuestionMark).Append("(").Append(keyMember.TypeFullyQualified).Append(nullableQuestionMark).Append(" ").Append(keyMember.ArgumentName.Escaped).Append(@") {"); if (bothAreReferenceTypes) { _sb.Append(@" - if(").Append(keyMember.ArgumentName).Append(@" is null) + if(").Append(keyMember.ArgumentName.Escaped).Append(@" is null) return null; "); } _sb.Append(@" - return ").Append(_state.TypeFullyQualified).Append(".Create(").Append(keyMember.ArgumentName).Append(@"); + return ").Append(_state.TypeFullyQualified).Append(".Create(").Append(keyMember.ArgumentName.Escaped).Append(@"); }"); } @@ -347,7 +365,7 @@ private void GenerateCreateMethod(bool allowNullOutput, bool emptyStringYieldsNu if (allowNullOutput && !emptyStringYieldsNull) { _sb.Append(@" - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyMember!.Member.ArgumentName).Append(@""")]"); + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""").Append(_state.KeyMember!.Member.ArgumentName.Escaped).Append(@""")]"); } _sb.Append(@" @@ -364,6 +382,9 @@ private void GenerateCreateMethod(bool allowNullOutput, bool emptyStringYieldsNu if (fieldsAndProperties.Count > 0) _sb.Append(", "); + if (_state.HasKeyMember) + _sb.Append("null, "); // IFormatProvider + _sb.Append("out ").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" obj); if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) @@ -394,6 +415,9 @@ private void GenerateTryCreateMethod(bool allowNullOutput, bool emptyStringYield if (fieldsAndProperties.Count > 0) _sb.Append(", "); + if (_state.HasKeyMember) + _sb.Append("null, "); // IFormatProvider + _sb.Append(@"out obj); return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; @@ -411,6 +435,13 @@ private void GenerateValidateMethod(bool allowNullKeyMemberInput, bool allowNull _sb.RenderArgumentsWithType(fieldsAndProperties, @" ", ",", trailingComma: true, useNullableTypes: allowNullKeyMemberInput); + if (_state.HasKeyMember) + { + var providerArgumentName = _state.KeyMember.Member.ArgumentName.Escaped == "provider" ? "formatProvider" : "provider"; + _sb.Append(@" + global::System.IFormatProvider? ").Append(providerArgumentName).Append(","); + } + _sb.Append(@" out ").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" obj) {"); @@ -420,7 +451,7 @@ private void GenerateValidateMethod(bool allowNullKeyMemberInput, bool allowNull if (emptyStringYieldsNull) { _sb.Append(@" - if(global::System.String.IsNullOrWhiteSpace(").Append(_state.KeyMember.Member.ArgumentName).Append(@")) + if(global::System.String.IsNullOrWhiteSpace(").Append(_state.KeyMember.Member.ArgumentName.Escaped).Append(@")) { obj = default; return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; @@ -430,7 +461,7 @@ private void GenerateValidateMethod(bool allowNullKeyMemberInput, bool allowNull else if (allowNullOutput) { _sb.Append(@" - if(").Append(_state.KeyMember.Member.ArgumentName).Append(@" is null) + if(").Append(_state.KeyMember.Member.ArgumentName.Escaped).Append(@" is null) { obj = default; return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; @@ -440,10 +471,10 @@ private void GenerateValidateMethod(bool allowNullKeyMemberInput, bool allowNull else if (_state.KeyMember.Member.IsReferenceType) { _sb.Append(@" - if(").Append(_state.KeyMember.Member.ArgumentName).Append(@" is null) + if(").Append(_state.KeyMember.Member.ArgumentName.Escaped).Append(@" is null) { obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult(""The argument '").Append(_state.KeyMember.Member.ArgumentName).Append(@"' must not be null."", global::Thinktecture.SingleItem.Collection(nameof(").Append(_state.TypeFullyQualified).Append(".").Append(_state.KeyMember.Member.Name).Append(@"))); + return new global::System.ComponentModel.DataAnnotations.ValidationResult(""The argument '").Append(_state.KeyMember.Member.ArgumentName.Escaped).Append(@"' must not be null."", global::Thinktecture.SingleItem.Collection(nameof(").Append(_state.TypeFullyQualified).Append(".").Append(_state.KeyMember.Member.Name).Append(@"))); } "); } @@ -498,7 +529,7 @@ private void GenerateValidateFactoryArguments() _sb.Append("static partial ").Append(_state.FactoryValidationReturnType ?? "void").Append(" ").Append(Constants.Methods.VALIDATE_FACTORY_ARGUMENTS).Append("(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult"); - _sb.RenderArgumentsWithType(fieldsAndProperties, "ref ", leadingComma: true); + _sb.RenderArgumentsWithType(fieldsAndProperties, "ref ", leadingComma: true, addAllowNullNotNullCombi: true); _sb.Append(");"); } @@ -593,7 +624,7 @@ private void GenerateConstructor() foreach (var memberInfo in fieldsAndProperties) { _sb.Append(@" - this.").Append(memberInfo.Name).Append(" = ").Append(memberInfo.ArgumentName).Append(";"); + this.").Append(memberInfo.Name).Append(" = ").Append(memberInfo.ArgumentName.Escaped).Append(";"); } } @@ -726,8 +757,13 @@ public override int GetHashCode() hashCode.Add(this.").Append(member.Name); if (equalityComparerAccessor is not null) + { _sb.Append(", ").Append(equalityComparerAccessor).Append(".EqualityComparer"); + if (member is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }) + _sb.Append("!"); + } + _sb.Append(");"); } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSettings.cs index b03fc050..bd579aee 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSettings.cs @@ -3,25 +3,31 @@ namespace Thinktecture.CodeAnalysis.ValueObjects; public readonly struct ValueObjectSettings : IEquatable { private readonly AllValueObjectSettings _allSettings; + private readonly AttributeInfo _attributeInfo; public bool SkipFactoryMethods => _allSettings.SkipFactoryMethods; public bool SkipToString => _allSettings.SkipToString; public bool EmptyStringInFactoryMethodsYieldsNull => _allSettings.EmptyStringInFactoryMethodsYieldsNull; public bool NullInFactoryMethodsYieldsNull => _allSettings.NullInFactoryMethodsYieldsNull; public string DefaultInstancePropertyName => _allSettings.DefaultInstancePropertyName; + public IReadOnlyList DesiredFactories => _attributeInfo.DesiredFactories; - public ValueObjectSettings(AllValueObjectSettings allSettings) + public ValueObjectSettings( + AllValueObjectSettings allSettings, + AttributeInfo attributeInfo) { _allSettings = allSettings; + _attributeInfo = attributeInfo; } public bool Equals(ValueObjectSettings other) { - return _allSettings.SkipFactoryMethods == other._allSettings.SkipFactoryMethods - && _allSettings.SkipToString == other._allSettings.SkipToString - && _allSettings.EmptyStringInFactoryMethodsYieldsNull == other._allSettings.EmptyStringInFactoryMethodsYieldsNull - && _allSettings.NullInFactoryMethodsYieldsNull == other._allSettings.NullInFactoryMethodsYieldsNull - && _allSettings.DefaultInstancePropertyName == other._allSettings.DefaultInstancePropertyName; + return SkipFactoryMethods == other.SkipFactoryMethods + && SkipToString == other.SkipToString + && EmptyStringInFactoryMethodsYieldsNull == other.EmptyStringInFactoryMethodsYieldsNull + && NullInFactoryMethodsYieldsNull == other.NullInFactoryMethodsYieldsNull + && DefaultInstancePropertyName == other.DefaultInstancePropertyName + && DesiredFactories.EqualsTo(other.DesiredFactories); } public override bool Equals(object? obj) @@ -33,11 +39,12 @@ public override int GetHashCode() { unchecked { - var hashCode = _allSettings.SkipFactoryMethods.GetHashCode(); - hashCode = (hashCode * 397) ^ _allSettings.SkipToString.GetHashCode(); - hashCode = (hashCode * 397) ^ _allSettings.EmptyStringInFactoryMethodsYieldsNull.GetHashCode(); - hashCode = (hashCode * 397) ^ _allSettings.NullInFactoryMethodsYieldsNull.GetHashCode(); - hashCode = (hashCode * 397) ^ _allSettings.DefaultInstancePropertyName.GetHashCode(); + var hashCode = SkipFactoryMethods.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipToString.GetHashCode(); + hashCode = (hashCode * 397) ^ EmptyStringInFactoryMethodsYieldsNull.GetHashCode(); + hashCode = (hashCode * 397) ^ NullInFactoryMethodsYieldsNull.GetHashCode(); + hashCode = (hashCode * 397) ^ DefaultInstancePropertyName.GetHashCode(); + hashCode = (hashCode * 397) ^ DesiredFactories.ComputeHashCode(); return hashCode; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs index 33e67802..53bd964f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs @@ -44,7 +44,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) InitializeSerializerGenerators(context, validStates, options); InitializeFormattableCodeGenerator(context, keyedValueObjects, options); InitializeComparableCodeGenerator(context, keyedValueObjects, options); - InitializeParsableCodeGenerator(context, keyedValueObjects, options); + InitializeParsableCodeGenerator(context, validStates, options); InitializeComparisonOperatorsCodeGenerator(context, keyedValueObjects, options); InitializeEqualityComparisonOperatorsCodeGenerator(context, keyedValueObjects, options); InitializeAdditionOperatorsCodeGenerator(context, keyedValueObjects, options); @@ -80,14 +80,14 @@ private void InitializeSerializerGenerators(IncrementalGeneratorInitializationCo : states.Distinct().ToImmutableArray()) .WithComparer(new SetComparer()); - validStates = validStates.Where(state => !state.Settings.SkipFactoryMethods); + validStates = validStates.Where(state => !state.Settings.SkipFactoryMethods || state.AttributeInfo.DesiredFactories.Any(f => f.UseForSerialization != SerializationFrameworks.None)); var keyedSerializerGeneratorStates = validStates.SelectMany((state, _) => { - if (!state.State.HasKeyMember) + if (!state.State.HasKeyMember && state.AttributeInfo.DesiredFactories.All(f => f.UseForSerialization == SerializationFrameworks.None)) return ImmutableArray.Empty; - var serializerState = new KeyedSerializerGeneratorState(state.State, state.State.KeyMember.Member, state.AttributeInfo); + var serializerState = new KeyedSerializerGeneratorState(state.State, state.State.KeyMember?.Member, state.AttributeInfo); return ImmutableArray.Create(serializerState); }) @@ -95,7 +95,7 @@ private void InitializeSerializerGenerators(IncrementalGeneratorInitializationCo .SelectMany((tuple, _) => ImmutableArray.CreateRange(tuple.Right, (factory, state) => (State: state, Factory: factory), tuple.Left)) .Where(tuple => { - if (tuple.Factory.MustGenerateCode(tuple.State.AttributeInfo)) + if (tuple.Factory.MustGenerateCode(tuple.State)) { Logger.LogDebug("Code generator must generate code.", namespaceAndName: tuple.State, factory: tuple.Factory); return true; @@ -118,7 +118,7 @@ private void InitializeSerializerGenerators(IncrementalGeneratorInitializationCo .SelectMany((tuple, _) => ImmutableArray.CreateRange(tuple.Right, (factory, state) => (State: state, Factory: factory), tuple.Left)) .Where(tuple => { - if (tuple.Factory.MustGenerateCode(tuple.State.AttributeInfo)) + if (tuple.Factory.MustGenerateCode(tuple.State)) { Logger.LogDebug("Code generator must generate code.", namespaceAndName: tuple.State, factory: tuple.Factory); return true; @@ -155,14 +155,15 @@ private void InitializeComparableCodeGenerator(IncrementalGeneratorInitializatio InitializeComparableCodeGenerator(context, comparables, options); } - private void InitializeParsableCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) + private void InitializeParsableCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) { var parsables = validStates - .Select((state, _) => new ParsableGeneratorState(state.Type, - state.KeyMember.Member, + .Select((state, _) => new ParsableGeneratorState(state.State, + state.State.KeyMember?.Member, state.Settings.SkipIParsable, - state.KeyMember.Member.IsParsable, - false)); + state.State.KeyMember?.Member.IsParsable ?? false, + false, + state.AttributeInfo.DesiredFactories.Any(t => t.SpecialType == SpecialType.System_String))); InitializeParsableCodeGenerator(context, parsables, options); } @@ -353,8 +354,9 @@ private bool IsValueObjectCandidate(TypeDeclarationSyntax typeDeclaration) if (factory is null) return new SourceGenContext(new SourceGenError("Could not fetch type information for code generation of a smart enum", tds)); + var attributeInfo = new AttributeInfo(type); var settings = new AllValueObjectSettings(context.Attributes[0]); - var state = new ValueObjectSourceGeneratorState(factory, type, new ValueObjectSettings(settings), cancellationToken); + var state = new ValueObjectSourceGeneratorState(factory, type, new ValueObjectSettings(settings, attributeInfo), cancellationToken); if (state.HasKeyMember) { @@ -377,8 +379,6 @@ private bool IsValueObjectCandidate(TypeDeclarationSyntax typeDeclaration) Logger.LogDebug("The type declaration is a valid complex value object", namespaceAndName: state); } - var attributeInfo = new AttributeInfo(type); - return new SourceGenContext(new ValidSourceGenState(state, settings, attributeInfo)); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs index 1a465082..62469208 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs @@ -95,6 +95,12 @@ public static OperatorsGeneration FindEqualityComparisonOperators(this Attribute return GetBooleanParameterValue(attributeData, "SkipMapMethods"); } + public static SerializationFrameworks FindUseForSerialization(this AttributeData attributeData) + { + return (SerializationFrameworks?)GetIntegerParameterValue(attributeData, "UseForSerialization") + ?? SerializationFrameworks.None; + } + public static (ITypeSymbol ComparerType, ITypeSymbol ItemType)? GetComparerTypes(this AttributeData attributeData) { if (attributeData.AttributeClass is not { } attributeClass || attributeClass.TypeKind == TypeKind.Error) @@ -123,10 +129,15 @@ public static (ITypeSymbol ComparerType, ITypeSymbol ItemType)? GetComparerTypes private static OperatorsGeneration GetOperatorsGeneration(AttributeData attributeData, string name) { - return (OperatorsGeneration?)(int?)attributeData.FindNamedAttribute(name).Value + return (OperatorsGeneration?)GetIntegerParameterValue(attributeData, name) ?? OperatorsGeneration.Default; } + private static int? GetIntegerParameterValue(AttributeData attributeData, string name) + { + return (int?)attributeData.FindNamedAttribute(name).Value; + } + private static bool? GetBooleanParameterValue(AttributeData attributeData, string name) { return (bool?)attributeData.FindNamedAttribute(name).Value; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumSettingsExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumSettingsExtensions.cs index f723bf9a..8754bfd2 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumSettingsExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumSettingsExtensions.cs @@ -5,13 +5,13 @@ namespace Thinktecture; public static class EnumSettingsExtensions { - public static IMemberState CreateKeyProperty(this EnumSettings settings, ITypedMemberState keyMemberState) + public static IMemberState CreateKeyProperty(this AllEnumSettings settings, ITypedMemberState keyMemberState) { var keyPropertyName = settings.GetKeyPropertyName(); return new DefaultMemberState(keyMemberState, keyPropertyName, keyPropertyName.MakeArgumentName()); } - private static string GetKeyPropertyName(this EnumSettings settings) + private static string GetKeyPropertyName(this AllEnumSettings settings) { var name = settings.KeyPropertyName; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/NamedTypeSymbolExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/NamedTypeSymbolExtensions.cs index 9987f610..1d759176 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/NamedTypeSymbolExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/NamedTypeSymbolExtensions.cs @@ -45,7 +45,7 @@ private static IReadOnlyList GetConstructors( var parameters = ctor.Parameters.IsDefaultOrEmpty ? (IReadOnlyList)Array.Empty() : ctor.Parameters - .Select(p => new DefaultMemberState(factory.Create(p.Type), p.Name, p.Name)) + .Select(p => new DefaultMemberState(factory.Create(p.Type), p.Name, new ArgumentName(p.Name, p.Name))) .ToList(); var ctorState = new ConstructorState(parameters); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringBuilderExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringBuilderExtensions.cs index a84a860e..d224d25f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringBuilderExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringBuilderExtensions.cs @@ -1,4 +1,5 @@ using System.Text; +using Microsoft.CodeAnalysis; using Thinktecture.CodeAnalysis; using Thinktecture.CodeAnalysis.SmartEnums; @@ -8,7 +9,7 @@ public static class StringBuilderExtensions { public static void GenerateStructLayoutAttributeIfRequired(this StringBuilder sb, EnumSourceGeneratorState state) { - if (state is { IsReferenceType: false, HasStructLayoutAttribute: false }) + if (state is { IsReferenceType: false, Settings.HasStructLayoutAttribute: false }) { sb.Append(@" [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Auto)]"); @@ -27,7 +28,7 @@ public static void RenderArguments( sb.Append(", "); var member = members[i]; - sb.Append(prefix).Append(member.ArgumentName); + sb.Append(prefix).Append(member.ArgumentName.Escaped); } } @@ -38,7 +39,8 @@ public static void RenderArgumentsWithType( string comma = ", ", bool leadingComma = false, bool trailingComma = false, - bool useNullableTypes = false) + bool useNullableTypes = false, + bool addAllowNullNotNullCombi = false) { for (var i = 0; i < members.Count; i++) { @@ -46,12 +48,16 @@ public static void RenderArgumentsWithType( sb.Append(comma); var member = members[i]; + + if (addAllowNullNotNullCombi && member.IsReferenceType && member.NullableAnnotation != NullableAnnotation.Annotated) + sb.Append("[global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] "); + sb.Append(prefix).Append(member.TypeFullyQualifiedWithNullability); if (useNullableTypes && !member.IsNullableStruct) sb.Append("?"); - sb.Append(' ').Append(member.ArgumentName); + sb.Append(' ').Append(member.ArgumentName.Escaped); } if (trailingComma && members.Count > 0) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringExtensions.cs index c7bb4e57..97c1c50e 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringExtensions.cs @@ -1,15 +1,34 @@ +using Thinktecture.CodeAnalysis; + namespace Thinktecture; public static class StringExtensions { - public static string MakeArgumentName(this string name) + private static readonly HashSet _reservedIdentifiers = new(StringComparer.Ordinal) + { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", + "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", + "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", + "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", + "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", + "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", + "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", + "void", "volatile", "while" + }; + + public static ArgumentName MakeArgumentName(this string name) { - if (name.Length == 1) - return name.ToLowerInvariant(); + name = name.Length switch + { + 1 => name.ToLowerInvariant(), + _ => name.StartsWith("_", StringComparison.Ordinal) + ? $"{Char.ToLowerInvariant(name[1])}{name.Substring(2)}" + : $"{Char.ToLowerInvariant(name[0])}{name.Substring(1)}" + }; + + var escaped = _reservedIdentifiers.Contains(name) ? $"@{name}" : name; - return name.StartsWith("_", StringComparison.Ordinal) - ? $"{Char.ToLowerInvariant(name[1])}{name.Substring(2)}" - : $"{Char.ToLowerInvariant(name[0])}{name.Substring(1)}"; + return new ArgumentName(name, escaped); } public static string? TrimAndNullify(this string? text) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs index 8c67ad90..d9bf2dba 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs @@ -40,6 +40,11 @@ public static bool IsMessagePackFormatterAttribute(this ITypeSymbol type) return type is { Name: "MessagePackFormatterAttribute", ContainingNamespace: { Name: "MessagePack", ContainingNamespace.IsGlobalNamespace: true } }; } + public static bool IsValueObjectFactoryAttribute(this INamedTypeSymbol type) + { + return type is { Name: "ValueObjectFactoryAttribute", TypeArguments.Length: 1, ContainingNamespace: { Name: "Thinktecture", ContainingNamespace.IsGlobalNamespace: true } }; + } + public static bool IsStructLayoutAttribute(this ITypeSymbol type) { return type is diff --git a/src/Thinktecture.Runtime.Extensions/IKeyedValueObject.cs b/src/Thinktecture.Runtime.Extensions/IKeyedValueObject.cs index 1276db49..7b14137f 100644 --- a/src/Thinktecture.Runtime.Extensions/IKeyedValueObject.cs +++ b/src/Thinktecture.Runtime.Extensions/IKeyedValueObject.cs @@ -1,6 +1,3 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.CompilerServices; - namespace Thinktecture; /// @@ -14,14 +11,9 @@ public interface IKeyedValueObject /// Common interface of keyed value objects. /// /// Type of the key member. -public interface IKeyedValueObject : IKeyedValueObject +public interface IKeyedValueObject : IValueObjectConverter, IKeyedValueObject where TKey : notnull { - /// - /// Gets the key of the item. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - TKey GetKey(); } /// @@ -32,14 +24,7 @@ public interface IKeyedValueObject : IKeyedValueObject /// /// Don't implement this interface directly. It will be implemented by a source generator. /// -public interface IKeyedValueObject : IKeyedValueObject +public interface IKeyedValueObject : IKeyedValueObject, IValueObjectFactory where TKey : notnull { - /// - /// Validate the and returns an if the validation succeeded. - /// - /// The value to validate. - /// Item with key property equals to the provided . - /// Validation result. - static abstract ValidationResult? Validate(TKey? key, out T? item); } diff --git a/src/Thinktecture.Runtime.Extensions/IValueObjectConverter.cs b/src/Thinktecture.Runtime.Extensions/IValueObjectConverter.cs new file mode 100644 index 00000000..33c87b8d --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions/IValueObjectConverter.cs @@ -0,0 +1,17 @@ +namespace Thinktecture; + +/// +/// Converts value object to an instance of type . +/// +/// Type to convert the value object to. +/// +/// Don't use this interface directly. It will be used by a source generator. +/// +public interface IValueObjectConverter + where T : notnull +{ + /// + /// Converts the value object to type . + /// + T ToValue(); +} diff --git a/src/Thinktecture.Runtime.Extensions/IValueObjectFactory.cs b/src/Thinktecture.Runtime.Extensions/IValueObjectFactory.cs new file mode 100644 index 00000000..c4519168 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions/IValueObjectFactory.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture; + +/// +/// Marker interface. +/// +/// +/// Don't use this interface directly. It will be used by a source generator. +/// +// ReSharper disable once UnusedTypeParameter +public interface IValueObjectFactory + where TValue : notnull +// ReSharper disable once RedundantTypeDeclarationBody +{ +} + +/// +/// A factory for creation of instances of . +/// +/// Type of the value object. +/// Type of the value to create the item from. +/// +/// Don't use this interface directly. It will be used by a source generator. +/// +public interface IValueObjectFactory : IValueObjectFactory + where TValue : notnull +{ + /// + /// Validates the and returns an if the validation succeeded. + /// + /// The value to validate. + /// An object that provides culture-specific formatting information. + /// Item with key property equals to the provided . + /// Validation result. + static abstract ValidationResult? Validate(TValue? value, IFormatProvider? provider, out T? item); +} diff --git a/src/Thinktecture.Runtime.Extensions/SerializationFrameworks.cs b/src/Thinktecture.Runtime.Extensions/SerializationFrameworks.cs new file mode 100644 index 00000000..aa2e2913 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions/SerializationFrameworks.cs @@ -0,0 +1,38 @@ +namespace Thinktecture; + +/// +/// Points to serialization frameworks. +/// +[Flags] +public enum SerializationFrameworks +{ + /// + /// No serialization frameworks + /// + None = 0, + + /// + /// Points to System.Text.Json + /// + SystemTextJson = 1 << 0, + + /// + /// Points to Newtonsoft.Json + /// + NewtonsoftJson = 1 << 1, + + /// + /// Points to System.Text.Json and Newtonsoft.Json + /// + Json = SystemTextJson | NewtonsoftJson, + + /// + /// Points to MessagePack + /// + MessagePack = 1 << 2, + + /// + /// Points to System.Text.Json, Newtonsoft.Json and MessagePack + /// + All = Json | MessagePack +} diff --git a/src/Thinktecture.Runtime.Extensions/ValueObjectFactoryAttribute.cs b/src/Thinktecture.Runtime.Extensions/ValueObjectFactoryAttribute.cs new file mode 100644 index 00000000..fd4cd30f --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions/ValueObjectFactoryAttribute.cs @@ -0,0 +1,42 @@ +namespace Thinktecture; + +/// +/// Signals a desire for an (additional) factory for creation of value objects from a value of type +/// +public abstract class ValueObjectFactoryAttribute : Attribute +{ + /// + /// Type of the value to create value objects from. + /// + public Type Type { get; } + + /// + /// Serialization frameworks that should use the type for serialization and deserialization. + /// + public SerializationFrameworks UseForSerialization { get; set; } + + /// + /// Initializes new instance of type . + /// + /// Type of the value to create value objects from. + protected ValueObjectFactoryAttribute(Type type) + { + Type = type; + } +} + +/// +/// Signals a desire for an (additional) factory for creation of value objects from a value of type . +/// +/// Type of the value to be able to create a value object from. +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)] +public sealed class ValueObjectFactoryAttribute : ValueObjectFactoryAttribute +{ + /// + /// Initializes new instance of type . + /// + public ValueObjectFactoryAttribute() + : base(typeof(T)) + { + } +} diff --git a/src/Thinktecture.Runtime.Extensions/ValueObjectTypeConverter.cs b/src/Thinktecture.Runtime.Extensions/ValueObjectTypeConverter.cs index 0a4ae1d6..a141327b 100644 --- a/src/Thinktecture.Runtime.Extensions/ValueObjectTypeConverter.cs +++ b/src/Thinktecture.Runtime.Extensions/ValueObjectTypeConverter.cs @@ -9,7 +9,7 @@ namespace Thinktecture; /// Type of the concrete enumeration. /// Type of the key. public class ValueObjectTypeConverter : TypeConverter - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter where TKey : notnull { private static readonly Type _type = typeof(T); @@ -54,22 +54,22 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina return obj; if (value is TKey key) - return ConvertFrom(key); + return ConvertFromKey(key, culture); if (_keyConverter is not null) { var convertedValue = _keyConverter.ConvertFrom(context, culture, value); if (convertedValue is TKey convertedKey) - return ConvertFrom(convertedKey); + return ConvertFromKey(convertedKey, culture); } return base.ConvertFrom(context, culture, value); } - private static T? ConvertFrom(TKey key) + private static T? ConvertFromKey(TKey key, IFormatProvider? culture) { - var validationResult = T.Validate(key, out var item); + var validationResult = T.Validate(key, culture, out var item); if (validationResult is null || _mayReturnInvalidObjects) return item; @@ -93,13 +93,13 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina var underlyingType = Nullable.GetUnderlyingType(destinationType); if (destinationType == _keyType || underlyingType == _keyType) - return item.GetKey(); + return item.ToValue(); if (destinationType == _type || underlyingType == _type) return value; if (_keyConverter is not null) - return _keyConverter.ConvertTo(context, culture, item.GetKey(), destinationType); + return _keyConverter.ConvertTo(context, culture, item.ToValue(), destinationType); } return base.ConvertTo(context, culture, value, destinationType); diff --git a/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderProviderTests/GetBinder.cs b/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderProviderTests/GetBinder.cs index 54e9b555..d6184e0b 100644 --- a/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderProviderTests/GetBinder.cs +++ b/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderProviderTests/GetBinder.cs @@ -31,6 +31,20 @@ public void Should_return_binder_for_string_based_value_type() binder.Should().BeOfType>(); } + [Fact] + public void Should_return_string_base_binder_specified_by_ValueObjectFactoryAttribute_of_value_object_() + { + var binder = GetModelBinder(); + binder.Should().BeOfType>(); + } + + [Fact] + public void Should_return_string_base_binder_specified_by_ValueObjectFactoryAttribute_smart_enum() + { + var binder = GetModelBinder(); + binder.Should().BeOfType>(); + } + [Fact] public void Should_return_null_for_non_enums_and_non_value_types() { diff --git a/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderTests/BindModelAsync.cs b/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderTests/BindModelAsync.cs index a1fbf20b..bbe0f0b1 100644 --- a/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderTests/BindModelAsync.cs +++ b/test/Thinktecture.Runtime.Extensions.AspNetCore.Tests/AspNetCore/ModelBinding/ValueObjectModelBinderTests/BindModelAsync.cs @@ -63,7 +63,7 @@ public async Task Should_not_bind_validatable_enum_if_value_not_matching_key_typ var ctx = await BindAsync("item1"); ctx.ModelState.ErrorCount.Should().Be(1); - ctx.ModelState[ctx.ModelName].Errors.Should().BeEquivalentTo(new[] { new ModelError("The value 'item1' is not valid.") }); + ctx.ModelState[ctx.ModelName]!.Errors.Should().BeEquivalentTo(new[] { new ModelError("The value 'item1' is not valid.") }); ctx.Result.IsModelSet.Should().BeFalse(); } @@ -73,7 +73,7 @@ public async Task Should_not_bind_enum_if_key_is_unknown() var ctx = await BindAsync("42"); ctx.ModelState.ErrorCount.Should().Be(1); - ctx.ModelState[ctx.ModelName].Errors.Should().BeEquivalentTo(new[] { new ModelError("There is no item of type 'ValidIntegerEnum' with the identifier '42'.") }); + ctx.ModelState[ctx.ModelName]!.Errors.Should().BeEquivalentTo(new[] { new ModelError("There is no item of type 'ValidIntegerEnum' with the identifier '42'.") }); ctx.Result.IsModelSet.Should().BeFalse(); } @@ -83,7 +83,7 @@ public async Task Should_not_bind_enum_if_value_not_matching_key_type() var ctx = await BindAsync("item1"); ctx.ModelState.ErrorCount.Should().Be(1); - ctx.ModelState[ctx.ModelName].Errors.Should().BeEquivalentTo(new[] { new ModelError("The value 'item1' is not valid.") }); + ctx.ModelState[ctx.ModelName]!.Errors.Should().BeEquivalentTo(new[] { new ModelError("The value 'item1' is not valid.") }); ctx.Result.IsModelSet.Should().BeFalse(); } @@ -196,9 +196,49 @@ public async Task Should_return_error_if_value_violates_validation_rules() ctx.Result.IsModelSet.Should().BeFalse(); } + [Fact] + public async Task Should_bind_successfully_value_object_having_string_base_factory_specified_by_ValueObjectFactoryAttribute() + { + var ctx = await BindAsync("1:2"); + + ctx.ModelState.ErrorCount.Should().Be(0); + ctx.Result.IsModelSet.Should().BeTrue(); + ctx.Result.Model.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } + + [Fact] + public async Task Should_return_error_when_binding_value_object_having_string_base_factory_specified_by_ValueObjectFactoryAttribute() + { + var ctx = await BindAsync("1"); + + ctx.ModelState.ErrorCount.Should().Be(1); + ctx.ModelState[ctx.ModelName]!.Errors.Should().BeEquivalentTo(new[] { new ModelError("Invalid format.") }); + ctx.Result.IsModelSet.Should().BeFalse(); + } + + [Fact] + public async Task Should_bind_successfully_smart_enum_having_string_base_factory_specified_by_ValueObjectFactoryAttribute() + { + var ctx = await BindAsync("=1="); + + ctx.ModelState.ErrorCount.Should().Be(0); + ctx.Result.IsModelSet.Should().BeTrue(); + ctx.Result.Model.Should().Be(EnumWithFactory.Item1); + } + + [Fact] + public async Task Should_return_error_when_binding_smart_enum_having_string_base_factory_specified_by_ValueObjectFactoryAttribute() + { + var ctx = await BindAsync("A"); + + ctx.ModelState.ErrorCount.Should().Be(1); + ctx.ModelState[ctx.ModelName]!.Errors.Should().BeEquivalentTo(new[] { new ModelError("Unknown item 'A'") }); + ctx.Result.IsModelSet.Should().BeFalse(); + } + private static async Task BindAsync( string value) - where T : IKeyedValueObject + where T : IValueObjectFactory { var binder = new ValueObjectModelBinder(NullLoggerFactory.Instance); var query = new Dictionary { { "name", value } }; diff --git a/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/ReadJson.cs b/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/ReadJson.cs index a289af52..5b957af0 100644 --- a/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/ReadJson.cs +++ b/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/ReadJson.cs @@ -25,14 +25,14 @@ public class ReadJson : JsonTestsBase [Fact] public void Should_deserialize_enum_when_null_and_default_unless_enum_and_underlying_are_both_null() { - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("0").Should().Be(default(TestSmartEnum_Struct_IntBased)); // default(int) is 0 - Deserialize>("null").Should().Be(default(TestSmartEnum_Struct_StringBased)); - - FluentActions.Invoking(() => Deserialize>("null")).Should() + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("0").Should().Be(default(TestSmartEnum_Struct_IntBased)); // default(int) is 0 + DeserializeWithConverter>("null").Should().Be(default(TestSmartEnum_Struct_StringBased)); + + FluentActions.Invoking(() => DeserializeWithConverter>("null")).Should() .Throw() .WithInnerException().WithMessage("Cannot get the value of a token type 'Null' as a number."); } @@ -40,26 +40,26 @@ public void Should_deserialize_enum_when_null_and_default_unless_enum_and_underl [Fact] public void Should_deserialize_keyed_value_object_when_null_and_default_unless_enum_and_underlying_are_both_null() { - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("null").Should().Be(null); - Deserialize>("0").Should().Be(default); // default(int) is 0 - Deserialize>("null").Should().Be(default); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("0").Should().Be(default); // default(int) is 0 + DeserializeWithConverter>("null").Should().Be(default); // NullInFactoryMethodsYieldsNull - Deserialize>("null").Should().Be(null); - FluentActions.Invoking(() => Deserialize>("\"\"")) + DeserializeWithConverter>("null").Should().Be(null); + FluentActions.Invoking(() => DeserializeWithConverter>("\"\"")) .Should().Throw().WithMessage("Property cannot be empty."); - FluentActions.Invoking(() => Deserialize>("\" \"")) + FluentActions.Invoking(() => DeserializeWithConverter>("\" \"")) .Should().Throw().WithMessage("Property cannot be empty."); // EmptyStringInFactoryMethodsYieldsNull - Deserialize>("null").Should().Be(null); - Deserialize>("\"\"").Should().Be(null); - Deserialize>("\" \"").Should().Be(null); + DeserializeWithConverter>("null").Should().Be(null); + DeserializeWithConverter>("\"\"").Should().Be(null); + DeserializeWithConverter>("\" \"").Should().Be(null); - FluentActions.Invoking(() => Deserialize>("null")).Should() + FluentActions.Invoking(() => DeserializeWithConverter>("null")).Should() .Throw() .WithInnerException().WithMessage("Cannot get the value of a token type 'Null' as a number."); } @@ -67,16 +67,16 @@ public void Should_deserialize_keyed_value_object_when_null_and_default_unless_e [Fact] public void Should_deserialize_value_object_if_null_and_default() { - Deserialize("null").Should().Be(null); - Deserialize("null").Should().Be(null); - Deserialize("null").Should().Be(default(TestValueObject_Complex_Struct)); + DeserializeWithConverter("null").Should().Be(null); + DeserializeWithConverter("null").Should().Be(null); + DeserializeWithConverter("null").Should().Be(default(TestValueObject_Complex_Struct)); } [Theory] [MemberData(nameof(DataForStringBasedEnumTest))] public void Should_deserialize_string_based_enum(TestEnum expectedValue, string json) { - var value = Deserialize>(json); + var value = DeserializeWithConverter>(json); value.Should().Be(expectedValue); } @@ -85,7 +85,7 @@ public void Should_deserialize_string_based_enum(TestEnum expectedValue, string [MemberData(nameof(DataForIntBasedEnumTest))] public void Should_deserialize_int_based_enum(IntegerEnum expectedValue, string json) { - var value = Deserialize>(json); + var value = DeserializeWithConverter>(json); value.Should().Be(expectedValue); } @@ -94,7 +94,7 @@ public void Should_deserialize_int_based_enum(IntegerEnum expectedValue, string [MemberData(nameof(DataForClassWithStringBasedEnumTest))] public void Should_deserialize_class_containing_string_based_enum(ClassWithStringBasedEnum expectedValue, string json, bool ignoreNullValues = false) { - var value = Deserialize>(json, ignoreNullValues: ignoreNullValues); + var value = DeserializeWithConverter>(json, ignoreNullValues: ignoreNullValues); value.Should().BeEquivalentTo(expectedValue); } @@ -103,7 +103,7 @@ public void Should_deserialize_class_containing_string_based_enum(ClassWithStrin [MemberData(nameof(DataForClassWithIntBasedEnumTest))] public void Should_deserialize_class_containing_int_based_enum(ClassWithIntBasedEnum expectedValue, string json, bool ignoreNullValues = false) { - var value = Deserialize>(json, ignoreNullValues: ignoreNullValues); + var value = DeserializeWithConverter>(json, ignoreNullValues: ignoreNullValues); value.Should().BeEquivalentTo(expectedValue); } @@ -116,7 +116,7 @@ public void Should_deserialize_value_type_with_multiple_properties( JsonNamingPolicy namingPolicy = null, bool propertyNameCaseInsensitive = false) { - var value = Deserialize(json, namingPolicy, propertyNameCaseInsensitive); + var value = DeserializeWithConverter(json, namingPolicy, propertyNameCaseInsensitive); value.Should().BeEquivalentTo(expectedValueObject); } @@ -124,22 +124,38 @@ public void Should_deserialize_value_type_with_multiple_properties( [Fact] public void Should_throw_JsonException_if_enum_parsing_throws_UnknownEnumIdentifierException() { - FluentActions.Invoking(() => Deserialize>("\"invalid\"")) + FluentActions.Invoking(() => DeserializeWithConverter>("\"invalid\"")) .Should().Throw().WithMessage("There is no item of type 'ValidTestEnum' with the identifier 'invalid'."); } [Fact] public void Should_throw_JsonException_if_complex_value_object_is_not_a_json_object() { - FluentActions.Invoking(() => Deserialize("\"invalid\"")) + FluentActions.Invoking(() => DeserializeWithConverter("\"invalid\"")) .Should().Throw().WithMessage("Unexpected token \"String\" when trying to deserialize \"TestValueObject_Complex_Class\". Expected token: \"StartObject\"."); - FluentActions.Invoking(() => Deserialize("42")) + FluentActions.Invoking(() => DeserializeWithConverter("42")) .Should().Throw().WithMessage("Unexpected token \"Number\" when trying to deserialize \"TestValueObject_Complex_Class\". Expected token: \"StartObject\"."); - FluentActions.Invoking(() => Deserialize("[]")) + FluentActions.Invoking(() => DeserializeWithConverter("[]")) .Should().Throw().WithMessage("Unexpected token \"StartArray\" when trying to deserialize \"TestValueObject_Complex_Class\". Expected token: \"StartObject\"."); } - private static T Deserialize( + [Fact] + public void Should_deserialize_using_custom_factory_specified_by_ValueObjectFactoryAttribute() + { + var value = Deserialize("\"1:2\""); + + value.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } + + private static T Deserialize( + string json, + JsonNamingPolicy namingPolicy = null) + where T : IValueObjectFactory, IValueObjectConverter + { + return DeserializeWithConverter(json, namingPolicy); + } + + private static T DeserializeWithConverter( string json, JsonNamingPolicy namingPolicy = null, bool propertyNameCaseInsensitive = false, diff --git a/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/WriteJson.cs b/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/WriteJson.cs index 221333ff..31a7047e 100644 --- a/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/WriteJson.cs +++ b/test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/WriteJson.cs @@ -30,38 +30,38 @@ public class WriteJson : JsonTestsBase [Fact] public void Should_deserialize_enum_if_null_and_default() { - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(default).Should().Be("0"); - Serialize>(default).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(default).Should().Be("0"); + SerializeWithConverter>(default).Should().Be("null"); } [Fact] public void Should_deserialize_keyed_value_object_if_null_and_default() { - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(null).Should().Be("null"); - Serialize>(default).Should().Be("0"); - Serialize>(default).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(null).Should().Be("null"); + SerializeWithConverter>(default).Should().Be("0"); + SerializeWithConverter>(default).Should().Be("null"); } [Fact] public void Should_deserialize_value_object_if_null_and_default() { - Serialize(null).Should().Be("null"); - Serialize(null).Should().Be("null"); - Serialize(default).Should().Be("{\"Property1\":null,\"Property2\":null}"); + SerializeWithConverter(null).Should().Be("null"); + SerializeWithConverter(null).Should().Be("null"); + SerializeWithConverter(default).Should().Be("{\"Property1\":null,\"Property2\":null}"); } [Theory] [MemberData(nameof(DataForStringBasedEnumTest))] public void Should_serialize_string_based_enum(TestEnum enumValue, string expectedJson) { - var json = Serialize>(enumValue); + var json = SerializeWithConverter>(enumValue); json.Should().Be(expectedJson); } @@ -70,7 +70,7 @@ public void Should_serialize_string_based_enum(TestEnum enumValue, string expect [MemberData(nameof(DataForIntBasedEnumTest))] public void Should_serialize_int_based_enum(IntegerEnum enumValue, string expectedJson) { - var json = Serialize>(enumValue); + var json = SerializeWithConverter>(enumValue); json.Should().Be(expectedJson); } @@ -79,7 +79,7 @@ public void Should_serialize_int_based_enum(IntegerEnum enumValue, string expect [MemberData(nameof(DataForClassWithStringBasedEnumTest))] public void Should_serialize_class_containing_string_based_enum(ClassWithStringBasedEnum classWithEnum, string expectedJson, bool ignoreNullValues = false) { - var json = Serialize>(classWithEnum, null, ignoreNullValues); + var json = SerializeWithConverter>(classWithEnum, null, ignoreNullValues); json.Should().Be(expectedJson); } @@ -88,7 +88,7 @@ public void Should_serialize_class_containing_string_based_enum(ClassWithStringB [MemberData(nameof(DataForClassWithIntBasedEnumTest))] public void Should_serialize_class_containing_int_based_enum(ClassWithIntBasedEnum classWithEnum, string expectedJson, bool ignoreNullValues = false) { - var json = Serialize>(classWithEnum, null, ignoreNullValues); + var json = SerializeWithConverter>(classWithEnum, null, ignoreNullValues); json.Should().Be(expectedJson); } @@ -101,21 +101,38 @@ public void Should_serialize_value_type_with_3_properties( JsonNamingPolicy namingPolicy = null, JsonIgnoreCondition jsonIgnoreCondition = JsonIgnoreCondition.Never) { - var json = Serialize(valueObject, namingPolicy, jsonIgnoreCondition); + var json = SerializeWithConverter(valueObject, namingPolicy, jsonIgnoreCondition); json.Should().Be(expectedJson); } - private static string Serialize( + [Fact] + public void Should_deserialize_using_custom_factory_specified_by_ValueObjectFactoryAttribute() + { + var json = Serialize(BoundaryWithFactories.Create(1, 2)); + + json.Should().Be("\"1:2\""); + } + + private static string Serialize( + T value, + JsonNamingPolicy namingStrategy = null, + bool ignoreNullValues = false) + where T : IValueObjectFactory, IValueObjectConverter + { + return SerializeWithConverter(value, namingStrategy, ignoreNullValues); + } + + private static string SerializeWithConverter( T value, JsonNamingPolicy namingPolicy = null, bool ignoreNullValues = false) where TConverterFactory : JsonConverterFactory, new() { - return Serialize(value, namingPolicy, ignoreNullValues ? JsonIgnoreCondition.WhenWritingNull : JsonIgnoreCondition.Never); + return SerializeWithConverter(value, namingPolicy, ignoreNullValues ? JsonIgnoreCondition.WhenWritingNull : JsonIgnoreCondition.Never); } - private static string Serialize( + private static string SerializeWithConverter( T value, JsonNamingPolicy namingPolicy, JsonIgnoreCondition jsonIgnoreCondition) diff --git a/test/Thinktecture.Runtime.Extensions.MessagePack.Tests/Formatters/EnumMessagePackFormatterTests/RoundtripSerialize.cs b/test/Thinktecture.Runtime.Extensions.MessagePack.Tests/Formatters/EnumMessagePackFormatterTests/RoundtripSerialize.cs index 5d469a11..2926e251 100644 --- a/test/Thinktecture.Runtime.Extensions.MessagePack.Tests/Formatters/EnumMessagePackFormatterTests/RoundtripSerialize.cs +++ b/test/Thinktecture.Runtime.Extensions.MessagePack.Tests/Formatters/EnumMessagePackFormatterTests/RoundtripSerialize.cs @@ -114,4 +114,17 @@ public void Should_roundtrip_serialize_ValueObjectWithMultipleProperties(ValueOb value.Should().BeEquivalentTo(expectedValueObject); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Should_roundtrip_serialize_using_factory_specified_by_ValueObjectFactoryAttribute(bool withCustomResolver) + { + var options = withCustomResolver ? _options : StandardResolver.Options; + + var bytes = MessagePackSerializer.Serialize(BoundaryWithFactories.Create(1, 2), options, CancellationToken.None); + var value = MessagePackSerializer.Deserialize(bytes, options, CancellationToken.None); + + value.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } } diff --git a/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/ReadJson.cs b/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/ReadJson.cs index bd2c43e7..879e4aa5 100644 --- a/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/ReadJson.cs +++ b/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/ReadJson.cs @@ -89,6 +89,14 @@ public void Should_deserialize_int_based_enum(IntegerEnum expectedValue, string value.Should().Be(expectedValue); } + [Fact] + public void Should_deserialize_using_custom_factory_specified_by_ValueObjectFactoryAttribute() + { + var value = Deserialize("\"1:2\""); + + value.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } + [Theory] [MemberData(nameof(DataForValueObjectWithMultipleProperties))] public void Should_deserialize_value_type_with_multiple_properties( @@ -122,7 +130,7 @@ public void Should_throw_JsonException_if_complex_value_object_is_not_a_json_obj private static T Deserialize( string json, NamingStrategy namingStrategy = null) - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter { return DeserializeWithConverter>(json, namingStrategy); } @@ -130,7 +138,7 @@ private static T Deserialize( private static T? DeserializeNullableStruct( string json, NamingStrategy namingStrategy = null) - where T : struct, IKeyedValueObject + where T : struct, IValueObjectFactory, IValueObjectConverter { return DeserializeWithConverter>(json, namingStrategy); } @@ -138,7 +146,7 @@ private static T Deserialize( private static T DeserializeStruct( string json, NamingStrategy namingStrategy = null) - where T : struct, IKeyedValueObject + where T : struct, IValueObjectFactory, IValueObjectConverter { return DeserializeWithConverter>(json, namingStrategy); } diff --git a/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/WriteJson.cs b/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/WriteJson.cs index 6bfdce76..2bbd1cdd 100644 --- a/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/WriteJson.cs +++ b/test/Thinktecture.Runtime.Extensions.Newtonsoft.Json.Tests/Json/ValueObjectNewtonsoftJsonConverterTests/WriteJson.cs @@ -84,11 +84,19 @@ public void Should_serialize_value_type_with_multiple_properties( json.Should().Be(expectedJson); } + [Fact] + public void Should_deserialize_using_custom_factory_specified_by_ValueObjectFactoryAttribute() + { + var json = Serialize(BoundaryWithFactories.Create(1, 2)); + + json.Should().Be("\"1:2\""); + } + private static string Serialize( T value, NamingStrategy namingStrategy = null, NullValueHandling nullValueHandling = NullValueHandling.Include) - where T : IKeyedValueObject + where T : IValueObjectFactory, IValueObjectConverter { return SerializeWithConverter>(value, namingStrategy, nullValueHandling); } @@ -97,7 +105,7 @@ private static string SerializeNullableStruct( T? value, NamingStrategy namingStrategy = null, NullValueHandling nullValueHandling = NullValueHandling.Include) - where T : struct, IKeyedValueObject + where T : struct, IValueObjectFactory, IValueObjectConverter { return SerializeWithConverter>(value, namingStrategy, nullValueHandling); } @@ -106,7 +114,7 @@ private static string SerializeStruct( T value, NamingStrategy namingStrategy = null, NullValueHandling nullValueHandling = NullValueHandling.Include) - where T : struct, IKeyedValueObject + where T : struct, IValueObjectFactory, IValueObjectConverter { return SerializeWithConverter>(value, namingStrategy, nullValueHandling); } diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs index b48973e6..4379b8a9 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs @@ -23,6 +23,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -77,7 +79,7 @@ private TestEnum(string key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -123,9 +125,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -383,10 +386,16 @@ namespace Thinktecture.Tests; partial class TestEnum : global::System.IParsable { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestEnum? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + /// public static global::Thinktecture.Tests.TestEnum Parse(string s, global::System.IFormatProvider? provider) { - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out var result); + var validationResult = Validate(s, provider, out var result); if(validationResult is null) return result!; @@ -406,7 +415,7 @@ public static bool TryParse( return false; } - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out result!); + var validationResult = Validate(s, provider, out result!); return validationResult is null; } } @@ -604,10 +613,16 @@ namespace Thinktecture.Tests; partial class TestEnum : global::System.IParsable { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestEnum? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + /// public static global::Thinktecture.Tests.TestEnum Parse(string s, global::System.IFormatProvider? provider) { - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out var result); + var validationResult = Validate(s, provider, out var result); return result!; } @@ -623,7 +638,7 @@ public static bool TryParse( return false; } - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out result!); + var validationResult = Validate(s, provider, out result!); return true; } } @@ -637,11 +652,17 @@ namespace Thinktecture.Tests; partial class TestEnum : global::System.IParsable { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(int key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestEnum? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + /// public static global::Thinktecture.Tests.TestEnum Parse(string s, global::System.IFormatProvider? provider) { var key = int.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(key, out var result); + var validationResult = Validate(key, provider, out var result); if(validationResult is null) return result!; @@ -667,7 +688,7 @@ public static bool TryParse( return false; } - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(key, out result!); + var validationResult = Validate(key, provider, out result!); return validationResult is null; } } @@ -795,6 +816,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -849,7 +872,7 @@ private TestEnum(string key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -895,9 +918,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -1048,6 +1072,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -1102,7 +1128,7 @@ private TestEnum(string key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -1148,9 +1174,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -1390,6 +1417,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -1459,7 +1488,7 @@ private TestEnum(string key, string key1) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -1505,9 +1534,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -1783,6 +1813,8 @@ public partial class TestEnum [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -1837,7 +1869,7 @@ private TestEnum(string key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -1883,9 +1915,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::TestEnum item) { if(global::TestEnum.TryGet(key, out item)) { @@ -2138,10 +2171,16 @@ public int CompareTo(global::TestEnum? obj) partial class TestEnum : global::System.IParsable { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::TestEnum? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + /// public static global::TestEnum Parse(string s, global::System.IFormatProvider? provider) { - var validationResult = global::TestEnum.Validate(s, out var result); + var validationResult = Validate(s, provider, out var result); if(validationResult is null) return result!; @@ -2161,7 +2200,7 @@ public static bool TryParse( return false; } - var validationResult = global::TestEnum.Validate(s, out result!); + var validationResult = Validate(s, provider, out result!); return validationResult is null; } } @@ -2274,6 +2313,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -2328,7 +2369,7 @@ private TestEnum(string key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -2374,9 +2415,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -2829,6 +2871,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::Thinktecture.IValidatableEnum, global::System.IEquatable { @@ -2903,7 +2947,7 @@ private TestEnum(string key, bool isValid) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -2970,9 +3014,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -3242,6 +3287,8 @@ namespace Thinktecture.Tests [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Auto)] [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::Thinktecture.IValidatableEnum, global::System.IEquatable { @@ -3316,7 +3363,7 @@ private TestEnum(string key, bool isValid) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -3380,9 +3427,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -3642,10 +3690,16 @@ namespace Thinktecture.Tests; partial struct TestEnum : global::System.IParsable { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestEnum result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + /// public static global::Thinktecture.Tests.TestEnum Parse(string s, global::System.IFormatProvider? provider) { - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out var result); + var validationResult = Validate(s, provider, out var result); return result!; } @@ -3661,7 +3715,7 @@ public static bool TryParse( return false; } - var validationResult = global::Thinktecture.Tests.TestEnum.Validate(s, out result!); + var validationResult = Validate(s, provider, out result!); return true; } } @@ -3737,6 +3791,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::Thinktecture.IValidatableEnum, global::System.IEquatable { @@ -3817,7 +3873,7 @@ private TestEnum(string name, bool isValid, int structProperty, int? nullableStr /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() + string global::Thinktecture.IValueObjectConverter.ToValue() { return this.Name; } @@ -3882,9 +3938,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string name, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string name, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(name, out item)) { @@ -4190,6 +4247,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -4241,7 +4300,7 @@ private TestEnum(int key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() + int global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -4278,9 +4337,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] in /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -4576,6 +4636,8 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -4627,7 +4689,7 @@ private TestEnum(int key) /// Gets the identifier of the item. /// [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() + int global::Thinktecture.IValueObjectConverter.ToValue() { return this.Key; } @@ -4664,9 +4726,10 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] in /// Validates the provided and returns a valid enumeration item if found. /// /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. /// A valid instance of ; otherwise null. /// if a valid item with provided exists; with an error message otherwise. - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) { if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) { @@ -4981,4 +5044,791 @@ partial class TestEnum : """); } + + [Fact] + public void Should_generate_int_based_IEnum_with_ValueObjectFactoryAttribute() + { + /* language=c# */ + var source = @" +using System; + +namespace Thinktecture.Tests +{ + [SmartEnum] + [ValueObjectFactory] + public partial class TestEnum + { + public static readonly TestEnum Item1 = new(1); + public static readonly TestEnum Item2 = new(2); + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); + outputs.Should().HaveCount(6); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; + var formattable = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(formattable, _FORMATTABLE_OUTPUT_CLASS); + AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); + AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); // string-based due to [ValueObjectFactory] + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); + + AssertOutput(mainOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IValueObjectFactory, + global::System.IEquatable + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + var convertFromKey = new global::System.Func(global::Thinktecture.Tests.TestEnum.Get); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static key => global::Thinktecture.Tests.TestEnum.Get(key); + + var convertToKey = new global::System.Func(static item => item.Key); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static item => item.Key; + + var enumType = typeof(global::Thinktecture.Tests.TestEnum); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(int), true, false, convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(enumType, metadata); + } + + public static global::System.Collections.Generic.IEqualityComparer KeyEqualityComparer => global::System.Collections.Generic.EqualityComparer.Default; + + private static readonly global::System.Lazy> _itemsLookup + = new global::System.Lazy>(GetLookup, global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + private static readonly global::System.Lazy> _items + = new global::System.Lazy>(() => global::System.Linq.Enumerable.ToList(_itemsLookup.Value.Values).AsReadOnly(), global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + /// + /// Gets all valid items. + /// + public static global::System.Collections.Generic.IReadOnlyList Items => _items.Value; + + /// + /// The identifier of the item. + /// + public int Key { get; } + + private readonly int _hashCode; + + private TestEnum(int key) + { + ValidateConstructorArguments(ref key); + + this.Key = key; + this._hashCode = global::System.HashCode.Combine(typeof(global::Thinktecture.Tests.TestEnum), KeyEqualityComparer.GetHashCode(key)); + } + + static partial void ValidateConstructorArguments(ref int key); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.Key; + } + + /// + /// Gets an enumeration item for provided . + /// + /// The identifier to return an enumeration item for. + /// An instance of if is not null; otherwise null. + /// If there is no item with the provided . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static global::Thinktecture.Tests.TestEnum Get(int key) + { + if (!_itemsLookup.Value.TryGetValue(key, out var item)) + { + throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(global::Thinktecture.Tests.TestEnum), key); + } + + return item; + } + + /// + /// Gets a valid enumeration item for provided if a valid item exists. + /// + /// The identifier to return an enumeration item for. + /// A valid instance of ; otherwise null. + /// true if a valid item with provided exists; false otherwise. + public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestEnum item) + { + return _itemsLookup.Value.TryGetValue(key, out item); + } + + /// + /// Validates the provided and returns a valid enumeration item if found. + /// + /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. + /// A valid instance of ; otherwise null. + /// if a valid item with provided exists; with an error message otherwise. + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + { + if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) + { + return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + else + { + return new global::System.ComponentModel.DataAnnotations.ValidationResult($"There is no item of type 'TestEnum' with the identifier '{key}'.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestEnum.Key))); + } + } + + /// + /// Implicit conversion to the type . + /// + /// Item to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("item")] + public static implicit operator int(global::Thinktecture.Tests.TestEnum? item) + { + return item is null ? default : item.Key; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of if the is a known item or implements . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static explicit operator global::Thinktecture.Tests.TestEnum?(int key) + { + return global::Thinktecture.Tests.TestEnum.Get(key); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestEnum? other) + { + return global::System.Object.ReferenceEquals(this, other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestEnum item && Equals(item); + } + + /// + public override int GetHashCode() + { + return _hashCode; + } + + /// + public override string ToString() + { + return this.Key.ToString(); + } + + /// + /// Executes an action depending on the current item. + /// + /// The item to compare to. + /// The action to execute if the current item is equal to . + /// The item to compare to. + /// The action to execute if the current item is equal to . + public void Switch( + TestEnum testEnum1, global::System.Action testEnumAction1, + TestEnum testEnum2, global::System.Action testEnumAction2) + { + if (this == testEnum1) + { + testEnumAction1(); + } + else if (this == testEnum2) + { + testEnumAction2(); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No action provided for the item '{this}'."); + } + } + + /// + /// Executes an action depending on the current item. + /// + /// Context to be passed to the callbacks. + /// The item to compare to. + /// The action to execute if the current item is equal to . + /// The item to compare to. + /// The action to execute if the current item is equal to . + public void Switch( + TContext context, + TestEnum testEnum1, global::System.Action testEnumAction1, + TestEnum testEnum2, global::System.Action testEnumAction2) + { + if (this == testEnum1) + { + testEnumAction1(context); + } + else if (this == testEnum2) + { + testEnumAction2(context); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No action provided for the item '{this}'."); + } + } + + /// + /// Executes a function depending on the current item. + /// + /// The item to compare to. + /// The function to execute if the current item is equal to . + /// The item to compare to. + /// The function to execute if the current item is equal to . + public T Switch( + TestEnum testEnum1, global::System.Func testEnumFunc1, + TestEnum testEnum2, global::System.Func testEnumFunc2) + { + if (this == testEnum1) + { + return testEnumFunc1(); + } + else if (this == testEnum2) + { + return testEnumFunc2(); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No function provided for the item '{this}'."); + } + } + + /// + /// Executes a function depending on the current item. + /// + /// Context to be passed to the callbacks. + /// The item to compare to. + /// The function to execute if the current item is equal to . + /// The item to compare to. + /// The function to execute if the current item is equal to . + public T Switch( + TContext context, + TestEnum testEnum1, global::System.Func testEnumFunc1, + TestEnum testEnum2, global::System.Func testEnumFunc2) + { + if (this == testEnum1) + { + return testEnumFunc1(context); + } + else if (this == testEnum2) + { + return testEnumFunc2(context); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No function provided for the item '{this}'."); + } + } + + /// + /// Maps an item to an instance of type . + /// + /// The item to compare to. + /// The instance to return if the current item is equal to . + /// The item to compare to. + /// The instance to return if the current item is equal to . + public T Map( + TestEnum testEnum1, T other1, + TestEnum testEnum2, T other2) + { + if (this == testEnum1) + { + return other1; + } + else if (this == testEnum2) + { + return other2; + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No instance provided for the item '{this}'."); + } + } + + private static global::System.Collections.Generic.IReadOnlyDictionary GetLookup() + { + var lookup = new global::System.Collections.Generic.Dictionary(2, KeyEqualityComparer); + + void AddItem(global::Thinktecture.Tests.TestEnum item, string itemName) + { + if (item is null) + throw new global::System.ArgumentNullException($"The item \"{itemName}\" of type \"TestEnum\" must not be null."); + + if (lookup.ContainsKey(item.Key)) + throw new global::System.ArgumentException($"The type \"TestEnum\" has multiple items with the identifier \"{item.Key}\"."); + + lookup.Add(item.Key, item); + } + + AddItem(Item1, nameof(Item1)); + AddItem(Item2, nameof(Item2)); + + #if NET8_0_OR_GREATER + return global::System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary(lookup, KeyEqualityComparer); + #else + return lookup; + #endif + } + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests; + + partial class TestEnum : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key < right.Key; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key <= right.Key; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key > right.Key; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key >= right.Key; + } + } + + """); + } + + [Fact] + public void Should_generate_int_based_IEnum_with_ValueObjectFactoryAttribute_and_UseForSerialization() + { + /* language=c# */ + var source = @" +using System; + +namespace Thinktecture.Tests +{ + [SmartEnum] + [ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] + public partial class TestEnum + { + public static readonly TestEnum Item1 = new(1); + public static readonly TestEnum Item2 = new(2); + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); + outputs.Should().HaveCount(6); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; + var formattable = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(formattable, _FORMATTABLE_OUTPUT_CLASS); + AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); + AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); // string-based due to [ValueObjectFactory] + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); + + AssertOutput(mainOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestEnum : global::Thinktecture.IEnum, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter, + global::System.IEquatable + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + var convertFromKey = new global::System.Func(global::Thinktecture.Tests.TestEnum.Get); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static key => global::Thinktecture.Tests.TestEnum.Get(key); + + var convertToKey = new global::System.Func(static item => item.Key); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static item => item.Key; + + var enumType = typeof(global::Thinktecture.Tests.TestEnum); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(int), true, false, convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(enumType, metadata); + } + + public static global::System.Collections.Generic.IEqualityComparer KeyEqualityComparer => global::System.Collections.Generic.EqualityComparer.Default; + + private static readonly global::System.Lazy> _itemsLookup + = new global::System.Lazy>(GetLookup, global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + private static readonly global::System.Lazy> _items + = new global::System.Lazy>(() => global::System.Linq.Enumerable.ToList(_itemsLookup.Value.Values).AsReadOnly(), global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + /// + /// Gets all valid items. + /// + public static global::System.Collections.Generic.IReadOnlyList Items => _items.Value; + + /// + /// The identifier of the item. + /// + public int Key { get; } + + private readonly int _hashCode; + + private TestEnum(int key) + { + ValidateConstructorArguments(ref key); + + this.Key = key; + this._hashCode = global::System.HashCode.Combine(typeof(global::Thinktecture.Tests.TestEnum), KeyEqualityComparer.GetHashCode(key)); + } + + static partial void ValidateConstructorArguments(ref int key); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.Key; + } + + /// + /// Gets an enumeration item for provided . + /// + /// The identifier to return an enumeration item for. + /// An instance of if is not null; otherwise null. + /// If there is no item with the provided . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static global::Thinktecture.Tests.TestEnum Get(int key) + { + if (!_itemsLookup.Value.TryGetValue(key, out var item)) + { + throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(global::Thinktecture.Tests.TestEnum), key); + } + + return item; + } + + /// + /// Gets a valid enumeration item for provided if a valid item exists. + /// + /// The identifier to return an enumeration item for. + /// A valid instance of ; otherwise null. + /// true if a valid item with provided exists; false otherwise. + public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestEnum item) + { + return _itemsLookup.Value.TryGetValue(key, out item); + } + + /// + /// Validates the provided and returns a valid enumeration item if found. + /// + /// The identifier to return an enumeration item for. + /// An object that provides culture-specific formatting information. + /// A valid instance of ; otherwise null. + /// if a valid item with provided exists; with an error message otherwise. + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] int key, global::System.IFormatProvider? provider, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + { + if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) + { + return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + else + { + return new global::System.ComponentModel.DataAnnotations.ValidationResult($"There is no item of type 'TestEnum' with the identifier '{key}'.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestEnum.Key))); + } + } + + /// + /// Implicit conversion to the type . + /// + /// Item to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("item")] + public static implicit operator int(global::Thinktecture.Tests.TestEnum? item) + { + return item is null ? default : item.Key; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of if the is a known item or implements . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static explicit operator global::Thinktecture.Tests.TestEnum?(int key) + { + return global::Thinktecture.Tests.TestEnum.Get(key); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestEnum? other) + { + return global::System.Object.ReferenceEquals(this, other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestEnum item && Equals(item); + } + + /// + public override int GetHashCode() + { + return _hashCode; + } + + /// + public override string ToString() + { + return this.Key.ToString(); + } + + /// + /// Executes an action depending on the current item. + /// + /// The item to compare to. + /// The action to execute if the current item is equal to . + /// The item to compare to. + /// The action to execute if the current item is equal to . + public void Switch( + TestEnum testEnum1, global::System.Action testEnumAction1, + TestEnum testEnum2, global::System.Action testEnumAction2) + { + if (this == testEnum1) + { + testEnumAction1(); + } + else if (this == testEnum2) + { + testEnumAction2(); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No action provided for the item '{this}'."); + } + } + + /// + /// Executes an action depending on the current item. + /// + /// Context to be passed to the callbacks. + /// The item to compare to. + /// The action to execute if the current item is equal to . + /// The item to compare to. + /// The action to execute if the current item is equal to . + public void Switch( + TContext context, + TestEnum testEnum1, global::System.Action testEnumAction1, + TestEnum testEnum2, global::System.Action testEnumAction2) + { + if (this == testEnum1) + { + testEnumAction1(context); + } + else if (this == testEnum2) + { + testEnumAction2(context); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No action provided for the item '{this}'."); + } + } + + /// + /// Executes a function depending on the current item. + /// + /// The item to compare to. + /// The function to execute if the current item is equal to . + /// The item to compare to. + /// The function to execute if the current item is equal to . + public T Switch( + TestEnum testEnum1, global::System.Func testEnumFunc1, + TestEnum testEnum2, global::System.Func testEnumFunc2) + { + if (this == testEnum1) + { + return testEnumFunc1(); + } + else if (this == testEnum2) + { + return testEnumFunc2(); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No function provided for the item '{this}'."); + } + } + + /// + /// Executes a function depending on the current item. + /// + /// Context to be passed to the callbacks. + /// The item to compare to. + /// The function to execute if the current item is equal to . + /// The item to compare to. + /// The function to execute if the current item is equal to . + public T Switch( + TContext context, + TestEnum testEnum1, global::System.Func testEnumFunc1, + TestEnum testEnum2, global::System.Func testEnumFunc2) + { + if (this == testEnum1) + { + return testEnumFunc1(context); + } + else if (this == testEnum2) + { + return testEnumFunc2(context); + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No function provided for the item '{this}'."); + } + } + + /// + /// Maps an item to an instance of type . + /// + /// The item to compare to. + /// The instance to return if the current item is equal to . + /// The item to compare to. + /// The instance to return if the current item is equal to . + public T Map( + TestEnum testEnum1, T other1, + TestEnum testEnum2, T other2) + { + if (this == testEnum1) + { + return other1; + } + else if (this == testEnum2) + { + return other2; + } + else + { + throw new global::System.ArgumentOutOfRangeException($"No instance provided for the item '{this}'."); + } + } + + private static global::System.Collections.Generic.IReadOnlyDictionary GetLookup() + { + var lookup = new global::System.Collections.Generic.Dictionary(2, KeyEqualityComparer); + + void AddItem(global::Thinktecture.Tests.TestEnum item, string itemName) + { + if (item is null) + throw new global::System.ArgumentNullException($"The item \"{itemName}\" of type \"TestEnum\" must not be null."); + + if (lookup.ContainsKey(item.Key)) + throw new global::System.ArgumentException($"The type \"TestEnum\" has multiple items with the identifier \"{item.Key}\"."); + + lookup.Add(item.Key, item); + } + + AddItem(Item1, nameof(Item1)); + AddItem(Item2, nameof(Item2)); + + #if NET8_0_OR_GREATER + return global::System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary(lookup, KeyEqualityComparer); + #else + return lookup; + #endif + } + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests; + + partial class TestEnum : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key < right.Key; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key <= right.Key; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key > right.Key; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.Key >= right.Key; + } + } + + """); + } } diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/NewtonsoftJsonValueObjectSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/NewtonsoftJsonValueObjectSourceGeneratorTests.cs index 42cf5b64..5f25c035 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/NewtonsoftJsonValueObjectSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/NewtonsoftJsonValueObjectSourceGeneratorTests.cs @@ -129,128 +129,187 @@ public partial class TestValueObject ".NewtonsoftJson", typeof(ValueObjectAttribute).Assembly, typeof(Json.ValueObjectNewtonsoftJsonConverter).Assembly, typeof(Newtonsoft.Json.JsonToken).Assembly); - AssertOutput(output, @"// -#nullable enable - -namespace Thinktecture.Tests; - -[global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] -partial class TestValueObject -{ - public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter - { - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - /// - public override bool CanConvert(global::System.Type objectType) - { - return _type.IsAssignableFrom(objectType); - } - - /// - public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (reader is null) - throw new global::System.ArgumentNullException(nameof(reader)); - if (serializer is null) - throw new global::System.ArgumentNullException(nameof(serializer)); - - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) - { - if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) - return null; - - return default(global::Thinktecture.Tests.TestValueObject); - } - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.StartObject)}\"".""); - - string? referenceField = default; - int structProperty = default; - decimal? nullableStructProperty = default; - - var comparer = global::System.StringComparer.OrdinalIgnoreCase; - - while (reader.Read()) - { - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) - break; - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.PropertyName)}\"".""); - - var propName = reader.Value!.ToString(); - - if(!reader.Read()) - throw new global::Newtonsoft.Json.JsonException($""Unexpected end of the JSON message when trying the read the value of \""{propName}\"" during deserialization of \""TestValueObject\"".""); - - if (comparer.Equals(propName, ""referenceField"")) - { - referenceField = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""structProperty"")) - { - structProperty = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""nullableStructProperty"")) - { - nullableStructProperty = serializer.Deserialize(reader); - } - else - { - throw new global::Newtonsoft.Json.JsonException($""Unknown member \""{propName}\"" encountered when trying to deserialize \""TestValueObject\"".""); - } - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate( - referenceField!, - structProperty!, - nullableStructProperty!, - out var obj); - - if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::Newtonsoft.Json.JsonException($""Unable to deserialize \""TestValueObject\"". Error: {validationResult!.ErrorMessage}.""); - - return obj; - } - - /// - public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - return; - } - - var obj = (global::Thinktecture.Tests.TestValueObject)value; - var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; - - writer.WriteStartObject(); - var referenceFieldPropertyValue = obj.ReferenceField; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""ReferenceField"") : ""ReferenceField""); - writer.WriteValue(referenceFieldPropertyValue); - } - var structPropertyPropertyValue = obj.StructProperty; - - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""StructProperty"") : ""StructProperty""); - writer.WriteValue(structPropertyPropertyValue); - var nullableStructPropertyPropertyValue = obj.NullableStructProperty; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""NullableStructProperty"") : ""NullableStructProperty""); - serializer.Serialize(writer, nullableStructPropertyPropertyValue); - } - writer.WriteEndObject(); - } - } -} -"); + AssertOutput(output, """ + // + #nullable enable + + namespace Thinktecture.Tests; + + [global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] + partial class TestValueObject + { + public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter + { + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + /// + public override bool CanConvert(global::System.Type objectType) + { + return _type.IsAssignableFrom(objectType); + } + + /// + public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (reader is null) + throw new global::System.ArgumentNullException(nameof(reader)); + if (serializer is null) + throw new global::System.ArgumentNullException(nameof(serializer)); + + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) + { + if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) + return null; + + return default(global::Thinktecture.Tests.TestValueObject); + } + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.StartObject)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + string? referenceField = default; + int structProperty = default; + decimal? nullableStructProperty = default; + + var comparer = global::System.StringComparer.OrdinalIgnoreCase; + + while (reader.Read()) + { + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) + break; + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.PropertyName)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + var propName = reader.Value!.ToString(); + + if(!reader.Read()) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected end of the JSON message when trying the read the value of \"{propName}\" during deserialization of \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + if (comparer.Equals(propName, "referenceField")) + { + referenceField = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "structProperty")) + { + structProperty = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "nullableStructProperty")) + { + nullableStructProperty = serializer.Deserialize(reader); + } + else + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unknown member \"{propName}\" encountered when trying to deserialize \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + } + + var validationResult = global::Thinktecture.Tests.TestValueObject.Validate( + referenceField!, + structProperty!, + nullableStructProperty!, + out var obj); + + if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonSerializationException( + $"Unable to deserialize \"TestValueObject\". Error: {validationResult!.ErrorMessage}.", + reader.Path, + lineNumber, + linePosition, + null); + } + + return obj; + } + + /// + public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + var obj = (global::Thinktecture.Tests.TestValueObject)value; + var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; + + writer.WriteStartObject(); + var referenceFieldPropertyValue = obj.ReferenceField; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("ReferenceField") : "ReferenceField"); + writer.WriteValue(referenceFieldPropertyValue); + } + var structPropertyPropertyValue = obj.StructProperty; + + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("StructProperty") : "StructProperty"); + writer.WriteValue(structPropertyPropertyValue); + var nullableStructPropertyPropertyValue = obj.NullableStructProperty; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("NullableStructProperty") : "NullableStructProperty"); + serializer.Serialize(writer, nullableStructPropertyPropertyValue); + } + writer.WriteEndObject(); + } + + private static (int Number, int Position) GetLineInfo(global::Newtonsoft.Json.JsonReader reader) + { + var lineInfo = (reader as global::Newtonsoft.Json.IJsonLineInfo); + + if (lineInfo?.HasLineInfo() == true) + { + return (lineInfo.LineNumber, lineInfo.LinePosition); + } + else + { + return (0, 0); + } + } + } + } + + """); } [Fact] @@ -272,126 +331,185 @@ public partial class TestValueObject ".NewtonsoftJson", typeof(ValueObjectAttribute).Assembly, typeof(Json.ValueObjectNewtonsoftJsonConverter).Assembly, typeof(Newtonsoft.Json.JsonToken).Assembly); - AssertOutput(output, @"// -#nullable enable - -[global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] -partial class TestValueObject -{ - public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter - { - private static readonly global::System.Type _type = typeof(global::TestValueObject); - - /// - public override bool CanConvert(global::System.Type objectType) - { - return _type.IsAssignableFrom(objectType); - } - - /// - public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (reader is null) - throw new global::System.ArgumentNullException(nameof(reader)); - if (serializer is null) - throw new global::System.ArgumentNullException(nameof(serializer)); - - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) - { - if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) - return null; - - return default(global::TestValueObject); - } - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.StartObject)}\"".""); - - string? referenceField = default; - int structProperty = default; - decimal? nullableStructProperty = default; - - var comparer = global::System.StringComparer.OrdinalIgnoreCase; - - while (reader.Read()) - { - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) - break; - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.PropertyName)}\"".""); - - var propName = reader.Value!.ToString(); - - if(!reader.Read()) - throw new global::Newtonsoft.Json.JsonException($""Unexpected end of the JSON message when trying the read the value of \""{propName}\"" during deserialization of \""TestValueObject\"".""); - - if (comparer.Equals(propName, ""referenceField"")) - { - referenceField = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""structProperty"")) - { - structProperty = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""nullableStructProperty"")) - { - nullableStructProperty = serializer.Deserialize(reader); - } - else - { - throw new global::Newtonsoft.Json.JsonException($""Unknown member \""{propName}\"" encountered when trying to deserialize \""TestValueObject\"".""); - } - } - - var validationResult = global::TestValueObject.Validate( - referenceField!, - structProperty!, - nullableStructProperty!, - out var obj); - - if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::Newtonsoft.Json.JsonException($""Unable to deserialize \""TestValueObject\"". Error: {validationResult!.ErrorMessage}.""); - - return obj; - } - - /// - public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - return; - } - - var obj = (global::TestValueObject)value; - var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; - - writer.WriteStartObject(); - var referenceFieldPropertyValue = obj.ReferenceField; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""ReferenceField"") : ""ReferenceField""); - writer.WriteValue(referenceFieldPropertyValue); - } - var structPropertyPropertyValue = obj.StructProperty; - - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""StructProperty"") : ""StructProperty""); - writer.WriteValue(structPropertyPropertyValue); - var nullableStructPropertyPropertyValue = obj.NullableStructProperty; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""NullableStructProperty"") : ""NullableStructProperty""); - serializer.Serialize(writer, nullableStructPropertyPropertyValue); - } - writer.WriteEndObject(); - } - } -} -"); + AssertOutput(output, """ + // + #nullable enable + + [global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] + partial class TestValueObject + { + public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter + { + private static readonly global::System.Type _type = typeof(global::TestValueObject); + + /// + public override bool CanConvert(global::System.Type objectType) + { + return _type.IsAssignableFrom(objectType); + } + + /// + public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (reader is null) + throw new global::System.ArgumentNullException(nameof(reader)); + if (serializer is null) + throw new global::System.ArgumentNullException(nameof(serializer)); + + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) + { + if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) + return null; + + return default(global::TestValueObject); + } + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.StartObject)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + string? referenceField = default; + int structProperty = default; + decimal? nullableStructProperty = default; + + var comparer = global::System.StringComparer.OrdinalIgnoreCase; + + while (reader.Read()) + { + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) + break; + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.PropertyName)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + var propName = reader.Value!.ToString(); + + if(!reader.Read()) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected end of the JSON message when trying the read the value of \"{propName}\" during deserialization of \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + if (comparer.Equals(propName, "referenceField")) + { + referenceField = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "structProperty")) + { + structProperty = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "nullableStructProperty")) + { + nullableStructProperty = serializer.Deserialize(reader); + } + else + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unknown member \"{propName}\" encountered when trying to deserialize \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + } + + var validationResult = global::TestValueObject.Validate( + referenceField!, + structProperty!, + nullableStructProperty!, + out var obj); + + if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonSerializationException( + $"Unable to deserialize \"TestValueObject\". Error: {validationResult!.ErrorMessage}.", + reader.Path, + lineNumber, + linePosition, + null); + } + + return obj; + } + + /// + public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + var obj = (global::TestValueObject)value; + var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; + + writer.WriteStartObject(); + var referenceFieldPropertyValue = obj.ReferenceField; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("ReferenceField") : "ReferenceField"); + writer.WriteValue(referenceFieldPropertyValue); + } + var structPropertyPropertyValue = obj.StructProperty; + + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("StructProperty") : "StructProperty"); + writer.WriteValue(structPropertyPropertyValue); + var nullableStructPropertyPropertyValue = obj.NullableStructProperty; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("NullableStructProperty") : "NullableStructProperty"); + serializer.Serialize(writer, nullableStructPropertyPropertyValue); + } + writer.WriteEndObject(); + } + + private static (int Number, int Position) GetLineInfo(global::Newtonsoft.Json.JsonReader reader) + { + var lineInfo = (reader as global::Newtonsoft.Json.IJsonLineInfo); + + if (lineInfo?.HasLineInfo() == true) + { + return (lineInfo.LineNumber, lineInfo.LinePosition); + } + else + { + return (0, 0); + } + } + } + } + + """); } [Fact] @@ -416,127 +534,186 @@ public readonly partial struct TestValueObject ".NewtonsoftJson", typeof(ValueObjectAttribute).Assembly, typeof(Json.ValueObjectNewtonsoftJsonConverter).Assembly, typeof(Newtonsoft.Json.JsonToken).Assembly); - AssertOutput(output, @"// -#nullable enable - -namespace Thinktecture.Tests; - -[global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] -partial struct TestValueObject -{ - public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter - { - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - /// - public override bool CanConvert(global::System.Type objectType) - { - return _type.IsAssignableFrom(objectType); - } - - /// - public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (reader is null) - throw new global::System.ArgumentNullException(nameof(reader)); - if (serializer is null) - throw new global::System.ArgumentNullException(nameof(serializer)); - - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) - { - if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) - return null; - - return default(global::Thinktecture.Tests.TestValueObject); - } - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.StartObject)}\"".""); - - string? referenceField = default; - int structProperty = default; - decimal? nullableStructProperty = default; - - var comparer = global::System.StringComparer.OrdinalIgnoreCase; - - while (reader.Read()) - { - if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) - break; - - if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) - throw new global::Newtonsoft.Json.JsonException($""Unexpected token \""{reader.TokenType}\"" when trying to deserialize \""TestValueObject\"". Expected token: \""{(global::Newtonsoft.Json.JsonToken.PropertyName)}\"".""); - - var propName = reader.Value!.ToString(); - - if(!reader.Read()) - throw new global::Newtonsoft.Json.JsonException($""Unexpected end of the JSON message when trying the read the value of \""{propName}\"" during deserialization of \""TestValueObject\"".""); - - if (comparer.Equals(propName, ""referenceField"")) - { - referenceField = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""structProperty"")) - { - structProperty = serializer.Deserialize(reader); - } - else if (comparer.Equals(propName, ""nullableStructProperty"")) - { - nullableStructProperty = serializer.Deserialize(reader); - } - else - { - throw new global::Newtonsoft.Json.JsonException($""Unknown member \""{propName}\"" encountered when trying to deserialize \""TestValueObject\"".""); - } - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate( - referenceField!, - structProperty!, - nullableStructProperty!, - out var obj); - - if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::Newtonsoft.Json.JsonException($""Unable to deserialize \""TestValueObject\"". Error: {validationResult!.ErrorMessage}.""); - - return obj; - } - - /// - public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - return; - } - - var obj = (global::Thinktecture.Tests.TestValueObject)value; - var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; - - writer.WriteStartObject(); - var referenceFieldPropertyValue = obj.ReferenceField; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""ReferenceField"") : ""ReferenceField""); - writer.WriteValue(referenceFieldPropertyValue); - } - var structPropertyPropertyValue = obj.StructProperty; - - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""StructProperty"") : ""StructProperty""); - writer.WriteValue(structPropertyPropertyValue); - var nullableStructPropertyPropertyValue = obj.NullableStructProperty; - - if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) - { - writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(""NullableStructProperty"") : ""NullableStructProperty""); - serializer.Serialize(writer, nullableStructPropertyPropertyValue); - } - writer.WriteEndObject(); - } - } -} -"); + AssertOutput(output, """ + // + #nullable enable + + namespace Thinktecture.Tests; + + [global::Newtonsoft.Json.JsonConverterAttribute(typeof(ValueObjectNewtonsoftJsonConverter))] + partial struct TestValueObject + { + public sealed class ValueObjectNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter + { + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + /// + public override bool CanConvert(global::System.Type objectType) + { + return _type.IsAssignableFrom(objectType); + } + + /// + public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (reader is null) + throw new global::System.ArgumentNullException(nameof(reader)); + if (serializer is null) + throw new global::System.ArgumentNullException(nameof(serializer)); + + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Null) + { + if (objectType.IsClass || global::System.Nullable.GetUnderlyingType(objectType) == _type) + return null; + + return default(global::Thinktecture.Tests.TestValueObject); + } + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.StartObject) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.StartObject)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + string? referenceField = default; + int structProperty = default; + decimal? nullableStructProperty = default; + + var comparer = global::System.StringComparer.OrdinalIgnoreCase; + + while (reader.Read()) + { + if (reader.TokenType == global::Newtonsoft.Json.JsonToken.EndObject) + break; + + if (reader.TokenType != global::Newtonsoft.Json.JsonToken.PropertyName) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected token \"{reader.TokenType}\" when trying to deserialize \"TestValueObject\". Expected token: \"{(global::Newtonsoft.Json.JsonToken.PropertyName)}\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + var propName = reader.Value!.ToString(); + + if(!reader.Read()) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unexpected end of the JSON message when trying the read the value of \"{propName}\" during deserialization of \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + + if (comparer.Equals(propName, "referenceField")) + { + referenceField = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "structProperty")) + { + structProperty = serializer.Deserialize(reader); + } + else if (comparer.Equals(propName, "nullableStructProperty")) + { + nullableStructProperty = serializer.Deserialize(reader); + } + else + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonReaderException( + $"Unknown member \"{propName}\" encountered when trying to deserialize \"TestValueObject\".", + reader.Path, + lineNumber, + linePosition, + null); + } + } + + var validationResult = global::Thinktecture.Tests.TestValueObject.Validate( + referenceField!, + structProperty!, + nullableStructProperty!, + out var obj); + + if (validationResult != System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + var (lineNumber, linePosition) = GetLineInfo(reader); + + throw new global::Newtonsoft.Json.JsonSerializationException( + $"Unable to deserialize \"TestValueObject\". Error: {validationResult!.ErrorMessage}.", + reader.Path, + lineNumber, + linePosition, + null); + } + + return obj; + } + + /// + public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + var obj = (global::Thinktecture.Tests.TestValueObject)value; + var resolver = serializer.ContractResolver as global::Newtonsoft.Json.Serialization.DefaultContractResolver; + + writer.WriteStartObject(); + var referenceFieldPropertyValue = obj.ReferenceField; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || referenceFieldPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("ReferenceField") : "ReferenceField"); + writer.WriteValue(referenceFieldPropertyValue); + } + var structPropertyPropertyValue = obj.StructProperty; + + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("StructProperty") : "StructProperty"); + writer.WriteValue(structPropertyPropertyValue); + var nullableStructPropertyPropertyValue = obj.NullableStructProperty; + + if(serializer.NullValueHandling != global::Newtonsoft.Json.NullValueHandling.Ignore || nullableStructPropertyPropertyValue is not null) + { + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName("NullableStructProperty") : "NullableStructProperty"); + serializer.Serialize(writer, nullableStructPropertyPropertyValue); + } + writer.WriteEndObject(); + } + + private static (int Number, int Position) GetLineInfo(global::Newtonsoft.Json.JsonReader reader) + { + var lineInfo = (reader as global::Newtonsoft.Json.IJsonLineInfo); + + if (lineInfo?.HasLineInfo() == true) + { + return (lineInfo.LineNumber, lineInfo.LinePosition); + } + else + { + return (0, 0); + } + } + } + } + + """); } } diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs index feaa52a3..e490ba0c 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs @@ -17,821 +17,1549 @@ public ValueObjectSourceGeneratorTests(ITestOutputHelper output) private const string _FORMATTABLE_INT = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial class TestValueObject : - global::System.IFormattable -{ - /// - public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) - { - return this.StructField.ToString(format, formatProvider); - } -} + partial class TestValueObject : + global::System.IFormattable + { + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + } -"""; + """; private const string _COMPARABLE_INT = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial class TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; + partial class TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - return this.CompareTo(item); - } + return this.CompareTo(item); + } - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) - { - if(obj is null) - return 1; + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; - return this.StructField.CompareTo(obj.StructField); - } -} + return this.StructField.CompareTo(obj.StructField); + } + } -"""; + """; private const string _COMPARABLE_STRUCT_STRING = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial struct TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; + partial struct TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - return this.CompareTo(item); - } + return this.CompareTo(item); + } - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(this.ReferenceField, obj.ReferenceField); - } -} + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(this.ReferenceField, obj.ReferenceField); + } + } -"""; + """; private const string _COMPARABLE_CLASS_STRING = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial class TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; + partial class TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - return this.CompareTo(item); - } + return this.CompareTo(item); + } - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) - { - if(obj is null) - return 1; + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(this.ReferenceField, obj.ReferenceField); - } -} + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(this.ReferenceField, obj.ReferenceField); + } + } -"""; + """; private const string _COMPARABLE_CLASS_STRING_WITH_ORDINAL_COMPARER = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial class TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; + partial class TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - return this.CompareTo(item); - } + return this.CompareTo(item); + } - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) - { - if(obj is null) - return 1; + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; - return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(this.ReferenceField, obj.ReferenceField); - } -} + return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(this.ReferenceField, obj.ReferenceField); + } + } -"""; + """; private const string _PARSABLE_INT = _GENERATED_HEADER + """ -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var key = int.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(int key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = int.Parse(s, provider); + var validationResult = Validate(key, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!int.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = Validate(key, provider, out result!); + return validationResult is null; + } + } + + """; + + /* language=c# */ + private const string _PARSABLE_STRUCT_STRING = _GENERATED_HEADER + """ - if(validationResult is null) - return result!; + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var validationResult = Validate(s, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + var validationResult = Validate(s, provider, out result!); + return validationResult is null; + } + } + + """; + + /* language=c# */ + private const string _PARSABLE_CLASS_STRING = _GENERATED_HEADER + """ - throw new global::System.FormatException(validationResult.ErrorMessage); - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var validationResult = Validate(s, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + var validationResult = Validate(s, provider, out result!); + return validationResult is null; + } + } + + """; + + /* language=c# */ + private const string _COMPARISON_OPERATORS_CLASS_STRING = _GENERATED_HEADER + """ - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators + { + + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; + } + } + + """; - if(!int.TryParse(s, provider, out var key)) - { - result = default; - return false; - } + private const string _COMPARISON_OPERATORS_STRUCT_STRING = _GENERATED_HEADER + """ - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); - return validationResult is null; - } -} + namespace Thinktecture.Tests; -"""; + partial struct TestValueObject : + global::System.Numerics.IComparisonOperators + { - private const string _PARSABLE_STRUCT_STRING = _GENERATED_HEADER + """ + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; + } -namespace Thinktecture.Tests; + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; + } -partial struct TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(s, out var result); + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; + } - if(validationResult is null) - return result!; + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; + } + } - throw new global::System.FormatException(validationResult.ErrorMessage); - } + """; - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } + private const string _COMPARISON_OPERATORS_STRING_WITH_ORDINAL_COMPARER = _GENERATED_HEADER + """ - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(s, out result!); - return validationResult is null; - } -} + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators + { + + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; + } + } + + """; -"""; + private const string _COMPARISON_OPERATORS_INT = _GENERATED_HEADER + """ - private const string _PARSABLE_CLASS_STRING = _GENERATED_HEADER + """ + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } + } + + """; -namespace Thinktecture.Tests; + private const string _EQUALITY_COMPARISON_OPERATORS_CLASS = _GENERATED_HEADER + """ -partial class TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(s, out var result); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IEqualityOperators + { + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + } + + """; - if(validationResult is null) - return result!; + private const string _EQUALITY_COMPARISON_OPERATORS_STRUCT = _GENERATED_HEADER + """ - throw new global::System.FormatException(validationResult.ErrorMessage); - } + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.Numerics.IEqualityOperators + { + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return !(obj == other); + } + } + + """; - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } + private const string _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS = _GENERATED_HEADER + """ - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(s, out result!); - return validationResult is null; - } -} + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IEqualityOperators, + global::System.Numerics.IEqualityOperators + { + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + if (obj is null) + return false; + + return obj.StructField.Equals(value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(int value, global::Thinktecture.Tests.TestValueObject? obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(int value, global::Thinktecture.Tests.TestValueObject? obj) + { + return !(obj == value); + } + } + + """; -"""; + private const string _ADDITION_OPERATORS_INT = _GENERATED_HEADER + """ - private const string _COMPARISON_OPERATORS_CLASS_STRING = _GENERATED_HEADER + """ + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IAdditionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField + right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField + right.StructField)); + } + } + + """; -namespace Thinktecture.Tests; + private const string _SUBTRACTION_OPERATORS_INT = _GENERATED_HEADER + """ -partial class TestValueObject : - global::System.Numerics.IComparisonOperators -{ + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.ISubtractionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField - right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField - right.StructField)); + } + } + + """; - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; - } + private const string _MULTIPLY_OPERATORS_INT = _GENERATED_HEADER + """ - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IMultiplyOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField * right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField * right.StructField)); + } + } + + """; - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; - } + private const string _DIVISION_OPERATORS_INT = _GENERATED_HEADER + """ - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; - } -} + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IDivisionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField / right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField / right.StructField)); + } + } + + """; -"""; + private const string _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT = _GENERATED_HEADER + """ - private const string _COMPARISON_OPERATORS_STRUCT_STRING = _GENERATED_HEADER + """ + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create() + { + var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(); + + private TestValueObject() + { + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + } + + """; -namespace Thinktecture.Tests; + [Fact] + public void Should_not_generate_code_if_not_partial() + { + var source = @" +using System; +using Thinktecture; -partial struct TestValueObject : - global::System.Numerics.IComparisonOperators +namespace Thinktecture.Tests { - - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; + [ValueObject] + public class TestValueObject + { } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; +} +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, null); } - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + [Fact] + public void Should_not_generate_code_if_generic() { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; - } + var source = @" +using System; +using Thinktecture; - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { } } +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, null); + } -"""; - - private const string _COMPARISON_OPERATORS_STRING_WITH_ORDINAL_COMPARER = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; + [Fact] + public void Should_generate_simple_class_with_ValueObjectAttribute() + { + var source = @" +using System; +using Thinktecture; -partial class TestValueObject : - global::System.Numerics.IComparisonOperators +namespace Thinktecture.Tests { - - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) < 0; + [ValueObject] + public partial class TestValueObject + { } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) <= 0; +} +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); } - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + [Fact] + public void Should_generate_post_init_method_if_validation_method_returns_struct() { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) > 0; - } + var source = @" +using System; +using Thinktecture; - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return global::Thinktecture.ComparerAccessors.StringOrdinal.Comparer.Compare(left.ReferenceField, right.ReferenceField) >= 0; +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { + static partial int ValidateFactoryArguments(ref ValidationResult? validationResult) + { + return 42; + } } } +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); -"""; - - private const string _COMPARISON_OPERATORS_INT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; + AssertOutput(output, _GENERATED_HEADER + """ -partial class TestValueObject : - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField < right.StructField; + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + var factoryArgumentsValidationResult = ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(); + obj.FactoryPostInit(factoryArgumentsValidationResult); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create() + { + var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + private static partial int ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(int factoryArgumentsValidationResult); + + private TestValueObject() + { + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + } + + """); } - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + [Fact] + public void Should_generate_post_init_method_if_validation_method_returns_nullable_string() { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField <= right.StructField; - } + var source = @" +using System; +using Thinktecture; - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField > right.StructField; - } +#nullable enable - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField >= right.StructField; +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { + static partial string? ValidateFactoryArguments(ref ValidationResult? validationResult) + { + return String.Empty; + } } } +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); -"""; + AssertOutput(output, _GENERATED_HEADER + """ - private const string _EQUALITY_COMPARISON_OPERATORS_CLASS = _GENERATED_HEADER + """ + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + var factoryArgumentsValidationResult = ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(); + obj.FactoryPostInit(factoryArgumentsValidationResult); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create() + { + var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + private static partial string? ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(string? factoryArgumentsValidationResult); + + private TestValueObject() + { + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + } + + """); + } -namespace Thinktecture.Tests; + [Fact] + public void Should_not_generate_code_for_class_with_generic() + { + var source = @" +using System; +using Thinktecture; -partial class TestValueObject : - global::System.Numerics.IEqualityOperators +namespace Thinktecture.Tests { - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } + [ValueObject] + public partial class TestValueObject + { + } } +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, null); + } -"""; - - private const string _EQUALITY_COMPARISON_OPERATORS_STRUCT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; + [Fact] + public void Should_generate_simple_class_without_namespace() + { + var source = @" +using System; +using Thinktecture; -partial struct TestValueObject : - global::System.Numerics.IEqualityOperators +[ValueObject] +public partial class TestValueObject { - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return obj.Equals(other); - } - - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return !(obj == other); - } } +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); -"""; + AssertOutput(output, _GENERATED_HEADER + """ - private const string _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS = _GENERATED_HEADER + """ + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::TestValueObject(); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::TestValueObject Create() + { + var validationResult = Validate(out global::TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::TestValueObject? obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(); + + private TestValueObject() + { + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::TestValueObject? obj, global::TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::TestValueObject? obj, global::TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + + """); + } -namespace Thinktecture.Tests; + [Fact] + public void Should_generate_simple_class_with_ValueObjectAttribute_using_long_form() + { + var source = @" +using System; +using Thinktecture; -partial class TestValueObject : - global::System.Numerics.IEqualityOperators, - global::System.Numerics.IEqualityOperators +namespace Thinktecture.Tests { - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } + [ValueObjectAttribute] + public partial class TestValueObject + { + } +} +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); + } - private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, int value) - { - if (obj is null) - return false; + [Fact] + public void Should_generate_simple_class_with_fully_qualified_ValueObjectAttribute() + { + var source = @" +using System; - return obj.StructField.Equals(value); - } - - /// - /// Compares an instance of with . - /// - /// Instance to compare. - /// Value to compare with. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, int value) - { - return Equals(obj, value); - } - - /// - /// Compares an instance of with . - /// - /// Value to compare. - /// Instance to compare with. - /// true if objects are equal; otherwise false. - public static bool operator ==(int value, global::Thinktecture.Tests.TestValueObject? obj) - { - return Equals(obj, value); - } - - /// - /// Compares an instance of with . - /// - /// Instance to compare. - /// Value to compare with. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, int value) - { - return !(obj == value); - } - - /// - /// Compares an instance of with . - /// - /// Value to compare. - /// Instance to compare with. - /// false if objects are equal; otherwise true. - public static bool operator !=(int value, global::Thinktecture.Tests.TestValueObject? obj) - { - return !(obj == value); - } -} - -"""; - - private const string _ADDITION_OPERATORS_INT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IAdditionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField + right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField + right.StructField)); - } -} - -"""; - - private const string _SUBTRACTION_OPERATORS_INT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.ISubtractionOperators +namespace Thinktecture.Tests { - /// - public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField - right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField - right.StructField)); + [Thinktecture.ValueObject] + public partial class TestValueObject + { } } - -"""; - - private const string _MULTIPLY_OPERATORS_INT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IMultiplyOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField * right.StructField); +"; + var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); } - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + [Fact] + public void Should_not_generate_factory_methods_if_SkipFactoryMethods_is_true() { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField * right.StructField)); - } -} - -"""; - - private const string _DIVISION_OPERATORS_INT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; + var source = @" +using System; +using Thinktecture; -partial class TestValueObject : - global::System.Numerics.IDivisionOperators +namespace Thinktecture.Tests { - /// - public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField / right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField / right.StructField)); + [ValueObject(SkipFactoryMethods = true)] + public partial class TestValueObject + { + public readonly int StructField; } } +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); -"""; - - private const string _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT = _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create() - { - var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); - - partial void FactoryPostInit(); - - private TestValueObject() - { - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - return true; - } + AssertOutput(formattableOutput, _FORMATTABLE_INT); + AssertOutput(comparableOutput, _COMPARABLE_INT); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } + AssertOutput(mainOutput, _GENERATED_HEADER + """ - /// - public override string ToString() - { - return "TestValueObject"; - } + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func? convertFromKey = null; + global::System.Linq.Expressions.Expression>? convertFromKeyExpression = null; + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); } -} - -"""; [Fact] - public void Should_not_generate_code_if_not_partial() + public void Should_generate_simple_struct_with_ValueObjectAttribute() { var source = @" using System; @@ -840,17 +1568,139 @@ public void Should_not_generate_code_if_not_partial() namespace Thinktecture.Tests { [ValueObject] - public class TestValueObject + public readonly partial struct TestValueObject { } } "; var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, null); + + AssertOutput(output, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests + { + partial struct TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create() + { + var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + } + + """); } [Fact] - public void Should_not_generate_code_if_generic() + public void Should_generate_struct_with_custom_default_instance_property_name() { var source = @" using System; @@ -858,18 +1708,140 @@ public void Should_not_generate_code_if_generic() namespace Thinktecture.Tests { - [ValueObject] - public partial class TestValueObject + [ValueObject(DefaultInstancePropertyName = ""Null"")] + public readonly partial struct TestValueObject { } } "; var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, null); + + AssertOutput(output, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests + { + partial struct TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Null = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create() + { + var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + + partial void FactoryPostInit(); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return true; + } + + /// + public override int GetHashCode() + { + return _type.GetHashCode(); + } + + /// + public override string ToString() + { + return "TestValueObject"; + } + } + } + + """); } [Fact] - public void Should_generate_simple_class_with_ValueObjectAttribute() + public void Should_generate_struct_with_string_key_member() { var source = @" using System; @@ -878,17 +1850,179 @@ public void Should_generate_simple_class_with_ValueObjectAttribute() namespace Thinktecture.Tests { [ValueObject] - public partial class TestValueObject + public readonly partial struct TestValueObject { + public readonly string ReferenceField; } } "; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); + AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); + + AssertOutput(mainOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial struct TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) + { + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] - public void Should_generate_post_init_method_if_validation_method_returns_struct() + public void Should_generate_struct_with_int_key_member() { var source = @" using System; @@ -897,3407 +2031,413 @@ public void Should_generate_post_init_method_if_validation_method_returns_struct namespace Thinktecture.Tests { [ValueObject] - public partial class TestValueObject + public readonly partial struct TestValueObject { - static partial int ValidateFactoryArguments(ref ValidationResult? validationResult) - { - return 42; - } + public readonly int StructField; } } "; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(10); - AssertOutput(output, _GENERATED_HEADER + """ + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; + var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; + var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; + var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - var members = new global::System.Collections.Generic.List(); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial struct TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided . + public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + + /* language=c# */ + AssertOutput(formattableOutput, _GENERATED_HEADER + """ - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + namespace Thinktecture.Tests; - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } + partial struct TestValueObject : + global::System.IFormattable + { + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + } - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + """); - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - var factoryArgumentsValidationResult = ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(); - obj.FactoryPostInit(factoryArgumentsValidationResult); - } - else - { - obj = default; - } - - return validationResult; - } + AssertOutput(comparableOutput, _GENERATED_HEADER + """ - public static global::Thinktecture.Tests.TestValueObject Create() - { - var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); + namespace Thinktecture.Tests; - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + partial struct TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - return obj!; - } + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(out obj); + return this.CompareTo(item); + } - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) + { + return this.StructField.CompareTo(obj.StructField); + } + } - private static partial int ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); + """); - partial void FactoryPostInit(int factoryArgumentsValidationResult); + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - private TestValueObject() - { - } + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(int key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = int.Parse(s, provider); + var validationResult = Validate(key, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!int.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = Validate(key, provider, out result!); + return validationResult is null; + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField >= right.StructField; + } + } + + """); - return obj.Equals(other); - } + AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } + namespace Thinktecture.Tests; - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + partial struct TestValueObject : + global::System.Numerics.IAdditionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField + right.StructField); + } - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField + right.StructField)); + } + } - if (global::System.Object.ReferenceEquals(this, other)) - return true; + """); - return true; - } + AssertOutput(subtractionOperatorsOutput, _GENERATED_HEADER + """ - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } + namespace Thinktecture.Tests; - /// - public override string ToString() - { - return "TestValueObject"; - } - } -} + partial struct TestValueObject : + global::System.Numerics.ISubtractionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField - right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField - right.StructField)); + } + } + + """); + + AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.Numerics.IMultiplyOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField * right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField * right.StructField)); + } + } + + """); + + AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ + + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.Numerics.IDivisionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField / right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField / right.StructField)); + } + } -"""); + """); } [Fact] - public void Should_generate_post_init_method_if_validation_method_returns_nullable_string() + public void Should_generate_struct_with_int_key_member_with_init_only() { var source = @" using System; using Thinktecture; -#nullable enable - namespace Thinktecture.Tests { [ValueObject] - public partial class TestValueObject + public readonly partial struct TestValueObject { - static partial string? ValidateFactoryArguments(ref ValidationResult? validationResult) - { - return String.Empty; - } - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - - AssertOutput(output, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - var factoryArgumentsValidationResult = ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(); - obj.FactoryPostInit(factoryArgumentsValidationResult); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create() - { - var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - private static partial string? ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); - - partial void FactoryPostInit(string? factoryArgumentsValidationResult); - - private TestValueObject() - { - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return true; - } - - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } - - /// - public override string ToString() - { - return "TestValueObject"; - } - } -} - -"""); - } - - [Fact] - public void Should_not_generate_code_for_class_with_generic() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public partial class TestValueObject - { - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, null); - } - - [Fact] - public void Should_generate_simple_class_without_namespace() - { - var source = @" -using System; -using Thinktecture; - -[ValueObject] -public partial class TestValueObject -{ -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - - AssertOutput(output, _GENERATED_HEADER + """ - - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::TestValueObject(); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::TestValueObject Create() - { - var validationResult = Validate(out global::TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::TestValueObject? obj) - { - var validationResult = Validate(out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); - - partial void FactoryPostInit(); - - private TestValueObject() - { - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::TestValueObject? obj, global::TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::TestValueObject? obj, global::TestValueObject? other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return true; - } - - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } - - /// - public override string ToString() - { - return "TestValueObject"; - } - } - -"""); - } - - [Fact] - public void Should_generate_simple_class_with_ValueObjectAttribute_using_long_form() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObjectAttribute] - public partial class TestValueObject - { - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); - } - - [Fact] - public void Should_generate_simple_class_with_fully_qualified_ValueObjectAttribute() - { - var source = @" -using System; - -namespace Thinktecture.Tests -{ - [Thinktecture.ValueObject] - public partial class TestValueObject - { - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - AssertOutput(output, _COMPLEX_VALUE_TYPE_WITHOUT_MEMBERS_OUTPUT); - } - - [Fact] - public void Should_not_generate_factory_methods_if_SkipFactoryMethods_is_true() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(SkipFactoryMethods = true)] - public partial class TestValueObject - { - public readonly int StructField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(formattableOutput, _FORMATTABLE_INT); - AssertOutput(comparableOutput, _COMPARABLE_INT); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func? convertFromKey = null; - global::System.Linq.Expressions.Expression>? convertFromKeyExpression = null; - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_simple_struct_with_ValueObjectAttribute() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public readonly partial struct TestValueObject - { - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - - AssertOutput(output, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create() - { - var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); - - partial void FactoryPostInit(); - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return true; - } - - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } - - /// - public override string ToString() - { - return "TestValueObject"; - } - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_custom_default_instance_property_name() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(DefaultInstancePropertyName = ""Null"")] - public readonly partial struct TestValueObject - { - } -} -"; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); - - AssertOutput(output, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Null = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create() - { - var validationResult = Validate(out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult); - - partial void FactoryPostInit(); - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return true; - } - - /// - public override int GetHashCode() - { - return _type.GetHashCode(); - } - - /// - public override string ToString() - { - return "TestValueObject"; - } - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_string_key_member() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public readonly partial struct TestValueObject - { - public readonly string ReferenceField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); - AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial struct TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) - { - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref string referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_int_key_member() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public readonly partial struct TestValueObject - { - public readonly int StructField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(10); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; - var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; - var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; - var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial struct TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided . - public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - - AssertOutput(formattableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IFormattable -{ - /// - public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) - { - return this.StructField.ToString(format, formatProvider); - } -} - -"""); - - AssertOutput(comparableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; - - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - - return this.CompareTo(item); - } - - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) - { - return this.StructField.CompareTo(obj.StructField); - } -} - -"""); - - AssertOutput(parsableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var key = int.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); - - if(validationResult is null) - return result!; - - throw new global::System.FormatException(validationResult.ErrorMessage); - } - - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } - - if(!int.TryParse(s, provider, out var key)) - { - result = default; - return false; - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); - return validationResult is null; - } -} - -"""); - - AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField < right.StructField; - } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField <= right.StructField; - } - - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField > right.StructField; - } - - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField >= right.StructField; - } -} - -"""); - - AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IAdditionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField + right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField + right.StructField)); - } -} - -"""); - - AssertOutput(subtractionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.ISubtractionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField - right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField - right.StructField)); - } -} - -"""); - - AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IMultiplyOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField * right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField * right.StructField)); - } -} - -"""); - - AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IDivisionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField / right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField / right.StructField)); - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_int_key_member_with_init_only() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public readonly partial struct TestValueObject - { - public readonly int StructField { get; private init; } - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(10); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; - var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; - var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; - var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial struct TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided . - public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - - AssertOutput(formattableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IFormattable -{ - /// - public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) - { - return this.StructField.ToString(format, formatProvider); - } -} - -"""); - - AssertOutput(comparableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; - - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - - return this.CompareTo(item); - } - - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) - { - return this.StructField.CompareTo(obj.StructField); - } -} - -"""); - - AssertOutput(parsableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var key = int.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); - - if(validationResult is null) - return result!; - - throw new global::System.FormatException(validationResult.ErrorMessage); - } - - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } - - if(!int.TryParse(s, provider, out var key)) - { - result = default; - return false; - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); - return validationResult is null; - } -} - -"""); - - AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField < right.StructField; - } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField <= right.StructField; - } - - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField > right.StructField; - } - - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return left.StructField >= right.StructField; - } -} - -"""); - - AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IAdditionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField + right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField + right.StructField)); - } -} - -"""); - - AssertOutput(subtractionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.ISubtractionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField - right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField - right.StructField)); - } -} - -"""); - - AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IMultiplyOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField * right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField * right.StructField)); - } -} - -"""); - - AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial struct TestValueObject : - global::System.Numerics.IDivisionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(left.StructField / right.StructField); - } - - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - return Create(checked(left.StructField / right.StructField)); - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_string_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(NullInFactoryMethodsYieldsNull = true)] - public readonly partial struct TestValueObject - { - public readonly string ReferenceField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); - AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial struct TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) - { - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref string referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_struct_with_string_key_member_and_EmptyStringInFactoryMethodsYieldsNull_should_be_ignored() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] - public readonly partial struct TestValueObject - { - public readonly string ReferenceField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); - AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial struct TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) - { - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref string referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject other) - { - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_class_with_string_key_member() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public partial class TestValueObject - { - public readonly string ReferenceField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); - AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) - { - if(referenceField is null) - return null; - - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref string referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_class_with_int_key_member() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public partial class TestValueObject - { - public readonly int StructField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(10); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; - var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; - var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; - var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - - AssertOutput(formattableOutput, _FORMATTABLE_INT); - AssertOutput(comparableOutput, _COMPARABLE_INT); - AssertOutput(parsableOutput, _PARSABLE_INT); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); - AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); - AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); - AssertOutput(divisionOperatorsOutput, _DIVISION_OPERATORS_INT); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - } - - [Fact] - public void Should_generate_class_with_DateOnly_key_member() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject] - public partial class TestValueObject - { - public readonly DateOnly StructField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(6); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::System.DateOnly), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - global::System.DateOnly structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(global::System.DateOnly structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - global::System.DateOnly structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::System.DateOnly structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - global::System.DateOnly global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator global::System.DateOnly?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator global::System.DateOnly(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(global::System.DateOnly structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(global::System.DateOnly structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - - AssertOutput(formattableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IFormattable -{ - /// - public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) - { - return this.StructField.ToString(format, formatProvider); - } -} - -"""); - - AssertOutput(comparableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; - - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - - return this.CompareTo(item); - } - - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) - { - if(obj is null) - return 1; - - return this.StructField.CompareTo(obj.StructField); - } -} - -"""); - - AssertOutput(parsableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var key = global::System.DateOnly.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); - - if(validationResult is null) - return result!; - - throw new global::System.FormatException(validationResult.ErrorMessage); - } - - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } - - if(!global::System.DateOnly.TryParse(s, provider, out var key)) - { - result = default; - return false; - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); - return validationResult is null; - } -} - -"""); - - AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField < right.StructField; - } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField <= right.StructField; - } - - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField > right.StructField; - } - - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField >= right.StructField; - } -} - -"""); - - AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IEqualityOperators -{ - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } -} - -"""); - } - - [Fact] - public void Should_generate_class_with_DateOnly_key_member_with_DefaultWithKeyTypeOverloads() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(EqualityComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] - public partial class TestValueObject - { - public readonly DateOnly StructField; - } -} -"; - var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(6); - - var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; - var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; - var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; - var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; - var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::System.DateOnly), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - global::System.DateOnly structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(global::System.DateOnly structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - global::System.DateOnly structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::System.DateOnly structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - global::System.DateOnly global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator global::System.DateOnly?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator global::System.DateOnly(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(global::System.DateOnly structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(global::System.DateOnly structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - - AssertOutput(formattableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IFormattable -{ - /// - public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) - { - return this.StructField.ToString(format, formatProvider); - } -} - -"""); - - AssertOutput(comparableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IComparable, - global::System.IComparable -{ - /// - public int CompareTo(object? obj) - { - if(obj is null) - return 1; - - if(obj is not global::Thinktecture.Tests.TestValueObject item) - throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - - return this.CompareTo(item); - } - - /// - public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) - { - if(obj is null) - return 1; - - return this.StructField.CompareTo(obj.StructField); - } -} - -"""); - - AssertOutput(parsableOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.IParsable -{ - /// - public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) - { - var key = global::System.DateOnly.Parse(s, provider); - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); - - if(validationResult is null) - return result!; - - throw new global::System.FormatException(validationResult.ErrorMessage); - } - - /// - public static bool TryParse( - string? s, - global::System.IFormatProvider? provider, - [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) - { - if(s is null) - { - result = default; - return false; - } - - if(!global::System.DateOnly.TryParse(s, provider, out var key)) - { - result = default; - return false; - } - - var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); - return validationResult is null; - } -} - -"""); - - AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField < right.StructField; - } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField <= right.StructField; - } - - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField > right.StructField; - } - - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField >= right.StructField; - } -} - -"""); - - AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IEqualityOperators, - global::System.Numerics.IEqualityOperators -{ - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares two instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } - - private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) - { - if (obj is null) - return false; - - return obj.StructField.Equals(value); - } - - /// - /// Compares an instance of with . - /// - /// Instance to compare. - /// Value to compare with. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) - { - return Equals(obj, value); - } - - /// - /// Compares an instance of with . - /// - /// Value to compare. - /// Instance to compare with. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) - { - return Equals(obj, value); - } - - /// - /// Compares an instance of with . - /// - /// Instance to compare. - /// Value to compare with. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) - { - return !(obj == value); - } - - /// - /// Compares an instance of with . - /// - /// Value to compare. - /// Instance to compare with. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) - { - return !(obj == value); - } -} - -"""); - } - - [Fact] - public void Should_generate_class_with_int_key_member_and_with_DefaultWithKeyTypeOverloads() - { - var source = @" -using System; -using Thinktecture; - -namespace Thinktecture.Tests -{ - [ValueObject(ComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - AdditionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - SubtractionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - MultiplyOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, - DivisionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] - public partial class TestValueObject - { - public readonly int StructField; + public readonly int StructField { get; private init; } } } "; @@ -4315,490 +2455,744 @@ public partial class TestValueObject var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - AssertOutput(formattableOutput, _FORMATTABLE_INT); - AssertOutput(comparableOutput, _COMPARABLE_INT); - AssertOutput(parsableOutput, _PARSABLE_INT); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); - - AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests; - -partial class TestValueObject : - global::System.Numerics.IComparisonOperators, - global::System.Numerics.IComparisonOperators -{ - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField < right.StructField; - } - - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField <= right.StructField; - } - - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField > right.StructField; - } - - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField >= right.StructField; - } - - /// - public static bool operator <(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return left.StructField < right; - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial struct TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided . + public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + + /* language=c# */ + AssertOutput(formattableOutput, _GENERATED_HEADER + """ - /// - public static bool operator <(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left < right.StructField; - } + namespace Thinktecture.Tests; - /// - public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return left.StructField <= right; - } + partial struct TestValueObject : + global::System.IFormattable + { + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + } - /// - public static bool operator <=(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left <= right.StructField; - } + """); - /// - public static bool operator >(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return left.StructField > right; - } + AssertOutput(comparableOutput, _GENERATED_HEADER + """ - /// - public static bool operator >(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left > right.StructField; - } + namespace Thinktecture.Tests; - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return left.StructField >= right; - } + partial struct TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - /// - public static bool operator >=(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left >= right.StructField; - } -} + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); -"""); + return this.CompareTo(item); + } - AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject obj) + { + return this.StructField.CompareTo(obj.StructField); + } + } -namespace Thinktecture.Tests; + """); -partial class TestValueObject : - global::System.Numerics.IAdditionOperators, - global::System.Numerics.IAdditionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField + right.StructField); - } + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField + right.StructField)); - } + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(int key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = int.Parse(s, provider); + var validationResult = Validate(key, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!int.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = Validate(key, provider, out result!); + return validationResult is null; + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - /// - public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(left.StructField + right); - } + namespace Thinktecture.Tests; + + partial struct TestValueObject : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return left.StructField >= right.StructField; + } + } + + """); - /// - public static global::Thinktecture.Tests.TestValueObject operator +(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left + right.StructField); - } + AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(checked(left.StructField + right)); - } + namespace Thinktecture.Tests; - /// - public static global::Thinktecture.Tests.TestValueObject operator checked +(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left + right.StructField)); - } -} + partial struct TestValueObject : + global::System.Numerics.IAdditionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField + right.StructField); + } -"""); + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField + right.StructField)); + } + } + + """); AssertOutput(subtractionOperatorsOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests; + namespace Thinktecture.Tests; -partial class TestValueObject : - global::System.Numerics.ISubtractionOperators, - global::System.Numerics.ISubtractionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField - right.StructField); - } + partial struct TestValueObject : + global::System.Numerics.ISubtractionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField - right.StructField); + } - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField - right.StructField)); - } + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField - right.StructField)); + } + } - /// - public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(left.StructField - right); - } + """); - /// - public static global::Thinktecture.Tests.TestValueObject operator -(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left - right.StructField); - } + AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(checked(left.StructField - right)); - } + namespace Thinktecture.Tests; - /// - public static global::Thinktecture.Tests.TestValueObject operator checked -(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left - right.StructField)); - } -} + partial struct TestValueObject : + global::System.Numerics.IMultiplyOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField * right.StructField); + } -"""); + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField * right.StructField)); + } + } - AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ + """); -namespace Thinktecture.Tests; + AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ -partial class TestValueObject : - global::System.Numerics.IMultiplyOperators, - global::System.Numerics.IMultiplyOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField * right.StructField); - } + namespace Thinktecture.Tests; - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField * right.StructField)); - } + partial struct TestValueObject : + global::System.Numerics.IDivisionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(left.StructField / right.StructField); + } - /// - public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(left.StructField * right); - } + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + return Create(checked(left.StructField / right.StructField)); + } + } - /// - public static global::Thinktecture.Tests.TestValueObject operator *(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left * right.StructField); + """); } - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, int right) + [Fact] + public void Should_generate_struct_with_string_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored() { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(checked(left.StructField * right)); - } + var source = @" +using System; +using Thinktecture; - /// - public static global::Thinktecture.Tests.TestValueObject operator checked *(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left * right.StructField)); +namespace Thinktecture.Tests +{ + [ValueObject(NullInFactoryMethodsYieldsNull = true)] + public readonly partial struct TestValueObject + { + public readonly string ReferenceField; } } +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); -"""); + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ + AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); + AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); -namespace Thinktecture.Tests; + AssertOutput(mainOutput, _GENERATED_HEADER + """ -partial class TestValueObject : - global::System.Numerics.IDivisionOperators, - global::System.Numerics.IDivisionOperators -{ - /// - public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left.StructField / right.StructField); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial struct TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) + { + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + [Fact] + public void Should_generate_struct_with_string_key_member_and_EmptyStringInFactoryMethodsYieldsNull_should_be_ignored() { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left.StructField / right.StructField)); - } + var source = @" +using System; +using Thinktecture; - /// - public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(left.StructField / right); +namespace Thinktecture.Tests +{ + [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] + public readonly partial struct TestValueObject + { + public readonly string ReferenceField; } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); - /// - public static global::Thinktecture.Tests.TestValueObject operator /(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(left / right.StructField); - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, int right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - return Create(checked(left.StructField / right)); - } + AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); + AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); - /// - public static global::Thinktecture.Tests.TestValueObject operator checked /(int left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return Create(checked(left / right.StructField)); - } -} + AssertOutput(mainOutput, _GENERATED_HEADER + """ -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial struct TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static readonly global::Thinktecture.Tests.TestValueObject Empty = default; + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(string referenceField) + { + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] - public void Should_generate_class_with_string_key_member_and_NullInFactoryMethodsYieldsNull() + public void Should_generate_class_with_string_key_member() { var source = @" using System; @@ -4806,7 +3200,7 @@ public void Should_generate_class_with_string_key_member_and_NullInFactoryMethod namespace Thinktecture.Tests { - [ValueObject(NullInFactoryMethodsYieldsNull = true)] + [ValueObject] public partial class TestValueObject { public readonly string ReferenceField; @@ -4829,163 +3223,730 @@ public partial class TestValueObject AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); + } + + [Fact] + public void Should_generate_class_with_int_key_member() { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + var source = @" +using System; +using Thinktecture; - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { + public readonly int StructField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(10); - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; + var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; + var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; + var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } + AssertOutput(formattableOutput, _FORMATTABLE_INT); + AssertOutput(comparableOutput, _COMPARABLE_INT); + AssertOutput(parsableOutput, _PARSABLE_INT); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); + AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); + AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); + AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); + AssertOutput(divisionOperatorsOutput, _DIVISION_OPERATORS_INT); - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(referenceField is null) - { - obj = default; - return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + } - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static global::Thinktecture.Tests.TestValueObject? Create(string? referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); + [Fact] + public void Should_generate_class_with_DateOnly_key_member() + { + var source = @" +using System; +using Thinktecture; - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { + public readonly DateOnly StructField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(6); - return obj; - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - public static bool TryCreate( - string? referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::System.DateOnly), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + global::System.DateOnly structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(global::System.DateOnly structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + global::System.DateOnly structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::System.DateOnly structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + global::System.DateOnly global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator global::System.DateOnly?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator global::System.DateOnly(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(global::System.DateOnly structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(global::System.DateOnly structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + + /* language=c# */ + AssertOutput(formattableOutput, _GENERATED_HEADER + """ - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); + namespace Thinktecture.Tests; - partial void FactoryPostInit(); + partial class TestValueObject : + global::System.IFormattable + { + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + } - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } + """); - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } + AssertOutput(comparableOutput, _GENERATED_HEADER + """ - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) - { - if(referenceField is null) - return null; + namespace Thinktecture.Tests; - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } + partial class TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - this.ReferenceField = referenceField; - } + return this.CompareTo(item); + } - static partial void ValidateConstructorArguments(ref string referenceField); + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + return this.StructField.CompareTo(obj.StructField); + } + } - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; + """); - if (global::System.Object.ReferenceEquals(this, other)) - return true; + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(global::System.DateOnly key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = global::System.DateOnly.Parse(s, provider); + var validationResult = Validate(key, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!global::System.DateOnly.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = Validate(key, provider, out result!); + return validationResult is null; + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } + } + + """); - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} + AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ -"""); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IEqualityOperators + { + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + } + + """); } [Fact] - public void Should_generate_class_with_string_key_member_and_EmptyStringInFactoryMethodsYieldsNull() + public void Should_generate_class_with_DateOnly_key_member_with_DefaultWithKeyTypeOverloads() { var source = @" using System; @@ -4993,184 +3954,416 @@ public void Should_generate_class_with_string_key_member_and_EmptyStringInFactor namespace Thinktecture.Tests { - [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] + [ValueObject(EqualityComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] public partial class TestValueObject { - public readonly string ReferenceField; + public readonly DateOnly StructField; } } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); + outputs.Should().HaveCount(6); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); - AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(global::System.String.IsNullOrWhiteSpace(referenceField)) - { - obj = default; - return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject? Create(string? referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj; - } - - public static bool TryCreate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::System.DateOnly), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + global::System.DateOnly structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(global::System.DateOnly structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + global::System.DateOnly structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::System.DateOnly structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + global::System.DateOnly global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator global::System.DateOnly?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator global::System.DateOnly(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(global::System.DateOnly structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(global::System.DateOnly structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + + /* language=c# */ + AssertOutput(formattableOutput, _GENERATED_HEADER + """ - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); + namespace Thinktecture.Tests; - partial void FactoryPostInit(); + partial class TestValueObject : + global::System.IFormattable + { + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + } - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } + """); - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } + AssertOutput(comparableOutput, _GENERATED_HEADER + """ - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) - { - if(referenceField is null) - return null; + namespace Thinktecture.Tests; - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } + partial class TestValueObject : + global::System.IComparable, + global::System.IComparable + { + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); - this.ReferenceField = referenceField; - } + return this.CompareTo(item); + } - static partial void ValidateConstructorArguments(ref string referenceField); + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + return this.StructField.CompareTo(obj.StructField); + } + } - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; + """); - if (global::System.Object.ReferenceEquals(this, other)) - return true; + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(global::System.DateOnly key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = global::System.DateOnly.Parse(s, provider); + var validationResult = Validate(key, provider, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!global::System.DateOnly.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = Validate(key, provider, out result!); + return validationResult is null; + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } + } + + """); - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} + AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ -"""); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IEqualityOperators, + global::System.Numerics.IEqualityOperators + { + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + if (obj is null) + return false; + + return obj.StructField.Equals(value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) + { + return !(obj == value); + } + } + + """); } [Fact] - public void Should_generate_class_with_int_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored() + public void Should_generate_class_with_int_key_member_and_with_DefaultWithKeyTypeOverloads() { var source = @" using System; @@ -5178,7 +4371,11 @@ public void Should_generate_class_with_int_key_member_and_NullInFactoryMethodsYi namespace Thinktecture.Tests { - [ValueObject(NullInFactoryMethodsYieldsNull = true)] + [ValueObject(ComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + AdditionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + SubtractionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + MultiplyOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, + DivisionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] public partial class TestValueObject { public readonly int StructField; @@ -5202,173 +4399,869 @@ public partial class TestValueObject AssertOutput(formattableOutput, _FORMATTABLE_INT); AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(parsableOutput, _PARSABLE_INT); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); - AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); - AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); - AssertOutput(divisionOperatorsOutput, _DIVISION_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS); AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IComparisonOperators, + global::System.Numerics.IComparisonOperators + { + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } + + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return left.StructField < right; + } + + /// + public static bool operator <(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return left.StructField <= right; + } + + /// + public static bool operator <=(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return left.StructField > right; + } + + /// + public static bool operator >(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return left.StructField >= right; + } + + /// + public static bool operator >=(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left >= right.StructField; + } + } + + """); - return obj!; - } + AssertOutput(additionOperatorsOutput, _GENERATED_HEADER + """ - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IAdditionOperators, + global::System.Numerics.IAdditionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField + right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField + right.StructField)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator +(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(left.StructField + right); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator +(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left + right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(checked(left.StructField + right)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked +(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left + right.StructField)); + } + } + + """); - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } + AssertOutput(subtractionOperatorsOutput, _GENERATED_HEADER + """ - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.ISubtractionOperators, + global::System.Numerics.ISubtractionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField - right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField - right.StructField)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator -(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(left.StructField - right); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator -(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left - right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(checked(left.StructField - right)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked -(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left - right.StructField)); + } + } + + """); - partial void FactoryPostInit(); + AssertOutput(multiplyOperatorsOutput, _GENERATED_HEADER + """ - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IMultiplyOperators, + global::System.Numerics.IMultiplyOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField * right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField * right.StructField)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator *(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(left.StructField * right); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator *(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left * right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(checked(left.StructField * right)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked *(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left * right.StructField)); + } + } + + """); - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } + AssertOutput(divisionOperatorsOutput, _GENERATED_HEADER + """ - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); + namespace Thinktecture.Tests; + + partial class TestValueObject : + global::System.Numerics.IDivisionOperators, + global::System.Numerics.IDivisionOperators + { + /// + public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left.StructField / right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left.StructField / right.StructField)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator /(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(left.StructField / right); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator /(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(left / right.StructField); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(global::Thinktecture.Tests.TestValueObject left, int right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + return Create(checked(left.StructField / right)); + } + + /// + public static global::Thinktecture.Tests.TestValueObject operator checked /(int left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return Create(checked(left / right.StructField)); + } + } + + """); + } - return obj.StructField; - } + [Fact] + public void Should_generate_class_with_string_key_member_and_NullInFactoryMethodsYieldsNull() + { + var source = @" +using System; +using Thinktecture; - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } +namespace Thinktecture.Tests +{ + [ValueObject(NullInFactoryMethodsYieldsNull = true)] + public partial class TestValueObject + { + public readonly string ReferenceField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - this.StructField = structField; - } + AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); + AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - static partial void ValidateConstructorArguments(ref int structField); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(referenceField is null) + { + obj = default; + return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static global::Thinktecture.Tests.TestValueObject? Create(string? referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj; + } + + public static bool TryCreate( + string? referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); + } - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; + [Fact] + public void Should_generate_class_with_string_key_member_and_EmptyStringInFactoryMethodsYieldsNull() + { + var source = @" +using System; +using Thinktecture; - if (global::System.Object.ReferenceEquals(this, other)) - return true; +namespace Thinktecture.Tests +{ + [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] + public partial class TestValueObject + { + public readonly string ReferenceField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); - return this.StructField.Equals(other.StructField); - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } + AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); + AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} + AssertOutput(mainOutput, _GENERATED_HEADER + """ -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(global::System.String.IsNullOrWhiteSpace(referenceField)) + { + obj = default; + return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject? Create(string? referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj; + } + + public static bool TryCreate( + string? referenceField, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] - public void Should_generate_class_with_int_key_member_and_EmptyStringInFactoryMethodsYieldsNull_should_be_ignored() + public void Should_generate_class_with_int_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored() { var source = @" using System; @@ -5376,7 +5269,7 @@ public void Should_generate_class_with_int_key_member_and_EmptyStringInFactoryMe namespace Thinktecture.Tests { - [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] + [ValueObject(NullInFactoryMethodsYieldsNull = true)] public partial class TestValueObject { public readonly int StructField; @@ -5409,164 +5302,167 @@ public partial class TestValueObject AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); - - var convertToKey = new global::System.Func(static item => item.StructField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - int structField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref structField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(structField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(int structField) - { - var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - int structField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(structField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - int global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.StructField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.StructField; - } - - /// - /// Explicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) - { - if(obj is null) - throw new global::System.NullReferenceException(); - - return obj.StructField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) - { - return global::Thinktecture.Tests.TestValueObject.Create(structField); - } - - private TestValueObject(int structField) - { - ValidateConstructorArguments(ref structField); - - this.StructField = structField; - } - - static partial void ValidateConstructorArguments(ref int structField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return this.StructField.Equals(other.StructField); - } - - /// - public override int GetHashCode() - { - return global::System.HashCode.Combine(this.StructField); - } - - /// - public override string ToString() - { - return this.StructField.ToString(); - } - } -} - -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); } [Fact] - public void Should_generate_keyed_value_type_if_second_member_is_ignored() + public void Should_generate_class_with_int_key_member_and_EmptyStringInFactoryMethodsYieldsNull_should_be_ignored() { var source = @" using System; @@ -5574,184 +5470,388 @@ public void Should_generate_keyed_value_type_if_second_member_is_ignored() namespace Thinktecture.Tests { - [ValueObject] + [ValueObject(EmptyStringInFactoryMethodsYieldsNull = true)] public partial class TestValueObject { - public readonly string ReferenceField; - - [ValueObjectMemberIgnore] - public readonly string OtherField; + public readonly int StructField; } } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; + var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; + var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; + var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; - AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); - AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); - AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); - AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - - AssertOutput(mainOutput, _GENERATED_HEADER + """ - -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) - { - if(referenceField is null) - return null; - - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); + AssertOutput(formattableOutput, _FORMATTABLE_INT); + AssertOutput(comparableOutput, _COMPARABLE_INT); + AssertOutput(parsableOutput, _PARSABLE_INT); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); + AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); + AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); + AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); + AssertOutput(divisionOperatorsOutput, _DIVISION_OPERATORS_INT); - this.ReferenceField = referenceField; - } + AssertOutput(mainOutput, _GENERATED_HEADER + """ - static partial void ValidateConstructorArguments(ref string referenceField); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + int structField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref structField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(structField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(int structField) + { + var validationResult = Validate(structField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + int structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + int global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(int structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref int structField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string ToString() + { + return this.StructField.ToString(); + } + } + } + + """); + } - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + [Fact] + public void Should_generate_keyed_value_type_if_second_member_is_ignored() + { + var source = @" +using System; +using Thinktecture; - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; +namespace Thinktecture.Tests +{ + [ValueObject] + public partial class TestValueObject + { + public readonly string ReferenceField; - if (global::System.Object.ReferenceEquals(this, other)) - return true; + [ValueObjectMemberIgnore] + public readonly string OtherField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(5); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } + AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); + AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); + AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} + AssertOutput(mainOutput, _GENERATED_HEADER + """ -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] @@ -5788,158 +5888,161 @@ public partial class TestValueObject AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - string global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) - { - if(referenceField is null) - return null; - - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(string referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref string referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return global::Thinktecture.ComparerAccessors.StringOrdinal.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinal.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(string), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static explicit operator global::Thinktecture.Tests.TestValueObject?(string? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(string referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref string referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinal.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinal.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] @@ -5973,158 +6076,161 @@ public class Foo AssertOutput(mainOutput, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] - partial class TestValueObject : global::System.IEquatable, - global::Thinktecture.IKeyedValueObject, - global::Thinktecture.IKeyedValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); - global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); - global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); - - var convertToKey = new global::System.Func(static item => item.ReferenceField); - global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::Thinktecture.Tests.Foo), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); - - global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - global::Thinktecture.Tests.Foo? referenceField, - out global::Thinktecture.Tests.TestValueObject? obj) - { - if(referenceField is null) - { - obj = default; - return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); - } - - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(global::Thinktecture.Tests.Foo referenceField) - { - var validationResult = Validate(referenceField, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - global::Thinktecture.Tests.Foo referenceField, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::Thinktecture.Tests.Foo referenceField); - - partial void FactoryPostInit(); - - /// - /// Gets the identifier of the item. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - global::Thinktecture.Tests.Foo global::Thinktecture.IKeyedValueObject.GetKey() - { - return this.ReferenceField; - } - - /// - /// Implicit conversion to the type . - /// - /// Object to covert. - /// The of provided or default if is null. - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] - public static implicit operator global::Thinktecture.Tests.Foo?(global::Thinktecture.Tests.TestValueObject? obj) - { - return obj?.ReferenceField; - } - - /// - /// Explicit conversion from the type . - /// - /// Value to covert. - /// An instance of . - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] - public static explicit operator global::Thinktecture.Tests.TestValueObject?(global::Thinktecture.Tests.Foo? referenceField) - { - if(referenceField is null) - return null; - - return global::Thinktecture.Tests.TestValueObject.Create(referenceField); - } - - private TestValueObject(global::Thinktecture.Tests.Foo referenceField) - { - ValidateConstructorArguments(ref referenceField); - - this.ReferenceField = referenceField; - } - - static partial void ValidateConstructorArguments(ref global::Thinktecture.Tests.Foo referenceField); - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return this.ReferenceField.ToString(); - } - } -} - -"""); + namespace Thinktecture.Tests + { + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectConverter, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static referenceField => global::Thinktecture.Tests.TestValueObject.Create(referenceField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static referenceField => new global::Thinktecture.Tests.TestValueObject(referenceField); + + var convertToKey = new global::System.Func(static item => item.ReferenceField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.ReferenceField; + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::Thinktecture.Tests.Foo), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + global::Thinktecture.Tests.Foo? referenceField, + global::System.IFormatProvider? provider, + out global::Thinktecture.Tests.TestValueObject? obj) + { + if(referenceField is null) + { + obj = default; + return new global::System.ComponentModel.DataAnnotations.ValidationResult("The argument 'referenceField' must not be null.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestValueObject.ReferenceField))); + } + + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(global::Thinktecture.Tests.Foo referenceField) + { + var validationResult = Validate(referenceField, null, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + global::Thinktecture.Tests.Foo referenceField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, null, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref global::Thinktecture.Tests.Foo referenceField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + global::Thinktecture.Tests.Foo global::Thinktecture.IValueObjectConverter.ToValue() + { + return this.ReferenceField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static implicit operator global::Thinktecture.Tests.Foo?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.ReferenceField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("referenceField")] + public static explicit operator global::Thinktecture.Tests.TestValueObject?(global::Thinktecture.Tests.Foo? referenceField) + { + if(referenceField is null) + return null; + + return global::Thinktecture.Tests.TestValueObject.Create(referenceField); + } + + private TestValueObject(global::Thinktecture.Tests.Foo referenceField) + { + ValidateConstructorArguments(ref referenceField); + + this.ReferenceField = referenceField; + } + + static partial void ValidateConstructorArguments(ref global::Thinktecture.Tests.Foo referenceField); + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return this.ReferenceField.ToString(); + } + } + } + + """); } [Fact] @@ -6173,169 +6279,694 @@ public int SetterProperty AssertOutput(output, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - o.ReferenceField, - o.StructField, - o.ReferenceProperty, - o.NullableReferenceProperty, - o.StructProperty, - o.NullableStructProperty - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + o.ReferenceField, + o.StructField, + o.ReferenceProperty, + o.NullableReferenceProperty, + o.StructProperty, + o.NullableStructProperty + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField, ref int structField, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + partial void FactoryPostInit(); + + private TestValueObject(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + ValidateConstructorArguments(ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + this.ReferenceField = referenceField; + this.StructField = structField; + this.ReferenceProperty = referenceProperty; + this.NullableReferenceProperty = nullableReferenceProperty; + this.StructProperty = structProperty; + this.NullableStructProperty = nullableStructProperty; + } + + static partial void ValidateConstructorArguments(ref string referenceField, ref int structField, ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField) + && global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.StructField, other.StructField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.StructField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return $"{{ ReferenceField = {this.ReferenceField}, StructField = {this.StructField} }}"; + } + } + } + + """); + } - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + [Fact] + public void Should_generate_class_with_8_members_and_ValueObjectFactoryAttribute() + { + var source = """ + + using System; + using Thinktecture; + + namespace Thinktecture.Tests + { + [ValueObject] + [ValueObjectFactory] + public partial class TestValueObject + { + [ValueObjectMemberEqualityComparer] + public readonly string ReferenceField; + + [ValueObjectMemberEqualityComparer, int>] + public readonly int StructField; + + public string ReferenceProperty { get; } + public string? NullableReferenceProperty { get; } + public int StructProperty { get; } + public int? NullableStructProperty { get; } + + public int ExpressionBodyProperty => 42; + + public int GetterExpressionProperty + { + get => 42; + } + + public int GetterBodyProperty + { + get { return 42; } + } + + public int SetterProperty + { + set { } + } + } + } + + """; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(2); - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string referenceField, - int structField, - string referenceProperty, - string? nullableReferenceProperty, - int structProperty, - int? nullableStructProperty, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) - { - var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out global::Thinktecture.Tests.TestValueObject? obj); + AssertOutput(mainOutput, _GENERATED_HEADER + """ - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject, + global::Thinktecture.IValueObjectFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + o.ReferenceField, + o.StructField, + o.ReferenceProperty, + o.NullableReferenceProperty, + o.StructProperty, + o.NullableStructProperty + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField, ref int structField, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + partial void FactoryPostInit(); + + private TestValueObject(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + ValidateConstructorArguments(ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + this.ReferenceField = referenceField; + this.StructField = structField; + this.ReferenceProperty = referenceProperty; + this.NullableReferenceProperty = nullableReferenceProperty; + this.StructProperty = structProperty; + this.NullableStructProperty = nullableStructProperty; + } + + static partial void ValidateConstructorArguments(ref string referenceField, ref int structField, ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField) + && global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.StructField, other.StructField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.StructField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return $"{{ ReferenceField = {this.ReferenceField}, StructField = {this.StructField} }}"; + } + } + } + + """); - return obj!; - } + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - public static bool TryCreate( - string referenceField, - int structField, - string referenceProperty, - string? nullableReferenceProperty, - int structProperty, - int? nullableStructProperty, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out obj); + namespace Thinktecture.Tests; - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField, ref int structField, ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var validationResult = Validate(s, provider, out var result); - partial void FactoryPostInit(); + if(validationResult is null) + return result!; - private TestValueObject(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) - { - ValidateConstructorArguments(ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); - - this.ReferenceField = referenceField; - this.StructField = structField; - this.ReferenceProperty = referenceProperty; - this.NullableReferenceProperty = nullableReferenceProperty; - this.StructProperty = structProperty; - this.NullableStructProperty = nullableStructProperty; - } + throw new global::System.FormatException(validationResult.ErrorMessage); + } - static partial void ValidateConstructorArguments(ref string referenceField, ref int structField, ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; + var validationResult = Validate(s, provider, out result!); + return validationResult is null; + } + } - return obj.Equals(other); - } + """); + } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } + [Fact] + public void Should_generate_class_with_8_members_and_ValueObjectFactoryAttribute_and_UseForSerialization() + { + var source = """ + + using System; + using Thinktecture; + + namespace Thinktecture.Tests + { + [ValueObject] + [ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] + public partial class TestValueObject + { + [ValueObjectMemberEqualityComparer] + public readonly string ReferenceField; + + [ValueObjectMemberEqualityComparer, int>] + public readonly int StructField; + + public string ReferenceProperty { get; } + public string? NullableReferenceProperty { get; } + public int StructProperty { get; } + public int? NullableStructProperty { get; } + + public int ExpressionBodyProperty => 42; + + public int GetterExpressionProperty + { + get => 42; + } + + public int GetterBodyProperty + { + get { return 42; } + } + + public int SetterProperty + { + set { } + } + } + } + + """; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(2); - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; + AssertOutput(mainOutput, _GENERATED_HEADER + """ - if (global::System.Object.ReferenceEquals(this, other)) - return true; + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject, + global::Thinktecture.IValueObjectFactory, + global::Thinktecture.IValueObjectConverter + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + o.ReferenceField, + o.StructField, + o.ReferenceProperty, + o.NullableReferenceProperty, + o.StructProperty, + o.NullableStructProperty + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField, + int structField, + string referenceProperty, + string? nullableReferenceProperty, + int structProperty, + int? nullableStructProperty, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField, structField, referenceProperty, nullableReferenceProperty, structProperty, nullableStructProperty, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField, ref int structField, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + partial void FactoryPostInit(); + + private TestValueObject(string referenceField, int structField, string referenceProperty, string? nullableReferenceProperty, int structProperty, int? nullableStructProperty) + { + ValidateConstructorArguments(ref referenceField, ref structField, ref referenceProperty, ref nullableReferenceProperty, ref structProperty, ref nullableStructProperty); + + this.ReferenceField = referenceField; + this.StructField = structField; + this.ReferenceProperty = referenceProperty; + this.NullableReferenceProperty = nullableReferenceProperty; + this.StructProperty = structProperty; + this.NullableStructProperty = nullableStructProperty; + } + + static partial void ValidateConstructorArguments(ref string referenceField, ref int structField, ref string referenceProperty, ref string? nullableReferenceProperty, ref int structProperty, ref int? nullableStructProperty); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField) + && global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.StructField, other.StructField); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.StructField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return $"{{ ReferenceField = {this.ReferenceField}, StructField = {this.StructField} }}"; + } + } + } + + """); - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField) - && global::Thinktecture.ComparerAccessors.Default.EqualityComparer.Equals(this.StructField, other.StructField); - } + AssertOutput(parsableOutput, _GENERATED_HEADER + """ - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.StructField, global::Thinktecture.ComparerAccessors.Default.EqualityComparer); - return hashCode.ToHashCode(); - } + namespace Thinktecture.Tests; - /// - public override string ToString() - { - return $"{{ ReferenceField = {this.ReferenceField}, StructField = {this.StructField} }}"; - } - } -} + partial class TestValueObject : + global::System.IParsable + { + private static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate(string key, global::System.IFormatProvider? provider, out global::Thinktecture.Tests.TestValueObject? result) + where T : global::Thinktecture.IValueObjectFactory + { + return T.Validate(key, provider, out result); + } + + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var validationResult = Validate(s, provider, out var result); + + if(validationResult is null) + return result!; -"""); + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + var validationResult = Validate(s, provider, out result!); + return validationResult is null; + } + } + + """); } [Fact] @@ -6366,194 +6997,194 @@ public partial class TestValueObject AssertOutput(output, _GENERATED_HEADER + """ -namespace Thinktecture.Tests -{ - partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, - global::Thinktecture.IComplexValueObject - { - [global::System.Runtime.CompilerServices.ModuleInitializer] - internal static void ModuleInit() - { - global::System.Linq.Expressions.Expression> action = o => new - { - o.ReferenceField1, - o.ReferenceField2, - o.ReferenceField3, - o.ReferenceField4, - o.ReferenceField5, - o.ReferenceField6, - o.ReferenceField7, - o.ReferenceField8, - o.ReferenceField9 - }; - - var members = new global::System.Collections.Generic.List(); - - foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) - { - members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); - } - - var type = typeof(global::Thinktecture.Tests.TestValueObject); - var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); - - global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); - } - - private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); - - public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( - string referenceField1, - string referenceField2, - string referenceField3, - string referenceField4, - string referenceField5, - string referenceField6, - string referenceField7, - string referenceField8, - string referenceField9, - out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - ValidateFactoryArguments(ref validationResult, ref referenceField1, ref referenceField2, ref referenceField3, ref referenceField4, ref referenceField5, ref referenceField6, ref referenceField7, ref referenceField8, ref referenceField9); - - if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - { - obj = new global::Thinktecture.Tests.TestValueObject(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9); - obj.FactoryPostInit(); - } - else - { - obj = default; - } - - return validationResult; - } - - public static global::Thinktecture.Tests.TestValueObject Create(string referenceField1, string referenceField2, string referenceField3, string referenceField4, string referenceField5, string referenceField6, string referenceField7, string referenceField8, string referenceField9) - { - var validationResult = Validate(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9, out global::Thinktecture.Tests.TestValueObject? obj); - - if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) - throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); - - return obj!; - } - - public static bool TryCreate( - string referenceField1, - string referenceField2, - string referenceField3, - string referenceField4, - string referenceField5, - string referenceField6, - string referenceField7, - string referenceField8, - string referenceField9, - [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) - { - var validationResult = Validate(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9, out obj); - - return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; - } - - static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref string referenceField1, ref string referenceField2, ref string referenceField3, ref string referenceField4, ref string referenceField5, ref string referenceField6, ref string referenceField7, ref string referenceField8, ref string referenceField9); - - partial void FactoryPostInit(); - - private TestValueObject(string referenceField1, string referenceField2, string referenceField3, string referenceField4, string referenceField5, string referenceField6, string referenceField7, string referenceField8, string referenceField9) - { - ValidateConstructorArguments(ref referenceField1, ref referenceField2, ref referenceField3, ref referenceField4, ref referenceField5, ref referenceField6, ref referenceField7, ref referenceField8, ref referenceField9); - - this.ReferenceField1 = referenceField1; - this.ReferenceField2 = referenceField2; - this.ReferenceField3 = referenceField3; - this.ReferenceField4 = referenceField4; - this.ReferenceField5 = referenceField5; - this.ReferenceField6 = referenceField6; - this.ReferenceField7 = referenceField7; - this.ReferenceField8 = referenceField8; - this.ReferenceField9 = referenceField9; - } - - static partial void ValidateConstructorArguments(ref string referenceField1, ref string referenceField2, ref string referenceField3, ref string referenceField4, ref string referenceField5, ref string referenceField6, ref string referenceField7, ref string referenceField8, ref string referenceField9); - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if objects are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - if (obj is null) - return other is null; - - return obj.Equals(other); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if objects are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) - { - return !(obj == other); - } - - /// - public override bool Equals(object? other) - { - return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); - } - - /// - public bool Equals(global::Thinktecture.Tests.TestValueObject? other) - { - if (other is null) - return false; - - if (global::System.Object.ReferenceEquals(this, other)) - return true; - - return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField1, other.ReferenceField1) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField2, other.ReferenceField2) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField3, other.ReferenceField3) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField4, other.ReferenceField4) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField5, other.ReferenceField5) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField6, other.ReferenceField6) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField7, other.ReferenceField7) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField8, other.ReferenceField8) - && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField9, other.ReferenceField9); - } - - /// - public override int GetHashCode() - { - var hashCode = new global::System.HashCode(); - hashCode.Add(this.ReferenceField1, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField2, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField3, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField4, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField5, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField6, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField7, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField8, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - hashCode.Add(this.ReferenceField9, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - return $"{{ ReferenceField1 = {this.ReferenceField1}, ReferenceField2 = {this.ReferenceField2}, ReferenceField3 = {this.ReferenceField3}, ReferenceField4 = {this.ReferenceField4}, ReferenceField5 = {this.ReferenceField5}, ReferenceField6 = {this.ReferenceField6}, ReferenceField7 = {this.ReferenceField7}, ReferenceField8 = {this.ReferenceField8}, ReferenceField9 = {this.ReferenceField9} }}"; - } - } -} - -"""); + namespace Thinktecture.Tests + { + partial class TestValueObject : global::System.IEquatable, + global::System.Numerics.IEqualityOperators, + global::Thinktecture.IComplexValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Linq.Expressions.Expression> action = o => new + { + o.ReferenceField1, + o.ReferenceField2, + o.ReferenceField3, + o.ReferenceField4, + o.ReferenceField5, + o.ReferenceField6, + o.ReferenceField7, + o.ReferenceField8, + o.ReferenceField9 + }; + + var members = new global::System.Collections.Generic.List(); + + foreach (var arg in ((global::System.Linq.Expressions.NewExpression)action.Body).Arguments) + { + members.Add(((global::System.Linq.Expressions.MemberExpression)arg).Member); + } + + var type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.ComplexValueObjectMetadata(type, members.AsReadOnly()); + + global::Thinktecture.Internal.ComplexValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + string referenceField1, + string referenceField2, + string referenceField3, + string referenceField4, + string referenceField5, + string referenceField6, + string referenceField7, + string referenceField8, + string referenceField9, + out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + ValidateFactoryArguments(ref validationResult, ref referenceField1, ref referenceField2, ref referenceField3, ref referenceField4, ref referenceField5, ref referenceField6, ref referenceField7, ref referenceField8, ref referenceField9); + + if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + { + obj = new global::Thinktecture.Tests.TestValueObject(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9); + obj.FactoryPostInit(); + } + else + { + obj = default; + } + + return validationResult; + } + + public static global::Thinktecture.Tests.TestValueObject Create(string referenceField1, string referenceField2, string referenceField3, string referenceField4, string referenceField5, string referenceField6, string referenceField7, string referenceField8, string referenceField9) + { + var validationResult = Validate(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + string referenceField1, + string referenceField2, + string referenceField3, + string referenceField4, + string referenceField5, + string referenceField6, + string referenceField7, + string referenceField8, + string referenceField9, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(referenceField1, referenceField2, referenceField3, referenceField4, referenceField5, referenceField6, referenceField7, referenceField8, referenceField9, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField1, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField2, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField3, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField4, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField5, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField6, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField7, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField8, [global::System.Diagnostics.CodeAnalysis.AllowNullAttribute, global::System.Diagnostics.CodeAnalysis.NotNullAttribute] ref string referenceField9); + + partial void FactoryPostInit(); + + private TestValueObject(string referenceField1, string referenceField2, string referenceField3, string referenceField4, string referenceField5, string referenceField6, string referenceField7, string referenceField8, string referenceField9) + { + ValidateConstructorArguments(ref referenceField1, ref referenceField2, ref referenceField3, ref referenceField4, ref referenceField5, ref referenceField6, ref referenceField7, ref referenceField8, ref referenceField9); + + this.ReferenceField1 = referenceField1; + this.ReferenceField2 = referenceField2; + this.ReferenceField3 = referenceField3; + this.ReferenceField4 = referenceField4; + this.ReferenceField5 = referenceField5; + this.ReferenceField6 = referenceField6; + this.ReferenceField7 = referenceField7; + this.ReferenceField8 = referenceField8; + this.ReferenceField9 = referenceField9; + } + + static partial void ValidateConstructorArguments(ref string referenceField1, ref string referenceField2, ref string referenceField3, ref string referenceField4, ref string referenceField5, ref string referenceField6, ref string referenceField7, ref string referenceField8, ref string referenceField9); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::Thinktecture.Tests.TestValueObject? other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField1, other.ReferenceField1) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField2, other.ReferenceField2) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField3, other.ReferenceField3) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField4, other.ReferenceField4) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField5, other.ReferenceField5) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField6, other.ReferenceField6) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField7, other.ReferenceField7) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField8, other.ReferenceField8) + && global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField9, other.ReferenceField9); + } + + /// + public override int GetHashCode() + { + var hashCode = new global::System.HashCode(); + hashCode.Add(this.ReferenceField1, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField2, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField3, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField4, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField5, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField6, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField7, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField8, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + hashCode.Add(this.ReferenceField9, global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer); + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + return $"{{ ReferenceField1 = {this.ReferenceField1}, ReferenceField2 = {this.ReferenceField2}, ReferenceField3 = {this.ReferenceField3}, ReferenceField4 = {this.ReferenceField4}, ReferenceField5 = {this.ReferenceField5}, ReferenceField6 = {this.ReferenceField6}, ReferenceField7 = {this.ReferenceField7}, ReferenceField8 = {this.ReferenceField8}, ReferenceField9 = {this.ReferenceField9} }}"; + } + } + } + + """); } } diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/EnumWithFactory.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/EnumWithFactory.cs new file mode 100644 index 00000000..1cec1517 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/EnumWithFactory.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Runtime.Tests.TestEnums; + +[SmartEnum] +[ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] +public sealed partial class EnumWithFactory +{ + public static readonly EnumWithFactory Item1 = new(1); + public static readonly EnumWithFactory Item2 = new(2); + + public static ValidationResult? Validate(string? value, IFormatProvider? provider, out EnumWithFactory? item) + { + switch (value) + { + case "=1=": + item = Item1; + return ValidationResult.Success; + case "=2=": + item = Item2; + return ValidationResult.Success; + default: + item = null; + return new ValidationResult($"Unknown item '{value}'"); + } + } + + public string ToValue() + { + return $"={Key}="; + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/TestEnumWithReservedIdentifier.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/TestEnumWithReservedIdentifier.cs new file mode 100644 index 00000000..a98f8773 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/TestEnumWithReservedIdentifier.cs @@ -0,0 +1,8 @@ +namespace Thinktecture.Runtime.Tests.TestEnums; + +[SmartEnum] +public sealed partial class TestEnumWithReservedIdentifier +{ + public static readonly TestEnumWithReservedIdentifier Operator = new(1); + public static readonly TestEnumWithReservedIdentifier True = new(2); +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactories.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactories.cs new file mode 100644 index 00000000..a618ec3c --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactories.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Runtime.Tests.TestValueObjects; + +[ValueObject] +[ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] +[ValueObjectFactory>] +public sealed partial class BoundaryWithFactories +{ + public decimal Lower { get; } + public decimal Upper { get; } + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref decimal lower, ref decimal upper) + { + if (lower <= upper) + return; + + validationResult = new ValidationResult($"Lower boundary '{lower}' must be less than upper boundary '{upper}'", + new[] { nameof(Lower), nameof(Upper) }); + } + + public static ValidationResult? Validate(string? value, IFormatProvider? provider, out BoundaryWithFactories? item) + { + item = null; + + if (value is null) + return ValidationResult.Success; + + var parts = value.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + return new ValidationResult("Invalid format."); + + if (!Decimal.TryParse(parts[0], provider, out var lower) || !Decimal.TryParse(parts[1], provider, out var upper)) + return new ValidationResult("The provided values are not numbers."); + + return Validate(lower, upper, out item); + } + + public string ToValue() + { + return $"{Lower}:{Upper}"; + } + + public static ValidationResult? Validate((int, int) value, IFormatProvider? provider, out BoundaryWithFactories? item) + { + return Validate(value.Item1, value.Item2, out item); + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactoryAndExplicitImplementation.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactoryAndExplicitImplementation.cs new file mode 100644 index 00000000..7af23d26 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithFactoryAndExplicitImplementation.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Runtime.Tests.TestValueObjects; + +[ValueObject] +[ValueObjectFactory(UseForSerialization = SerializationFrameworks.All)] +public sealed partial class BoundaryWithFactoryAndExplicitImplementation +{ + public decimal Lower { get; } + public decimal Upper { get; } + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref decimal lower, ref decimal upper) + { + if (lower <= upper) + return; + + validationResult = new ValidationResult($"Lower boundary '{lower}' must be less than upper boundary '{upper}'", + new[] { nameof(Lower), nameof(Upper) }); + } + + static ValidationResult? IValueObjectFactory.Validate(string? value, IFormatProvider? provider, out BoundaryWithFactoryAndExplicitImplementation? item) + { + item = null; + + if (value is null) + return ValidationResult.Success; + + var parts = value.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + return new ValidationResult("Invalid format."); + + if (!Decimal.TryParse(parts[0], provider, out var lower) || !Decimal.TryParse(parts[1], provider, out var upper)) + return new ValidationResult("The provided values are not numbers."); + + return Validate(lower, upper, out item); + } + + string IValueObjectConverter.ToValue() + { + return $"{Lower}:{Upper}"; + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithStrings.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithStrings.cs new file mode 100644 index 00000000..b7c0c8e9 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/BoundaryWithStrings.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace Thinktecture.Runtime.Tests.TestValueObjects; + +[ValueObject] +public sealed partial class BoundaryWithStrings +{ + public string Lower { get; } + public string? Upper { get; } + + static partial void ValidateFactoryArguments(ref ValidationResult? validationResult, ref string lower, ref string? upper) + { + if (lower is null) + { + lower = null!; + validationResult = new ValidationResult("Lower boundary must not be null."); + return; + } + + if (upper is null || lower.Length <= upper.Length) + return; + + validationResult = new ValidationResult($"The length of lower boundary '{lower}' must be less than the length of the upper boundary '{upper}'"); + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/ComplexValueObjectWithReservedIdentifiers.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/ComplexValueObjectWithReservedIdentifiers.cs new file mode 100644 index 00000000..b5b594fd --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/ComplexValueObjectWithReservedIdentifiers.cs @@ -0,0 +1,8 @@ +namespace Thinktecture.Runtime.Tests.TestValueObjects; + +[ValueObject] +public sealed partial class ComplexValueObjectWithReservedIdentifiers +{ + public int Operator { get; } + public int? True { get; } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/SimpleValueObjectWithReservedIdentifiers.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/SimpleValueObjectWithReservedIdentifiers.cs new file mode 100644 index 00000000..39c32136 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/SimpleValueObjectWithReservedIdentifiers.cs @@ -0,0 +1,7 @@ +namespace Thinktecture.Runtime.Tests.TestValueObjects; + +[ValueObject] +public sealed partial class SimpleValueObjectWithReservedIdentifiers +{ + public int Operator { get; } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObject.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObject.cs index 5b1f47a4..67a29ed8 100644 --- a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObject.cs +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObject.cs @@ -12,6 +12,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(property)) { + property = null!; validationResult = new ValidationResult("Property cannot be empty."); return; } diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithEmptyStringInFactoryMethodsYieldsNull.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithEmptyStringInFactoryMethodsYieldsNull.cs index e6aeec5f..358cf295 100644 --- a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithEmptyStringInFactoryMethodsYieldsNull.cs +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithEmptyStringInFactoryMethodsYieldsNull.cs @@ -12,6 +12,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(property)) { + property = null!; validationResult = new ValidationResult("Property cannot be empty."); return; } diff --git a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithNullInFactoryMethodsYieldsNull.cs b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithNullInFactoryMethodsYieldsNull.cs index 8f3fef10..65138edc 100644 --- a/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithNullInFactoryMethodsYieldsNull.cs +++ b/test/Thinktecture.Runtime.Extensions.Tests.Shared/TestValueObjects/StringBasedReferenceValueObjectWithNullInFactoryMethodsYieldsNull.cs @@ -12,6 +12,7 @@ static partial void ValidateFactoryArguments(ref ValidationResult? validationRes { if (String.IsNullOrWhiteSpace(property)) { + property = null!; validationResult = new ValidationResult("Property cannot be empty."); return; } diff --git a/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/ToValue.cs b/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/ToValue.cs new file mode 100644 index 00000000..d70d7d4c --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/ToValue.cs @@ -0,0 +1,16 @@ +using Thinktecture.Runtime.Tests.TestEnums; + +namespace Thinktecture.Runtime.Tests.EnumTests; + +public class ToValue +{ + [Fact] + public void Should_return_value_using_factory_specified_via_ValueObjectFactoryAttribute() + { + ((IValueObjectConverter)EnumWithFactory.Item1).ToValue() + .Should().Be("=1="); + + ((IValueObjectConverter)EnumWithFactory.Item1).ToValue() + .Should().Be(1); + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/Validate.cs b/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/Validate.cs index bc9acb73..ca6fb52c 100644 --- a/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/Validate.cs +++ b/test/Thinktecture.Runtime.Extensions.Tests/EnumTests/Validate.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Thinktecture.Runtime.Tests.TestEnums; namespace Thinktecture.Runtime.Tests.EnumTests; @@ -8,14 +9,14 @@ public class Validate [Fact] public void Should_return_error_if_null_is_provided() { - var testEnumValidationResult = TestEnum.Validate(null!, out var testEnum); + var testEnumValidationResult = TestEnum.Validate(null!, null, out var testEnum); testEnumValidationResult.Should().NotBeNull(); testEnumValidationResult.ErrorMessage.Should().Be("There is no item of type 'TestEnum' with the identifier ''."); testEnumValidationResult.MemberNames.Should().BeEquivalentTo(nameof(TestEnum.Key)); testEnum.Should().BeNull(); - var validTestEnumValidationResult = ValidTestEnum.Validate(null!, out var validTestEnum); + var validTestEnumValidationResult = ValidTestEnum.Validate(null!, null, out var validTestEnum); validTestEnumValidationResult.Should().NotBeNull(); validTestEnumValidationResult.ErrorMessage.Should().Be("There is no item of type 'ValidTestEnum' with the identifier ''."); validTestEnumValidationResult.MemberNames.Should().BeEquivalentTo(nameof(TestEnum.Key)); @@ -26,7 +27,7 @@ public void Should_return_error_if_null_is_provided() [Fact] public void Should_return_invalid_item_if_enum_doesnt_have_any_items() { - var validationResult = EmptyEnum.Validate("unknown", out var item); + var validationResult = EmptyEnum.Validate("unknown", null, out var item); validationResult.Should().NotBeNull(); validationResult.ErrorMessage.Should().Be("There is no item of type 'EmptyEnum' with the identifier 'unknown'."); @@ -40,7 +41,7 @@ public void Should_return_invalid_item_if_enum_doesnt_have_any_items() [Fact] public void Should_return_invalid_item_if_enum_doesnt_have_item_with_provided_key() { - var validationResult = TestEnum.Validate("unknown", out var item); + var validationResult = TestEnum.Validate("unknown", null, out var item); validationResult.Should().NotBeNull(); validationResult.ErrorMessage.Should().Be("There is no item of type 'TestEnum' with the identifier 'unknown'."); @@ -54,48 +55,48 @@ public void Should_return_invalid_item_if_enum_doesnt_have_item_with_provided_ke [Fact] public void Should_throw_if_CreateInvalidItem_uses_key_of_valid_item() { - Action action = () => TestEnumWithInvalidCreateInvalidItem.Validate(TestEnumWithInvalidCreateInvalidItem.INVALID_KEY_FOR_TESTING_KEY_REUSE, out _); + Action action = () => TestEnumWithInvalidCreateInvalidItem.Validate(TestEnumWithInvalidCreateInvalidItem.INVALID_KEY_FOR_TESTING_KEY_REUSE, null, out _); action.Should().Throw().WithMessage("The implementation of method 'CreateInvalidItem' must not return an instance with property 'Key' equals to one of a valid item."); } [Fact] public void Should_throw_if_CreateInvalidItem_isValid_is_true() { - Action action = () => TestEnumWithInvalidCreateInvalidItem.Validate(TestEnumWithInvalidCreateInvalidItem.INVALID_KEY_FOR_TESTING_IS_VALID_TRUE, out _); + Action action = () => TestEnumWithInvalidCreateInvalidItem.Validate(TestEnumWithInvalidCreateInvalidItem.INVALID_KEY_FOR_TESTING_IS_VALID_TRUE, null, out _); action.Should().Throw().WithMessage("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); } [Fact] public void Should_throw_if_custom_validation_throws() { - Action action = () => TestEnum.Validate(String.Empty, out _); + Action action = () => TestEnum.Validate(String.Empty, null, out _); action.Should().Throw().WithMessage("Key cannot be empty."); } [Fact] public void Should_return_item_with_provided_key() { - TestEnum.Validate("item2", out var testEnum).Should().BeNull(); + TestEnum.Validate("item2", null, out var testEnum).Should().BeNull(); testEnum.Should().Be(TestEnum.Item2); - ValidTestEnum.Validate("item1", out var validTestEnum).Should().BeNull(); + ValidTestEnum.Validate("item1", null, out var validTestEnum).Should().BeNull(); validTestEnum.Should().Be(ValidTestEnum.Item1); } [Fact] public void Should_return_item_with_provided_key_ignoring_casing() { - TestEnum.Validate("Item1", out var item).Should().BeNull(); + TestEnum.Validate("Item1", null, out var item).Should().BeNull(); item.Should().Be(TestEnum.Item1); - TestEnum.Validate("item1", out item).Should().BeNull(); + TestEnum.Validate("item1", null, out item).Should().BeNull(); item.Should().Be(TestEnum.Item1); } [Fact] public void Should_return_invalid_item_if_the_casing_does_not_match_according_to_comparer() { - var validationResult = TestEnumWithNonDefaultComparer.Validate("Item2", out var item); + var validationResult = TestEnumWithNonDefaultComparer.Validate("Item2", null, out var item); validationResult.Should().NotBeNull(); validationResult.ErrorMessage.Should().Be("There is no item of type 'TestEnumWithNonDefaultComparer' with the identifier 'Item2'."); validationResult.MemberNames.Should().BeEquivalentTo(nameof(TestEnum.Key)); @@ -107,19 +108,28 @@ public void Should_return_invalid_item_if_the_casing_does_not_match_according_to [Fact] public void Should_return_derived_type() { - EnumWithDerivedType.Validate(2, out var item).Should().BeNull(); + EnumWithDerivedType.Validate(2, null, out var item).Should().BeNull(); item.Should().Be(EnumWithDerivedType.ItemOfDerivedType); - AbstractEnum.Validate(1, out var otherItem).Should().BeNull(); + AbstractEnum.Validate(1, null, out var otherItem).Should().BeNull(); otherItem.Should().Be(AbstractEnum.Item); } [Fact] public void Should_return_error_if_key_is_unknown_to_non_validatable_enum() { - var validationResult = ValidTestEnum.Validate("invalid", out var item); + var validationResult = ValidTestEnum.Validate("invalid", null, out var item); validationResult.ErrorMessage.Should().Be("There is no item of type 'ValidTestEnum' with the identifier 'invalid'."); item.Should().BeNull(); } + + [Fact] + public void Should_return_item_using_factory_specified_via_ValueObjectFactoryAttribute() + { + var validationResult = EnumWithFactory.Validate("=1=", null, out var item); + validationResult.Should().Be(ValidationResult.Success); + + item.Should().Be(EnumWithFactory.Item1); + } } diff --git a/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/ToValue.cs b/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/ToValue.cs new file mode 100644 index 00000000..bd551286 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/ToValue.cs @@ -0,0 +1,14 @@ +using Thinktecture.Runtime.Tests.TestValueObjects; + +namespace Thinktecture.Runtime.Tests.ValueObjectTests; + +public class ToValue +{ + [Fact] + public void Should_return_value_using_factory_specified_via_ValueObjectFactoryAttribute() + { + BoundaryWithFactories.Create(1, 2) + .ToValue() + .Should().Be("1:2"); + } +} diff --git a/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/Validate.cs b/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/Validate.cs new file mode 100644 index 00000000..976481a9 --- /dev/null +++ b/test/Thinktecture.Runtime.Extensions.Tests/ValueObjectTests/Validate.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using Thinktecture.Runtime.Tests.TestValueObjects; + +namespace Thinktecture.Runtime.Tests.ValueObjectTests; + +public class Validate +{ + [Fact] + public void Should_create_BoundaryWithFactories_from_string() + { + BoundaryWithFactories.Validate("1:2", null, out var boundary) + .Should().Be(ValidationResult.Success); + + boundary.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } + + [Fact] + public void Should_create_BoundaryWithFactories_from_tuple() + { + BoundaryWithFactories.Validate((1, 2), null, out var boundary) + .Should().Be(ValidationResult.Success); + + boundary.Should().BeEquivalentTo(BoundaryWithFactories.Create(1, 2)); + } + + [Fact] + public void Should_return_error_on_creation_of_BoundaryWithFactories_with_invalid_parameter() + { + BoundaryWithFactories.Validate("1", null, out var boundary) + .Should().BeEquivalentTo(new ValidationResult("Invalid format.")); + + boundary.Should().BeNull(); + } +}