diff --git a/Consumers.sln.DotSettings b/Consumers.sln.DotSettings index f719822611..1737c4adcc 100644 --- a/Consumers.sln.DotSettings +++ b/Consumers.sln.DotSettings @@ -1,4 +1,5 @@  + False DO_NOT_SHOW <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> diff --git a/docs/site/Writerside/hi.tree b/docs/site/Writerside/hi.tree index 598e0c7e70..e11601df62 100644 --- a/docs/site/Writerside/hi.tree +++ b/docs/site/Writerside/hi.tree @@ -31,6 +31,7 @@ + diff --git a/docs/site/Writerside/topics/reference/EfCoreIntegrationHowTo.md b/docs/site/Writerside/topics/reference/EfCoreIntegrationHowTo.md index 3e87b9c87e..83bca49c9e 100644 --- a/docs/site/Writerside/topics/reference/EfCoreIntegrationHowTo.md +++ b/docs/site/Writerside/topics/reference/EfCoreIntegrationHowTo.md @@ -15,7 +15,7 @@ public partial class Name ``` Another way, if you're using .NET 8 or greater, is to use `EfCoreConverter` attributes on -a marker class: +a marker class, normally in a separate project from the value objects themselves: ```c# [EfCoreConverter] diff --git a/docs/site/Writerside/topics/reference/Integration.md b/docs/site/Writerside/topics/reference/Integration.md index 3ad93e3a37..84a3c10c87 100644 --- a/docs/site/Writerside/topics/reference/Integration.md +++ b/docs/site/Writerside/topics/reference/Integration.md @@ -73,7 +73,12 @@ public enum Conversions /// /// Sets the SerializeFn and DeSerializeFn members in JsConfig in a static constructor. /// - ServiceStackDotText = 1 << 7 + ServiceStackDotText = 1 << 7, + + /// + /// Creates a BSON serializer for each value object. + /// + Bson = 1 << 8, } ``` @@ -93,7 +98,8 @@ Other converters/serializers are: They are controlled by the `Conversions` enum. The following has serializers for NSJ and STJ: ```c# -[ValueObject(conversions: Conversions.NewtonsoftJson | Conversions.SystemTextJson, underlyingType: typeof(float))] +[ValueObject(conversions: + Conversions.NewtonsoftJson | Conversions.SystemTextJson)] public readonly partial struct Celsius { } ``` diff --git a/docs/site/Writerside/topics/reference/MongoIntegrationHowTo.md b/docs/site/Writerside/topics/reference/MongoIntegrationHowTo.md new file mode 100644 index 0000000000..c087d0a9c8 --- /dev/null +++ b/docs/site/Writerside/topics/reference/MongoIntegrationHowTo.md @@ -0,0 +1,57 @@ +# Integration with MongoDB + +It is possible to use value objects (VOs) in MongoDB. + +To generate a converter (serializer), add the `Bson` conversion in the attribute, e.g. + +```c# +[ValueObject(conversions: Conversions.Bson)] +public partial class Name +{ + public static readonly Name NotSet = new("[NOT_SET]"); +} +``` + +Now that the serializers are generated, you now need to register them. +Vogen generates a static class named `RegisterBsonSerializersFor[NameOfProject]`. +This static class has a static method named `TryRegister`, which registers the serializers if they're not already registered, e.g.: + +```C# +BsonSerializer.TryRegisterSerializer(new CustomerIdBsonSerializer()); +BsonSerializer.TryRegisterSerializer(new EnergyUsedBsonSerializer()); +``` +A [MongoDB example is included in the source](https://github.com/SteveDunn/Vogen/tree/main/samples/Vogen.Examples/SerializationAndConversion/MongoScenario). + +Below is a walkthrough of that sample. + +The sample uses MongoDB to read and write entities (a `Person`) to a MongoDB database in a testcontainer. +Note that attributes on the value objects do not specify the BSON serializer; that is specified in global config in `ModuleInitializer.cs`: + +```c# +[ValueObject] +public readonly partial struct Age; + +[ValueObject] +public readonly partial struct Name; + +public class Person +{ + public Name Name { get; set; } + public Age Age { get; set; } +} +``` + +This simple example registers the serializers manually: +```C# +BsonSerializer.RegisterSerializer(new NameBsonSerializer()); +BsonSerializer.RegisterSerializer(new AgeBsonSerializer()); +``` + +… but it could just as easily registered them with the generated register: +```C# +BsonSerializationRegisterForVogen_Examples.TryRegister(); +``` + +(_replace `Vogen_Examples` with the name of *your* project_) + +Next, it adds a bunch of `Person` objects to the database, each containing value objects representing age and name, and then reads them back. \ No newline at end of file diff --git a/samples/AotTrimmedSample/AotTrimmedSample.v3.ncrunchproject b/samples/AotTrimmedSample/AotTrimmedSample.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/AotTrimmedSample/AotTrimmedSample.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/samples/Onion/Domain/Domain.v3.ncrunchproject b/samples/Onion/Domain/Domain.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/Onion/Domain/Domain.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/samples/Onion/Infra/Infra.v3.ncrunchproject b/samples/Onion/Infra/Infra.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/Onion/Infra/Infra.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/samples/Vogen.Examples/ModuleInitializer.cs b/samples/Vogen.Examples/ModuleInitializer.cs index c0d7a35d0f..974fcf8b07 100644 --- a/samples/Vogen.Examples/ModuleInitializer.cs +++ b/samples/Vogen.Examples/ModuleInitializer.cs @@ -5,7 +5,9 @@ using Vogen; using Vogen.Examples.Types; -[assembly: VogenDefaults(staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon)] +[assembly: VogenDefaults( + staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon | StaticAbstractsGeneration.InstanceMethodsAndProperties, + conversions: Conversions.Default | Conversions.Bson)] namespace Vogen.Examples; @@ -14,7 +16,8 @@ public static class ModuleInitializer [ModuleInitializer] public static void Init() { - MappingSchema.Default.SetConverter(dt => TimeOnly.FromDateTime(dt)); + MappingSchema.Default.SetConverter(TimeOnly.FromDateTime); + SqlMapper.AddTypeHandler(new DapperDateTimeOffsetVo.DapperTypeHandler()); SqlMapper.AddTypeHandler(new DapperIntVo.DapperTypeHandler()); SqlMapper.AddTypeHandler(new DapperStringVo.DapperTypeHandler()); diff --git a/samples/Vogen.Examples/Program.cs b/samples/Vogen.Examples/Program.cs index 9de6356885..f0bd9fe0d1 100644 --- a/samples/Vogen.Examples/Program.cs +++ b/samples/Vogen.Examples/Program.cs @@ -7,22 +7,20 @@ namespace Vogen.Examples class Program { // ReSharper disable once UnusedParameter.Local - static Task Main(string[] args) + static async Task Main(string[] args) { var scenarioTypes = typeof(Program).Assembly.GetTypes() .Where(t => typeof(IScenario).IsAssignableFrom(t) && t != typeof(IScenario)).ToList(); foreach (var eachScenarioType in scenarioTypes) { - var instance = (IScenario)Activator.CreateInstance(eachScenarioType); + var instance = (IScenario)Activator.CreateInstance(eachScenarioType)!; WriteBanner(instance); - instance!.Run(); + await instance.Run(); } Console.WriteLine("Finished"); - - return Task.CompletedTask; } private static void WriteBanner(IScenario scenario) diff --git a/samples/Vogen.Examples/SerializationAndConversion/Mongo/MongoScenario.cs b/samples/Vogen.Examples/SerializationAndConversion/Mongo/MongoScenario.cs new file mode 100644 index 0000000000..b8cf3731c1 --- /dev/null +++ b/samples/Vogen.Examples/SerializationAndConversion/Mongo/MongoScenario.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Bogus; +using JetBrains.Annotations; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver; +using Testcontainers.MongoDb; + +namespace Vogen.Examples.SerializationAndConversion.Mongo; + + +[ValueObject] +public readonly partial struct Age; + +[ValueObject] +public readonly partial struct Name; + +[UsedImplicitly] +public class Person +{ + [BsonId] + public ObjectId Id { get; set; } + public Name Name { get; set; } + public Age Age { get; set; } +} + +[UsedImplicitly] +public class MongoScenario : IScenario +{ + public async Task Run() + { + string runnerOs = Environment.GetEnvironmentVariable("RUNNER_OS"); + + bool isLocalOrLinuxOnGitHub = string.IsNullOrEmpty(runnerOs) || runnerOs == "Linux"; + + if (!isLocalOrLinuxOnGitHub) + { + Console.WriteLine("Skipping because not running locally or on Linux on a GitHub action."); + return; + } + + MongoDbContainer container = new MongoDbBuilder().WithImage("mongo:latest").Build(); + + await container.StartAsync(); + + var client = new MongoClient(container.GetConnectionString()); + + var database = client.GetDatabase("testDatabase"); + var collection = database.GetCollection("peopleCollection"); + + BsonSerializer.RegisterSerializer(new NameBsonSerializer()); + BsonSerializer.RegisterSerializer(new AgeBsonSerializer()); + + // or, use the generated one for all value objects... + // BsonSerializationRegisterForVogen_Examples.TryRegister(); + + var personFaker = new Faker() + .RuleFor(p => p.Name, f => Name.From(f.Name.FirstName())) + .RuleFor(p => p.Age, f => Age.From(DateTime.Now.Year - f.Person.DateOfBirth.Year)); + + foreach (Person eachPerson in personFaker.Generate(10)) + { + await collection.InsertOneAsync(eachPerson); + } + + Console.WriteLine("Inserted people... Now finding them..."); + + IAsyncCursor people = await collection.FindAsync("{}"); + await people.ForEachAsync((person) => Console.WriteLine($"{person.Name} is {person.Age}")); + + await container.DisposeAsync(); + } +} + + + +// Note, if you don't want any generated BSON serializers, you can specify your own generic one like the one below. +// Be aware that you'll need specify static abstracts generation in global config for this to work: +// [assembly: VogenDefaults( +// staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon | StaticAbstractsGeneration.InstanceMethodsAndProperties, +// conversions: Conversions.Default | Conversions.Bson)] + + // ReSharper disable once UnusedType.Global + public class BsonVogenSerializer + : SerializerBase where TValue : IVogen +{ + private readonly IBsonSerializer _serializer = BsonSerializer.LookupSerializer(); + + public override TValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => + TValue.From(_serializer.Deserialize(context, args)); + + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value) => + _serializer.Serialize(context, args, value.Value); +} diff --git a/samples/Vogen.Examples/Vogen.Examples.csproj b/samples/Vogen.Examples/Vogen.Examples.csproj index a9aaddd5e0..d1c09c2c3e 100644 --- a/samples/Vogen.Examples/Vogen.Examples.csproj +++ b/samples/Vogen.Examples/Vogen.Examples.csproj @@ -9,6 +9,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -17,11 +18,14 @@ + + + diff --git a/samples/WebApplication/WebApplication.net8.0.v3.ncrunchproject b/samples/WebApplication/WebApplication.net8.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/WebApplication/WebApplication.net8.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/samples/WebApplication/WebApplication.net9.0.v3.ncrunchproject b/samples/WebApplication/WebApplication.net9.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/WebApplication/WebApplication.net9.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/samples/WebApplicationConsumer/WebApplicationConsumer.v3.ncrunchproject b/samples/WebApplicationConsumer/WebApplicationConsumer.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/samples/WebApplicationConsumer/WebApplicationConsumer.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/Vogen.SharedTypes/Conversions.cs b/src/Vogen.SharedTypes/Conversions.cs index 895e0f0da0..191e6ba382 100644 --- a/src/Vogen.SharedTypes/Conversions.cs +++ b/src/Vogen.SharedTypes/Conversions.cs @@ -55,5 +55,10 @@ public enum Conversions /// /// Sets the SerializeFn and DeSerializeFn members in JsConfig in a static constructor. /// - ServiceStackDotText = 1 << 7 + ServiceStackDotText = 1 << 7, + + /// + /// Creates a BSON serializer for each value object. + /// + Bson = 1 << 8 } \ No newline at end of file diff --git a/src/Vogen/ValueObjectGenerator.cs b/src/Vogen/ValueObjectGenerator.cs index 80b759f3c1..5fd49a737b 100644 --- a/src/Vogen/ValueObjectGenerator.cs +++ b/src/Vogen/ValueObjectGenerator.cs @@ -97,6 +97,8 @@ private static void Execute( WriteOpenApiSchemaCustomizationCode.WriteIfNeeded(globalConfig, spc, compilation, workItems); WriteEfCoreSpecs.WriteIfNeeded(spc, compilation, efCoreConverterSpecs); + + WriteBsonSerializers.WriteIfNeeded(spc, compilation, workItems); if (workItems.Count > 0) { diff --git a/src/Vogen/WriteBsonSerializers.cs b/src/Vogen/WriteBsonSerializers.cs new file mode 100644 index 0000000000..a3e775cace --- /dev/null +++ b/src/Vogen/WriteBsonSerializers.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace Vogen; + +internal class WriteBsonSerializers +{ + public static void WriteIfNeeded(SourceProductionContext context, Compilation compilation, List workItems) + { + if (!compilation.IsAtLeastCSharpVersion(LanguageVersion.CSharp12)) + { + return; + } + + var items = workItems.Where(i => i.Config.Conversions.HasFlag(Conversions.Bson)).ToList(); + + var toWrite = items.Select(GenerateSerializerReadyForWriting).ToList(); + + foreach (var eachToWrite in toWrite) + { + WriteSerializer(eachToWrite, context); + } + + WriteRegisterer(toWrite, compilation, context); + } + + private static void WriteSerializer(SerializerEntry eachToWrite, SourceProductionContext context) + { + SourceText sourceText = SourceText.From(eachToWrite.SourceCode, Encoding.UTF8); + + Util.TryWriteUsingUniqueFilename(eachToWrite.Filename, context, sourceText); + } + + private static void WriteRegisterer(List items, Compilation compilation, SourceProductionContext context) + { + if (items.Count == 0) + { + return; + } + + var assemblyName = compilation.AssemblyName?.Replace(".", "_") ?? ""; + if (assemblyName.EndsWith("_dll") || assemblyName.EndsWith("_exe")) + { + assemblyName = assemblyName[..^4]; + } + + string className = $"BsonSerializationRegister"; + if(assemblyName.Length > 0) + { + className = className + "For" + assemblyName; + } + + string source = + $$""" + {{GeneratedCodeSegments.Preamble}} + + public static class {{className}} + { + static {{className}}() + { + {{TextForEachRegisterCall(items)}} + } + + public static void TryRegister() { } + } + """; + + SourceText sourceText = SourceText.From(source, Encoding.UTF8); + + Util.TryWriteUsingUniqueFilename(className, context, sourceText); + + } + + private static string TextForEachRegisterCall(List items) + { + StringBuilder sb = new(); + foreach (SerializerEntry eachEntry in items) + { + sb.AppendLine($"global::MongoDB.Bson.Serialization.BsonSerializer.TryRegisterSerializer(new {eachEntry.SerializerFullyQualifiedName}());"); + } + + return sb.ToString(); + } + + public record SerializerEntry(string SerializerFullyQualifiedName, string Filename, string SourceCode); + + private static SerializerEntry GenerateSerializerReadyForWriting(VoWorkItem spec) + { + var fullNamespace = spec.FullNamespace; + + var isPublic = spec.WrapperType.DeclaredAccessibility.HasFlag(Accessibility.Public); + var accessor = isPublic ? "public" : "internal"; + + var ns = string.IsNullOrEmpty(fullNamespace) ? string.Empty : $"namespace {fullNamespace};"; + + string wrapperName = Util.EscapeIfRequired(spec.WrapperType.Name); + string underlyingTypeName = spec.UnderlyingTypeFullName; + + string sb = + $$""" + {{GeneratedCodeSegments.Preamble}} + + {{ns}} + + {{accessor}} partial class {{wrapperName}}BsonSerializer : global::MongoDB.Bson.Serialization.Serializers.SerializerBase<{{wrapperName}}> + { + private readonly global::MongoDB.Bson.Serialization.IBsonSerializer<{{underlyingTypeName}}> _serializer = global::MongoDB.Bson.Serialization.BsonSerializer.LookupSerializer<{{underlyingTypeName}}>(); + + public override {{wrapperName}} Deserialize(global::MongoDB.Bson.Serialization.BsonDeserializationContext context, global::MongoDB.Bson.Serialization.BsonDeserializationArgs args) + { + var newArgs = new global::MongoDB.Bson.Serialization.BsonDeserializationArgs { NominalType = typeof({{underlyingTypeName}}) }; + + return Deserialize(_serializer.Deserialize(context, newArgs)); + } + + public override void Serialize(global::MongoDB.Bson.Serialization.BsonSerializationContext context, global::MongoDB.Bson.Serialization.BsonSerializationArgs args, {{wrapperName}} value) => + _serializer.Serialize(context, args, value.Value); + + static {{wrapperName}} Deserialize({{underlyingTypeName}} value) => UnsafeDeserialize(default, value); + + [global::System.Runtime.CompilerServices.UnsafeAccessor(global::System.Runtime.CompilerServices.UnsafeAccessorKind.StaticMethod, Name = "__Deserialize")] + static extern {{wrapperName}} UnsafeDeserialize({{wrapperName}} @this, {{underlyingTypeName}} value); + + } + """; + + var fn = string.IsNullOrEmpty(fullNamespace) ? "" : fullNamespace + "."; + string serializerFqn = $"{fn}{wrapperName}BsonSerializer"; + + var unsanitized = $"{spec.WrapperType.ToDisplayString()}_bson.g.cs"; + string filename = Util.SanitizeToALegalFilename(unsanitized); + return new SerializerEntry(serializerFqn, filename, sb); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/BsonSerializerButUsesJson.cs b/tests/ConsumerTests/BsonSerializerButUsesJson.cs new file mode 100644 index 0000000000..d5276dc7d1 --- /dev/null +++ b/tests/ConsumerTests/BsonSerializerButUsesJson.cs @@ -0,0 +1,29 @@ +using System.IO; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; + +namespace ConsumerTests; + +internal static class BsonSerializerButUsesJson +{ + public static string Serialize(T input) + { + TextWriter sw = new StringWriter(); + + IBsonWriter writer = new JsonWriter(sw); + BsonSerializationContext context = BsonSerializationContext.CreateRoot(writer); + + BsonSerializer.LookupSerializer().Serialize(context, input); + + sw.Flush(); + + return sw.ToString()!; + } + + public static T Deserialize(string input) + { + IBsonReader reader = new JsonReader(input); + var context = BsonDeserializationContext.CreateRoot(reader); + return BsonSerializer.LookupSerializer().Deserialize(context); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/BsonTests.cs b/tests/ConsumerTests/BsonTests.cs new file mode 100644 index 0000000000..6d13bcfbd1 --- /dev/null +++ b/tests/ConsumerTests/BsonTests.cs @@ -0,0 +1,78 @@ +using System.IO; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; + +namespace ConsumerTests.BsonTests; + +[ValueObject] +public readonly partial struct Age; + +[ValueObject] +public readonly partial struct Name; + +public class Person +{ + public Name Name { get; init; } + public Age Age { get; init; } +} + +public class BsonTests +{ + private readonly IBsonSerializer _serializer; + + public BsonTests() => _serializer = BsonSerializer.LookupSerializer(); + + // The register for all serializers is generated and is called in ModuleInitialization.cs (BsonSerializationRegisterForConsumerTests.TryRegister()) + [Fact] + public void Value_objects_are_written_as_primitives() + { + Person person = new() + { + Age = Age.From(42), + Name = Name.From("Fred Flintstone") + }; + + TextWriter sw = new StringWriter(); + + IBsonWriter writer = new JsonWriter(sw); + BsonSerializationContext context = BsonSerializationContext.CreateRoot(writer); + + _serializer.Serialize(context, person); + + sw.Flush(); + sw.ToString().Should().Be($$"""{ "Name" : "Fred Flintstone", "Age" : 42 }"""); + } + + [Fact] + public void Value_objects_are_read_as_primitives() + { + using IBsonReader reader = new JsonReader("""{ "Name" : "Fred Flintstone", "Age" : 42 }"""); + var context = BsonDeserializationContext.CreateRoot(reader); + + Person person = _serializer.Deserialize(context); + person.Age.Value.Should().Be(42); + person.Name.Value.Should().Be("Fred Flintstone"); + } + + [SkippableIfBuiltWithNoValidationFlagFact] + public void Missing_values_are_allowed_when_configured() + { + using IBsonReader reader = new JsonReader("""{ "Age" : 42 }"""); + var context = BsonDeserializationContext.CreateRoot(reader); + + Person person = _serializer.Deserialize(context); + person.Age.Value.Should().Be(42); + person.Name.IsInitialized().Should().BeFalse(); + } + + [SkippableIfNotBuiltWithNoValidationFlagFact] + public void Missing_values_are_still_show_as_initialized_if_built_with_no_validation_preprocessor() + { + using IBsonReader reader = new JsonReader("""{ "Age" : 42 }"""); + var context = BsonDeserializationContext.CreateRoot(reader); + + Person person = _serializer.Deserialize(context); + person.Age.Value.Should().Be(42); + person.Name.IsInitialized().Should().BeTrue(); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/ConsumerTests.csproj b/tests/ConsumerTests/ConsumerTests.csproj index f3d1ca6da7..791cc3dab5 100644 --- a/tests/ConsumerTests/ConsumerTests.csproj +++ b/tests/ConsumerTests/ConsumerTests.csproj @@ -54,6 +54,8 @@ Run the tests with `.\test.ps1` in the root folder.--> + + diff --git a/tests/ConsumerTests/ConsumerTests.v3.ncrunchproject b/tests/ConsumerTests/ConsumerTests.v3.ncrunchproject index fa6fdebe2c..e1dbb83f7c 100644 --- a/tests/ConsumerTests/ConsumerTests.v3.ncrunchproject +++ b/tests/ConsumerTests/ConsumerTests.v3.ncrunchproject @@ -3,5 +3,20 @@ ..\..\local-global-packages\**.* + + + + ConsumerTests.SerializationAndConversionTests.RecordClassVos.BsonSerializationTests.RoundTrip_TimeOnly + + + ConsumerTests.SerializationAndConversionTests.ClassVos.BsonSerializationTests.RoundTrip_TimeOnly + + + ConsumerTests.SerializationAndConversionTests.RecordStructVos.BsonSerializationTests.RoundTrip_TimeOnly + + + ConsumerTests.SerializationAndConversionTests.StructVos.BsonSerializationTests.RoundTrip_TimeOnly + + \ No newline at end of file diff --git a/tests/ConsumerTests/ModuleInitialization.cs b/tests/ConsumerTests/ModuleInitialization.cs index de8b4dee24..de969ed6b3 100644 --- a/tests/ConsumerTests/ModuleInitialization.cs +++ b/tests/ConsumerTests/ModuleInitialization.cs @@ -2,6 +2,8 @@ using Dapper; using LinqToDB.Mapping; +[assembly: VogenDefaults(conversions: Conversions.Default | Conversions.Bson)] + namespace ConsumerTests; public static class ModuleInitialization @@ -9,6 +11,8 @@ public static class ModuleInitialization [ModuleInitializer] public static void Init() { + BsonSerializationRegisterForConsumerTests.TryRegister(); + SqlMapper.AddTypeHandler(new DeserializationValidationTests.MyVoInt_should_not_bypass_validation.DapperTypeHandler()); SqlMapper.AddTypeHandler(new DeserializationValidationTests.MyVoString_should_not_bypass_validation.DapperTypeHandler()); SqlMapper.AddTypeHandler(new DeserializationValidationTests.MyVoString_should_bypass_validation.DapperTypeHandler()); diff --git a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/BsonSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/BsonSerializationTests.cs new file mode 100644 index 0000000000..1505644e00 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/BsonSerializationTests.cs @@ -0,0 +1,171 @@ +using FluentAssertions.Extensions; +using Vogen.IntegrationTests.TestTypes; +using Vogen.IntegrationTests.TestTypes.ClassVos; +// ReSharper disable NullableWarningSuppressionIsUsed + +namespace ConsumerTests.SerializationAndConversionTests.ClassVos; + +public class BsonSerializationTests +{ + [Fact] + public void RoundTrip_Bool() + { + var vo = BsonBoolVo.From(true); + + string json = BsonSerializerButUsesJson.Serialize(vo); + + BsonBoolVo deserialised = BsonSerializerButUsesJson.Deserialize(json); + + vo.Value.Should().Be(deserialised.Value); + } + + [Fact] + public void RoundTrip_Byte() + { + byte value = 123; + var vo = BsonByteVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Char() + { + char value = 'a'; + var vo = BsonCharVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip() + { + var value = "ABC"; + var vo = BsonStringVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTimeOffset() + { + var vo = BsonDateTimeOffsetVo.From(Primitives.DateTimeOffset1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTime() + { + var vo = BsonDateTimeVo.From(Primitives.DateTime1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + // micro and non-second not populated in the Mongo C# driver + vo.Value.Should().BeCloseTo(deserializedVo.Value, 1.Milliseconds()); + } + + [Fact] + public void RoundTrip_Decimal() + { + var vo = BsonDecimalVo.From(123.45m); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Double() + { + var vo = BsonDoubleVo.From(123.45d); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Float() + { + var vo = BsonFloatVo.From(123.45f); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123.45f); + } + + [Fact] + public void RoundTrip_Guid() + { + var vo = BsonGuidVo.From(Primitives.Guid1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Guid1); + } + + [Fact] + public void RoundTrip_Int() + { + var vo = BsonLongVo.From(123L); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_ShortBsonProvider() + { + var vo = BsonShortVo.From(123); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_String() + { + var vo = BsonStringVo.From("aaa"); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be("aaa"); + } + + [SkippableFact] + public void RoundTrip_TimeOnly() + { + Skip.If(true, "Unsupported in the C# driver - https://jira.mongodb.org/browse/CSHARP-3717"); + var vo = BsonTimeOnlyVo.From(Primitives.Time1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Time1); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ByteVoTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ByteVoTests.cs index be0ac8649c..f2e9dd1bde 100644 --- a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ByteVoTests.cs +++ b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ByteVoTests.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Threading.Tasks; +using ConsumerTests; using Dapper; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -83,6 +84,18 @@ public void CanSerializeToByte_WithSsdtProvider() vo.Value.Should().Be(deserialised.Value); } + + [Fact] + public void CanSerializeToByte_WithBson() + { + var vo = BsonShortVo.From(123); + + string json = BsonSerializerButUsesJson.Serialize(vo); + + BsonShortVo deserialised = BsonSerializerButUsesJson.Deserialize(json); + + vo.Value.Should().Be(deserialised.Value); + } [Fact] diff --git a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ServiceStackDotTextSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ServiceStackDotTextSerializationTests.cs index e841826455..7382549a66 100644 --- a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ServiceStackDotTextSerializationTests.cs +++ b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/ServiceStackDotTextSerializationTests.cs @@ -8,7 +8,7 @@ namespace ConsumerTests.SerializationAndConversionTests.ClassVos; public class ServiceStackDotTextSerializationTests { [Fact] - public void RoundTrip_Bool_WithSsdtProvider() + public void RoundTrip_Bool() { var vo = SsdtBoolVo.From(true); @@ -20,7 +20,7 @@ public void RoundTrip_Bool_WithSsdtProvider() } [Fact] - public void RoundTrip_Byte_WithSsdtProvider() + public void RoundTrip_Byte() { byte value = 123; var vo = SsdtByteVo.From(value); @@ -32,7 +32,7 @@ public void RoundTrip_Byte_WithSsdtProvider() } [Fact] - public void RoundTrip_Char_WithSsdtProvider() + public void RoundTrip_Char() { char value = 'a'; var vo = SsdtCharVo.From(value); @@ -44,7 +44,7 @@ public void RoundTrip_Char_WithSsdtProvider() } [Fact] - public void RoundTrip_WithSsdtProvider() + public void RoundTrip() { var value = "ABC"; var vo = SsdtStringVo.From(value); @@ -56,7 +56,7 @@ public void RoundTrip_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTimeOffset_WithSsdtProvider() + public void RoundTrip_DateTimeOffset() { var vo = SsdtDateTimeOffsetVo.From(Primitives.DateTimeOffset1); var json = JsonSerializer.SerializeToString(vo); @@ -67,7 +67,7 @@ public void RoundTrip_DateTimeOffset_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTime_WithSsdtProvider() + public void RoundTrip_DateTime() { var vo = SsdtDateTimeVo.From(Primitives.DateTime1); var json = JsonSerializer.SerializeToString(vo); @@ -78,7 +78,7 @@ public void RoundTrip_DateTime_WithSsdtProvider() } [Fact] - public void RoundTrip_Decimal_WithSsdtProvider() + public void RoundTrip_Decimal() { var vo = SsdtDecimalVo.From(123.45m); @@ -90,7 +90,7 @@ public void RoundTrip_Decimal_WithSsdtProvider() } [Fact] - public void RoundTrip_Double_WithSsdtProvider() + public void RoundTrip_Double() { var vo = SsdtDoubleVo.From(123.45d); @@ -102,7 +102,7 @@ public void RoundTrip_Double_WithSsdtProvider() } [Fact] - public void RoundTrip_Float_WithSsdtProvider() + public void RoundTrip_Float() { var vo = SsdtFloatVo.From(123.45f); @@ -113,7 +113,7 @@ public void RoundTrip_Float_WithSsdtProvider() } [Fact] - public void RoundTrip_Guid_WithSsdtProvider() + public void RoundTrip_Guid() { var vo = SsdtGuidVo.From(Primitives.Guid1); @@ -124,7 +124,7 @@ public void RoundTrip_Guid_WithSsdtProvider() } [Fact] - public void RoundTrip_Int_WithSsdtProvider() + public void RoundTrip_Int() { var vo = SsdtLongVo.From(123L); @@ -146,7 +146,7 @@ public void RoundTrip_ShortSsdtProvider() } [Fact] - public void RoundTrip_String_WithSsdtProvider() + public void RoundTrip_String() { var vo = SsdtStringVo.From("aaa"); @@ -157,7 +157,7 @@ public void RoundTrip_String_WithSsdtProvider() } [Fact] - public void RoundTrip_TimeOnly_WithSsdtProvider() + public void RoundTrip_TimeOnly() { var vo = SsdtTimeOnlyVo.From(Primitives.Time1); diff --git a/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/Types/BsonTypes.cs b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/Types/BsonTypes.cs new file mode 100644 index 0000000000..c31dcc81c8 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/ClassVos/Types/BsonTypes.cs @@ -0,0 +1,55 @@ +namespace Vogen.IntegrationTests.TestTypes.ClassVos; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(bool))] +public partial class BsonBoolVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(float))] +public partial class BsonFloatVo; + + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(byte))] +public partial class BsonByteVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(char))] +public partial class BsonCharVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateOnly))] +// ReSharper disable once UnusedType.Global - The C# driver doesn't yet support this +public partial class BsonDateOnlyVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTimeOffset))] +public partial class BsonDateTimeOffsetVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTime))] +public partial class BsonDateTimeVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(decimal))] +public partial class BsonDecimalVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(double))] +public partial class BsonDoubleVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Bar))] +public partial class BsonFooVo +{ + public static BsonFooVo Parse(string s) => throw new Exception("todo!"); +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Guid))] +public partial class BsonGuidVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(int))] +public partial class BsonIntVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(long))] +public partial class BsonLongVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(short))] +public partial class BsonShortVo; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(string))] +public partial class BsonStringVo; + +// The C# driver doesn't yet support this +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(TimeOnly))] +public partial class BsonTimeOnlyVo; \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/ComplexSerializationTests_Bson.cs b/tests/ConsumerTests/SerializationAndConversionTests/ComplexSerializationTests_Bson.cs new file mode 100644 index 0000000000..dc29564a75 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/ComplexSerializationTests_Bson.cs @@ -0,0 +1,240 @@ +#nullable disable +using ConsumerTests; + +// ReSharper disable FunctionComplexityOverflow + +namespace MediumTests.SerializationAndConversionTests; + + +public partial class ComplexSerializationTests +{ + [Fact] + public void Bson_CanSerializeAndDeserialize() + { + var complex = new ComplexBson(); + + + string serialized = BsonSerializerButUsesJson.Serialize(complex); + ComplexBson deserialized = BsonSerializerButUsesJson.Deserialize(serialized); + + deserialized.ClassVos_BsonBoolVo.Value.Should().Be(true); + deserialized.ClassVos_BsonByteVo.Value.Should().Be(1); + deserialized.ClassVos_BsonCharVo.Value.Should().Be('2'); + deserialized.ClassVos_BsonDateTimeOffsetVo.Value.Should().Be(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, TimeSpan.Zero)); + deserialized.ClassVos_BsonDateTimeVo.Value.Should().Be(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + deserialized.ClassVos_BsonDecimalVo.Value.Should().Be(3.33m); + deserialized.ClassVos_BsonDoubleVo.Value.Should().Be(4.44d); + deserialized.ClassVos_BsonFloatVo.Value.Should().Be(5.55f); + deserialized.ClassVos_BsonFooVo.Value.Age.Should().Be(42); + deserialized.ClassVos_BsonFooVo.Value.Name.Should().Be("Fred"); + deserialized.ClassVos_BsonGuidVo.Value.Should().Be(Guid.Empty); + deserialized.ClassVos_BsonIntVo.Value.Should().Be(6); + deserialized.ClassVos_BsonLongVo.Value.Should().Be(7L); + deserialized.ClassVos_BsonStringVo.Value.Should().Be("8"); + + deserialized.RecordClassVos_BsonBoolVo.Value.Should().Be(true); + deserialized.RecordClassVos_BsonBoolVo.Value.Should().Be(true); + deserialized.RecordClassVos_BsonByteVo.Value.Should().Be(1); + deserialized.RecordClassVos_BsonCharVo.Value.Should().Be('2'); + deserialized.RecordClassVos_BsonDateTimeOffsetVo.Value.Should() + .Be(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, TimeSpan.Zero)); + deserialized.RecordClassVos_BsonDateTimeVo.Value.Should().Be(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + deserialized.RecordClassVos_BsonDecimalVo.Value.Should().Be(3.33m); + deserialized.RecordClassVos_BsonDoubleVo.Value.Should().Be(4.44d); + deserialized.RecordClassVos_BsonFloatVo.Value.Should().Be(5.55f); + deserialized.RecordClassVos_BsonFooVo.Value.Age.Should().Be(42); + deserialized.RecordClassVos_BsonFooVo.Value.Name.Should().Be("Fred"); + deserialized.RecordClassVos_BsonGuidVo.Value.Should().Be(Guid.Empty); + deserialized.RecordClassVos_BsonIntVo.Value.Should().Be(6); + deserialized.RecordClassVos_BsonLongVo.Value.Should().Be(7L); + + deserialized.StructVos_BsonBoolVo.Value.Should().Be(true); + deserialized.StructVos_BsonByteVo.Value.Should().Be(1); + deserialized.StructVos_BsonCharVo.Value.Should().Be('2'); + deserialized.StructVos_BsonDateTimeOffsetVo.Value.Should().Be(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, TimeSpan.Zero)); + deserialized.StructVos_BsonDateTimeVo.Value.Should().Be(new DateTime(2020, 12, 13, 23, 59, 59, 999)); + deserialized.StructVos_BsonDecimalVo.Value.Should().Be(3.33m); + deserialized.StructVos_BsonDoubleVo.Value.Should().Be(4.44d); + deserialized.StructVos_BsonFloatVo.Value.Should().Be(5.55f); + deserialized.StructVos_BsonFooVo.Value.Age.Should().Be(42); + deserialized.StructVos_BsonFooVo.Value.Name.Should().Be("Fred"); + deserialized.StructVos_BsonGuidVo.Value.Should().Be(Guid.Empty); + deserialized.StructVos_BsonIntVo.Value.Should().Be(6); + deserialized.StructVos_BsonLongVo.Value.Should().Be(7L); + deserialized.StructVos_BsonStringVo.Value.Should().Be("8"); + + deserialized.RecordStructVos_BsonBoolVo.Value.Should().Be(true); + deserialized.RecordStructVos_BsonByteVo.Value.Should().Be(1); + deserialized.RecordStructVos_BsonCharVo.Value.Should().Be('2'); + deserialized.RecordStructVos_BsonDateTimeOffsetVo.Value.Should() + .Be(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, TimeSpan.Zero)); + deserialized.RecordStructVos_BsonDateTimeVo.Value.Should().Be(new DateTime(2020, 12, 13, 23, 59, 59, 999)); + deserialized.RecordStructVos_BsonDecimalVo.Value.Should().Be(3.33m); + deserialized.RecordStructVos_BsonDoubleVo.Value.Should().Be(4.44d); + deserialized.RecordStructVos_BsonFloatVo.Value.Should().Be(5.55f); + deserialized.RecordStructVos_BsonFooVo.Value.Age.Should().Be(42); + deserialized.RecordStructVos_BsonFooVo.Value.Name.Should().Be("Fred"); + deserialized.RecordStructVos_BsonGuidVo.Value.Should().Be(Guid.Empty); + deserialized.RecordStructVos_BsonIntVo.Value.Should().Be(6); + deserialized.RecordStructVos_BsonLongVo.Value.Should().Be(7L); + deserialized.RecordStructVos_BsonStringVo.Value.Should().Be("8"); + } +} + + +public class ComplexBson +{ + public Vogen.IntegrationTests.TestTypes.ClassVos.BsonBoolVo ClassVos_BsonBoolVo { get; set; } = + Vogen.IntegrationTests.TestTypes.ClassVos.BsonBoolVo.From(true); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonByteVo ClassVos_BsonByteVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonByteVo.From(1); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonCharVo ClassVos_BsonCharVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonCharVo.From('2'); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeOffsetVo ClassVos_BsonDateTimeOffsetVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeOffsetVo.From( + new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, TimeSpan.Zero)); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeVo ClassVos_BsonDateTimeVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeVo.From(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDecimalVo ClassVos_BsonDecimalVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDecimalVo.From(3.33m); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDoubleVo ClassVos_BsonDoubleVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDoubleVo.From(4.44d); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonFloatVo ClassVos_BsonFloatVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonFloatVo.From(5.55f); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonFooVo ClassVos_BsonFooVo { get; set; } = Vogen.IntegrationTests.TestTypes.StructVos.BsonFooVo.From(new Vogen.IntegrationTests.TestTypes.StructVos.Bar(42, "Fred")); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonGuidVo ClassVos_BsonGuidVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonGuidVo.From(Guid.Empty); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonIntVo ClassVos_BsonIntVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonIntVo.From(6); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonLongVo ClassVos_BsonLongVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonLongVo.From(7L); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonStringVo ClassVos_BsonStringVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonStringVo.From("8"); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonBoolVo RecordClassVos_BsonBoolVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonBoolVo.From(true); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonByteVo RecordClassVos_BsonByteVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonByteVo.From(1); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonCharVo RecordClassVos_BsonCharVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonCharVo.From('2'); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDateTimeOffsetVo RecordClassVos_BsonDateTimeOffsetVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDateTimeOffsetVo.From(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, + TimeSpan.Zero)); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDateTimeVo RecordClassVos_BsonDateTimeVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDateTimeVo.From(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDecimalVo RecordClassVos_BsonDecimalVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDecimalVo.From(3.33m); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDoubleVo RecordClassVos_BsonDoubleVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonDoubleVo.From(4.44d); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonFloatVo RecordClassVos_BsonFloatVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonFloatVo.From(5.55f); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonFooVo RecordClassVos_BsonFooVo { get; set; } = Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonFooVo.From(new Vogen.IntegrationTests.TestTypes.RecordClassVos.Bar(42, "Fred")); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonGuidVo RecordClassVos_BsonGuidVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonGuidVo.From(Guid.Empty); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonIntVo RecordClassVos_BsonIntVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonIntVo.From(6); + + public Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonLongVo RecordClassVos_BsonLongVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordClassVos.BsonLongVo.From(7L); + + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonBoolVo StructVos_BsonBoolVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonBoolVo.From(true); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonByteVo StructVos_BsonByteVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonByteVo.From(1); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonCharVo StructVos_BsonCharVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonCharVo.From('2'); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeOffsetVo StructVos_BsonDateTimeOffsetVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeOffsetVo.From(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, + TimeSpan.Zero)); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeVo StructVos_BsonDateTimeVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDateTimeVo.From(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDecimalVo StructVos_BsonDecimalVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDecimalVo.From(3.33m); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonDoubleVo StructVos_BsonDoubleVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonDoubleVo.From(4.44d); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonFloatVo StructVos_BsonFloatVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonFloatVo.From(5.55f); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonFooVo StructVos_BsonFooVo { get; set; } = Vogen.IntegrationTests.TestTypes.StructVos.BsonFooVo.From(new Vogen.IntegrationTests.TestTypes.StructVos.Bar(42, "Fred")); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonGuidVo StructVos_BsonGuidVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonGuidVo.From(Guid.Empty); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonIntVo StructVos_BsonIntVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonIntVo.From(6); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonLongVo StructVos_BsonLongVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonLongVo.From(7L); + + public Vogen.IntegrationTests.TestTypes.StructVos.BsonStringVo StructVos_BsonStringVo { get; set; } = + Vogen.IntegrationTests.TestTypes.StructVos.BsonStringVo.From("8"); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonBoolVo RecordStructVos_BsonBoolVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonBoolVo.From(true); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonByteVo RecordStructVos_BsonByteVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonByteVo.From(1); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonCharVo RecordStructVos_BsonCharVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonCharVo.From('2'); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDateTimeOffsetVo RecordStructVos_BsonDateTimeOffsetVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDateTimeOffsetVo.From(new DateTimeOffset(2020, 12, 13, 23, 59, 59, 999, + TimeSpan.Zero)); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDateTimeVo RecordStructVos_BsonDateTimeVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDateTimeVo.From(new DateTime(2020, 12, 13, 23, 59, 59, 999, DateTimeKind.Utc)); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDecimalVo RecordStructVos_BsonDecimalVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDecimalVo.From(3.33m); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDoubleVo RecordStructVos_BsonDoubleVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonDoubleVo.From(4.44d); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonFloatVo RecordStructVos_BsonFloatVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonFloatVo.From(5.55f); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonFooVo RecordStructVos_BsonFooVo { get; set; } = Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonFooVo.From(new Vogen.IntegrationTests.TestTypes.RecordStructVos.Bar(42, "Fred")); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonGuidVo RecordStructVos_BsonGuidVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonGuidVo.From(Guid.Empty); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonIntVo RecordStructVos_BsonIntVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonIntVo.From(6); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonLongVo RecordStructVos_BsonLongVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonLongVo.From(7L); + + public Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonStringVo RecordStructVos_BsonStringVo { get; set; } = + Vogen.IntegrationTests.TestTypes.RecordStructVos.BsonStringVo.From("8"); +} diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/BsonSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/BsonSerializationTests.cs new file mode 100644 index 0000000000..7045cab2e9 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/BsonSerializationTests.cs @@ -0,0 +1,171 @@ +using FluentAssertions.Extensions; +using Vogen.IntegrationTests.TestTypes; +using Vogen.IntegrationTests.TestTypes.RecordClassVos; +// ReSharper disable NullableWarningSuppressionIsUsed + +namespace ConsumerTests.SerializationAndConversionTests.RecordClassVos; + +public class BsonSerializationTests +{ + [Fact] + public void RoundTrip_Bool() + { + var vo = BsonBoolVo.From(true); + + string json = BsonSerializerButUsesJson.Serialize(vo); + + BsonBoolVo deserialised = BsonSerializerButUsesJson.Deserialize(json); + + vo.Value.Should().Be(deserialised.Value); + } + + [Fact] + public void RoundTrip_Byte() + { + byte value = 123; + var vo = BsonByteVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Char() + { + char value = 'a'; + var vo = BsonCharVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip() + { + var value = "ABC"; + var vo = BsonStringVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTimeOffset() + { + var vo = BsonDateTimeOffsetVo.From(Primitives.DateTimeOffset1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTime() + { + var vo = BsonDateTimeVo.From(Primitives.DateTime1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + // micro and non-second not populated in the Mongo C# driver + vo.Value.Should().BeCloseTo(deserializedVo.Value, 1.Milliseconds()); + } + + [Fact] + public void RoundTrip_Decimal() + { + var vo = BsonDecimalVo.From(123.45m); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Double() + { + var vo = BsonDoubleVo.From(123.45d); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Float() + { + var vo = BsonFloatVo.From(123.45f); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123.45f); + } + + [Fact] + public void RoundTrip_Guid() + { + var vo = BsonGuidVo.From(Primitives.Guid1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Guid1); + } + + [Fact] + public void RoundTrip_Int() + { + var vo = BsonLongVo.From(123L); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_ShortBsonProvider() + { + var vo = BsonShortVo.From(123); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_String() + { + var vo = BsonStringVo.From("aaa"); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be("aaa"); + } + + [SkippableFact] + public void RoundTrip_TimeOnly() + { + Skip.If(true, "Unsupported in the C# driver - https://jira.mongodb.org/browse/CSHARP-3717"); + var vo = BsonTimeOnlyVo.From(Primitives.Time1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Time1); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/ServiceStackDotTextSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/ServiceStackDotTextSerializationTests.cs index 3fa2f08f8b..d98fe91bed 100644 --- a/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/ServiceStackDotTextSerializationTests.cs +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/ServiceStackDotTextSerializationTests.cs @@ -8,7 +8,7 @@ namespace ConsumerTests.SerializationAndConversionTests.RecordClassVos; public class ServiceStackDotTextSerializationTests { [Fact] - public void RoundTrip_Bool_WithSsdtProvider() + public void RoundTrip_Bool() { var vo = SsdtBoolVo.From(true); @@ -20,7 +20,7 @@ public void RoundTrip_Bool_WithSsdtProvider() } [Fact] - public void RoundTrip_Byte_WithSsdtProvider() + public void RoundTrip_Byte() { byte value = 123; var vo = SsdtByteVo.From(value); @@ -32,7 +32,7 @@ public void RoundTrip_Byte_WithSsdtProvider() } [Fact] - public void RoundTrip_Char_WithSsdtProvider() + public void RoundTrip_Char() { char value = 'a'; var vo = SsdtCharVo.From(value); @@ -44,7 +44,7 @@ public void RoundTrip_Char_WithSsdtProvider() } [Fact] - public void RoundTrip_WithSsdtProvider() + public void RoundTrip() { var value = "ABC"; var vo = SsdtStringVo.From(value); @@ -56,7 +56,7 @@ public void RoundTrip_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTimeOffset_WithSsdtProvider() + public void RoundTrip_DateTimeOffset() { var vo = SsdtDateTimeOffsetVo.From(Primitives.DateTimeOffset1); var json = JsonSerializer.SerializeToString(vo); @@ -67,7 +67,7 @@ public void RoundTrip_DateTimeOffset_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTime_WithSsdtProvider() + public void RoundTrip_DateTime() { var vo = SsdtDateTimeVo.From(Primitives.DateTime1); var json = JsonSerializer.SerializeToString(vo); @@ -78,7 +78,7 @@ public void RoundTrip_DateTime_WithSsdtProvider() } [Fact] - public void RoundTrip_Decimal_WithSsdtProvider() + public void RoundTrip_Decimal() { var vo = SsdtDecimalVo.From(123.45m); @@ -90,7 +90,7 @@ public void RoundTrip_Decimal_WithSsdtProvider() } [Fact] - public void RoundTrip_Double_WithSsdtProvider() + public void RoundTrip_Double() { var vo = SsdtDoubleVo.From(123.45d); @@ -102,7 +102,7 @@ public void RoundTrip_Double_WithSsdtProvider() } [Fact] - public void RoundTrip_Float_WithSsdtProvider() + public void RoundTrip_Float() { var vo = SsdtFloatVo.From(123.45f); @@ -113,7 +113,7 @@ public void RoundTrip_Float_WithSsdtProvider() } [Fact] - public void RoundTrip_Guid_WithSsdtProvider() + public void RoundTrip_Guid() { var vo = SsdtGuidVo.From(Primitives.Guid1); @@ -124,7 +124,7 @@ public void RoundTrip_Guid_WithSsdtProvider() } [Fact] - public void RoundTrip_Int_WithSsdtProvider() + public void RoundTrip_Int() { var vo = SsdtLongVo.From(123L); @@ -146,7 +146,7 @@ public void RoundTrip_ShortSsdtProvider() } [Fact] - public void RoundTrip_String_WithSsdtProvider() + public void RoundTrip_String() { var vo = SsdtStringVo.From("aaa"); @@ -157,7 +157,7 @@ public void RoundTrip_String_WithSsdtProvider() } [Fact] - public void RoundTrip_TimeOnly_WithSsdtProvider() + public void RoundTrip_TimeOnly() { var vo = SsdtTimeOnlyVo.From(Primitives.Time1); diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/Types/BsonTypes.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/Types/BsonTypes.cs new file mode 100644 index 0000000000..f69dfe5049 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordClassVos/Types/BsonTypes.cs @@ -0,0 +1,82 @@ +namespace Vogen.IntegrationTests.TestTypes.RecordClassVos; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(bool))] +public partial record class BsonBoolVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(byte))] +public partial record class BsonByteVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(char))] +public partial record class BsonCharVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateOnly))] +public partial record class BsonDateOnlyVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTimeOffset))] +public partial record class BsonDateTimeOffsetVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTime))] +public partial record class BsonDateTimeVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(decimal))] +public partial record class BsonDecimalVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(double))] +public partial record class BsonDoubleVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(float))] +public partial record class BsonFloatVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Bar))] +public partial record class BsonFooVo +{ + public static SsdtFooVo Parse(string s) => throw new Exception("todo!"); +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Guid))] +public partial record class BsonGuidVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(int))] +public partial record class BsonIntVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(long))] +public partial record class BsonLongVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(short))] +public partial record class BsonShortVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(string))] +public partial record class BsonStringVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(TimeOnly))] +public partial record class BsonTimeOnlyVo +{ +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/BsonSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/BsonSerializationTests.cs new file mode 100644 index 0000000000..c859f60928 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/BsonSerializationTests.cs @@ -0,0 +1,171 @@ +using FluentAssertions.Extensions; +using Vogen.IntegrationTests.TestTypes; +using Vogen.IntegrationTests.TestTypes.RecordStructVos; +// ReSharper disable NullableWarningSuppressionIsUsed + +namespace ConsumerTests.SerializationAndConversionTests.RecordStructVos; + +public class BsonSerializationTests +{ + [Fact] + public void RoundTrip_Bool() + { + var vo = BsonBoolVo.From(true); + + string json = BsonSerializerButUsesJson.Serialize(vo); + + BsonBoolVo deserialised = BsonSerializerButUsesJson.Deserialize(json); + + vo.Value.Should().Be(deserialised.Value); + } + + [Fact] + public void RoundTrip_Byte() + { + byte value = 123; + var vo = BsonByteVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Char() + { + char value = 'a'; + var vo = BsonCharVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip() + { + var value = "ABC"; + var vo = BsonStringVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTimeOffset() + { + var vo = BsonDateTimeOffsetVo.From(Primitives.DateTimeOffset1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTime() + { + var vo = BsonDateTimeVo.From(Primitives.DateTime1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + // micro and non-second not populated in the Mongo C# driver + vo.Value.Should().BeCloseTo(deserializedVo.Value, 1.Milliseconds()); + } + + [Fact] + public void RoundTrip_Decimal() + { + var vo = BsonDecimalVo.From(123.45m); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Double() + { + var vo = BsonDoubleVo.From(123.45d); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Float() + { + var vo = BsonFloatVo.From(123.45f); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123.45f); + } + + [Fact] + public void RoundTrip_Guid() + { + var vo = BsonGuidVo.From(Primitives.Guid1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Guid1); + } + + [Fact] + public void RoundTrip_Int() + { + var vo = BsonLongVo.From(123L); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_ShortBsonProvider() + { + var vo = BsonShortVo.From(123); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_String() + { + var vo = BsonStringVo.From("aaa"); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be("aaa"); + } + + [SkippableFact] + public void RoundTrip_TimeOnly() + { + Skip.If(true, "Unsupported in the C# driver - https://jira.mongodb.org/browse/CSHARP-3717"); + var vo = BsonTimeOnlyVo.From(Primitives.Time1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Time1); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/ServiceStackDotTextSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/ServiceStackDotTextSerializationTests.cs index 96da24ea28..487a256876 100644 --- a/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/ServiceStackDotTextSerializationTests.cs +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/ServiceStackDotTextSerializationTests.cs @@ -9,7 +9,7 @@ namespace ConsumerTests.SerializationAndConversionTests.RecordStructVos; public class ServiceStackDotTextSerializationTests { [Fact] - public void RoundTrip_Bool_WithSsdtProvider() + public void RoundTrip_Bool() { var vo = SsdtBoolVo.From(true); @@ -21,7 +21,7 @@ public void RoundTrip_Bool_WithSsdtProvider() } [Fact] - public void RoundTrip_Byte_WithSsdtProvider() + public void RoundTrip_Byte() { byte value = 123; var vo = SsdtByteVo.From(value); @@ -33,7 +33,7 @@ public void RoundTrip_Byte_WithSsdtProvider() } [Fact] - public void RoundTrip_Char_WithSsdtProvider() + public void RoundTrip_Char() { char value = 'a'; var vo = SsdtCharVo.From(value); @@ -45,7 +45,7 @@ public void RoundTrip_Char_WithSsdtProvider() } [Fact] - public void RoundTrip_WithSsdtProvider() + public void RoundTrip() { var value = "ABC"; var vo = SsdtStringVo.From(value); @@ -57,7 +57,7 @@ public void RoundTrip_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTimeOffset_WithSsdtProvider() + public void RoundTrip_DateTimeOffset() { var vo = SsdtDateTimeOffsetVo.From(Primitives.DateTimeOffset1); var json = JsonSerializer.SerializeToString(vo); @@ -68,7 +68,7 @@ public void RoundTrip_DateTimeOffset_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTime_WithSsdtProvider() + public void RoundTrip_DateTime() { var vo = SsdtDateTimeVo.From(Primitives.DateTime1); var json = JsonSerializer.SerializeToString(vo); @@ -79,7 +79,7 @@ public void RoundTrip_DateTime_WithSsdtProvider() } [Fact] - public void RoundTrip_Decimal_WithSsdtProvider() + public void RoundTrip_Decimal() { var vo = SsdtDecimalVo.From(123.45m); @@ -91,7 +91,7 @@ public void RoundTrip_Decimal_WithSsdtProvider() } [Fact] - public void RoundTrip_Double_WithSsdtProvider() + public void RoundTrip_Double() { var vo = SsdtDoubleVo.From(123.45d); @@ -103,7 +103,7 @@ public void RoundTrip_Double_WithSsdtProvider() } [Fact] - public void RoundTrip_Float_WithSsdtProvider() + public void RoundTrip_Float() { var vo = SsdtFloatVo.From(123.45f); @@ -114,7 +114,7 @@ public void RoundTrip_Float_WithSsdtProvider() } [Fact] - public void RoundTrip_Guid_WithSsdtProvider() + public void RoundTrip_Guid() { var vo = SsdtGuidVo.From(Primitives.Guid1); @@ -125,7 +125,7 @@ public void RoundTrip_Guid_WithSsdtProvider() } [Fact] - public void RoundTrip_Int_WithSsdtProvider() + public void RoundTrip_Int() { var vo = SsdtLongVo.From(123L); @@ -147,7 +147,7 @@ public void RoundTrip_ShortSsdtProvider() } [Fact] - public void RoundTrip_String_WithSsdtProvider() + public void RoundTrip_String() { var vo = SsdtStringVo.From("aaa"); @@ -158,7 +158,7 @@ public void RoundTrip_String_WithSsdtProvider() } [Fact] - public void RoundTrip_TimeOnly_WithSsdtProvider() + public void RoundTrip_TimeOnly() { var vo = SsdtTimeOnlyVo.From(Primitives.Time1); diff --git a/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/Types/BsonTypes.cs b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/Types/BsonTypes.cs new file mode 100644 index 0000000000..27326db186 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/RecordStructVos/Types/BsonTypes.cs @@ -0,0 +1,82 @@ +namespace Vogen.IntegrationTests.TestTypes.RecordStructVos; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(bool))] +public partial record struct BsonBoolVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(byte))] +public partial record struct BsonByteVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(char))] +public partial record struct BsonCharVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateOnly))] +public partial record struct BsonDateOnlyVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTimeOffset))] +public partial record struct BsonDateTimeOffsetVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTime))] +public partial record struct BsonDateTimeVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(float))] +public partial record struct BsonFloatVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(decimal))] +public partial record struct BsonDecimalVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(double))] +public partial record struct BsonDoubleVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Bar))] +public partial record struct BsonFooVo +{ + public static SsdtFooVo Parse(string s) => throw new Exception("todo!"); +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Guid))] +public partial record struct BsonGuidVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(int))] +public partial record struct BsonIntVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(long))] +public partial record struct BsonLongVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(short))] +public partial record struct BsonShortVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(string))] +public partial record struct BsonStringVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(TimeOnly))] +public partial record struct BsonTimeOnlyVo +{ +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/StructVos/BsonSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/BsonSerializationTests.cs new file mode 100644 index 0000000000..9fb00668a0 --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/BsonSerializationTests.cs @@ -0,0 +1,171 @@ +using FluentAssertions.Extensions; +using Vogen.IntegrationTests.TestTypes; +using Vogen.IntegrationTests.TestTypes.RecordStructVos; +// ReSharper disable NullableWarningSuppressionIsUsed + +namespace ConsumerTests.SerializationAndConversionTests.StructVos; + +public class BsonSerializationTests +{ + [Fact] + public void RoundTrip_Bool() + { + var vo = BsonBoolVo.From(true); + + string json = BsonSerializerButUsesJson.Serialize(vo); + + BsonBoolVo deserialised = BsonSerializerButUsesJson.Deserialize(json); + + vo.Value.Should().Be(deserialised.Value); + } + + [Fact] + public void RoundTrip_Byte() + { + byte value = 123; + var vo = BsonByteVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Char() + { + char value = 'a'; + var vo = BsonCharVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip() + { + var value = "ABC"; + var vo = BsonStringVo.From(value); + var json = BsonSerializerButUsesJson.Serialize(value); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTimeOffset() + { + var vo = BsonDateTimeOffsetVo.From(Primitives.DateTimeOffset1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_DateTime() + { + var vo = BsonDateTimeVo.From(Primitives.DateTime1); + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + // micro and non-second not populated in the Mongo C# driver + vo.Value.Should().BeCloseTo(deserializedVo.Value, 1.Milliseconds()); + } + + [Fact] + public void RoundTrip_Decimal() + { + var vo = BsonDecimalVo.From(123.45m); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Double() + { + var vo = BsonDoubleVo.From(123.45d); + + var json = BsonSerializerButUsesJson.Serialize(vo); + + var deserializedVo = BsonSerializerButUsesJson.Deserialize(json); + + Assert.Equal(vo, deserializedVo); + } + + [Fact] + public void RoundTrip_Float() + { + var vo = BsonFloatVo.From(123.45f); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123.45f); + } + + [Fact] + public void RoundTrip_Guid() + { + var vo = BsonGuidVo.From(Primitives.Guid1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Guid1); + } + + [Fact] + public void RoundTrip_Int() + { + var vo = BsonLongVo.From(123L); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_ShortBsonProvider() + { + var vo = BsonShortVo.From(123); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(123); + } + + [Fact] + public void RoundTrip_String() + { + var vo = BsonStringVo.From("aaa"); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be("aaa"); + } + + [SkippableFact] + public void RoundTrip_TimeOnly() + { + Skip.If(true, "Unsupported in the C# driver - https://jira.mongodb.org/browse/CSHARP-3717"); + var vo = BsonTimeOnlyVo.From(Primitives.Time1); + + string serializedVo = BsonSerializerButUsesJson.Serialize(vo); + var deserializedVo = BsonSerializerButUsesJson.Deserialize(serializedVo); + + deserializedVo.Value.Should().Be(Primitives.Time1); + } +} \ No newline at end of file diff --git a/tests/ConsumerTests/SerializationAndConversionTests/StructVos/ServiceStackDotTextSerializationTests.cs b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/ServiceStackDotTextSerializationTests.cs index bee358d89f..2460a10fb3 100644 --- a/tests/ConsumerTests/SerializationAndConversionTests/StructVos/ServiceStackDotTextSerializationTests.cs +++ b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/ServiceStackDotTextSerializationTests.cs @@ -9,7 +9,7 @@ namespace ConsumerTests.SerializationAndConversionTests.StructVos; public class ServiceStackDotTextSerializationTests { [Fact] - public void RoundTrip_Bool_WithSsdtProvider() + public void RoundTrip_Bool() { var vo = SsdtBoolVo.From(true); @@ -21,7 +21,7 @@ public void RoundTrip_Bool_WithSsdtProvider() } [Fact] - public void RoundTrip_Byte_WithSsdtProvider() + public void RoundTrip_Byte() { byte value = 123; var vo = SsdtByteVo.From(value); @@ -33,7 +33,7 @@ public void RoundTrip_Byte_WithSsdtProvider() } [Fact] - public void RoundTrip_Char_WithSsdtProvider() + public void RoundTrip_Char() { char value = 'a'; var vo = SsdtCharVo.From(value); @@ -45,7 +45,7 @@ public void RoundTrip_Char_WithSsdtProvider() } [Fact] - public void RoundTrip_WithSsdtProvider() + public void RoundTrip() { var value = "ABC"; var vo = SsdtStringVo.From(value); @@ -57,7 +57,7 @@ public void RoundTrip_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTimeOffset_WithSsdtProvider() + public void RoundTrip_DateTimeOffset() { var vo = SsdtDateTimeOffsetVo.From(Primitives.DateTimeOffset1); var json = JsonSerializer.SerializeToString(vo); @@ -68,7 +68,7 @@ public void RoundTrip_DateTimeOffset_WithSsdtProvider() } [Fact] - public void RoundTrip_DateTime_WithSsdtProvider() + public void RoundTrip_DateTime() { var vo = SsdtDateTimeVo.From(Primitives.DateTime1); var json = JsonSerializer.SerializeToString(vo); @@ -79,7 +79,7 @@ public void RoundTrip_DateTime_WithSsdtProvider() } [Fact] - public void RoundTrip_Decimal_WithSsdtProvider() + public void RoundTrip_Decimal() { var vo = SsdtDecimalVo.From(123.45m); @@ -91,7 +91,7 @@ public void RoundTrip_Decimal_WithSsdtProvider() } [Fact] - public void RoundTrip_Double_WithSsdtProvider() + public void RoundTrip_Double() { var vo = SsdtDoubleVo.From(123.45d); @@ -103,7 +103,7 @@ public void RoundTrip_Double_WithSsdtProvider() } [Fact] - public void RoundTrip_Float_WithSsdtProvider() + public void RoundTrip_Float() { var vo = SsdtFloatVo.From(123.45f); @@ -114,7 +114,7 @@ public void RoundTrip_Float_WithSsdtProvider() } [Fact] - public void RoundTrip_Guid_WithSsdtProvider() + public void RoundTrip_Guid() { var vo = SsdtGuidVo.From(Primitives.Guid1); @@ -125,7 +125,7 @@ public void RoundTrip_Guid_WithSsdtProvider() } [Fact] - public void RoundTrip_Int_WithSsdtProvider() + public void RoundTrip_Int() { var vo = SsdtLongVo.From(123L); @@ -147,7 +147,7 @@ public void RoundTrip_ShortSsdtProvider() } [Fact] - public void RoundTrip_String_WithSsdtProvider() + public void RoundTrip_String() { var vo = SsdtStringVo.From("aaa"); @@ -158,7 +158,7 @@ public void RoundTrip_String_WithSsdtProvider() } [Fact] - public void RoundTrip_TimeOnly_WithSsdtProvider() + public void RoundTrip_TimeOnly() { var vo = SsdtTimeOnlyVo.From(Primitives.Time1); diff --git a/tests/ConsumerTests/SerializationAndConversionTests/StructVos/Types/BsonTypes.cs b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/Types/BsonTypes.cs new file mode 100644 index 0000000000..e55022804c --- /dev/null +++ b/tests/ConsumerTests/SerializationAndConversionTests/StructVos/Types/BsonTypes.cs @@ -0,0 +1,82 @@ +namespace Vogen.IntegrationTests.TestTypes.StructVos; + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(bool))] +public partial struct BsonBoolVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(byte))] +public partial struct BsonByteVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(char))] +public partial struct BsonCharVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateOnly))] +public partial struct BsonDateOnlyVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTimeOffset))] +public partial struct BsonDateTimeOffsetVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(DateTime))] +public partial struct BsonDateTimeVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(decimal))] +public partial struct BsonDecimalVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(float))] +public partial struct BsonFloatVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(double))] +public partial struct BsonDoubleVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Bar))] +public partial struct BsonFooVo +{ + public static BsonFooVo Parse(string s) => throw new Exception("todo!"); +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(Guid))] +public partial struct BsonGuidVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(int))] +public partial struct BsonIntVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(long))] +public partial struct BsonLongVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(short))] +public partial struct BsonShortVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(string))] +public partial struct BsonStringVo +{ +} + +[ValueObject(conversions: Conversions.Bson, underlyingType: typeof(TimeOnly))] +public partial struct BsonTimeOnlyVo +{ +} \ No newline at end of file diff --git a/tests/Shared/ProjectBuilder.cs b/tests/Shared/ProjectBuilder.cs index 9fe22bbbb1..3b32651d7a 100644 --- a/tests/Shared/ProjectBuilder.cs +++ b/tests/Shared/ProjectBuilder.cs @@ -25,7 +25,7 @@ public sealed partial class ProjectBuilder { public IList DiagnosticAnalyzers { get; } = new List(); public IList ExpectedDiagnosticResults { get; } = new List(); - + public string? DefaultAnalyzerId { get; set; } public string? DefaultAnalyzerMessage { get; set; } @@ -68,7 +68,6 @@ public ProjectBuilder ShouldReportDiagnostic(params DiagnosticResult[] expectedD } - public ProjectBuilder ShouldExcludeSystemTextJson(bool excludeStj = false) { _excludeStj = excludeStj; @@ -80,7 +79,7 @@ public ProjectBuilder WithLanguageVersion(LanguageVersion languageVersion) _languageVersion = languageVersion; return this; } - + public ProjectBuilder WithAnalyzerFromNuGet(string packageName, string version, string path, string[] ruleIds) { var ruleFound = false; @@ -93,7 +92,7 @@ public ProjectBuilder WithAnalyzerFromNuGet(string packageName, string version, if (type.IsAbstract || !typeof(DiagnosticAnalyzer).IsAssignableFrom(type)) continue; - var instance = (DiagnosticAnalyzer)Activator.CreateInstance(type); + var instance = (DiagnosticAnalyzer) Activator.CreateInstance(type); if (instance.SupportedDiagnostics.Any(d => ruleIds.Contains(d.Id, StringComparer.Ordinal))) { DiagnosticAnalyzers.Add(instance); @@ -119,7 +118,7 @@ public void AddNuGetReference(string packageName, string version, string pathPre private void AddNuGetReferences() { - if(_targetFramework is null) + if (_targetFramework is null) { throw new InvalidOperationException("No target framework!"); } @@ -169,7 +168,6 @@ private void AddNuGetReferences() AddNuGetReference("Microsoft.EntityFrameworkCore", "6.0.0", "lib/net6.0/"); AddNuGetReference("Dapper", "2.0.123", "lib/net5.0/"); AddNuGetReference("ServiceStack.Text", "6.11.0", "lib/net6.0"); - break; case TargetFramework.Net7_0: @@ -178,8 +176,6 @@ private void AddNuGetReferences() AddNuGetReference("Microsoft.EntityFrameworkCore", "7.0.0", "lib/net6.0/"); AddNuGetReference("Dapper", "2.0.123", "lib/net5.0/"); AddNuGetReference("ServiceStack.Text", "6.11.0", "lib/net6.0"); - - break; case TargetFramework.Net8_0: @@ -188,7 +184,7 @@ private void AddNuGetReferences() AddNuGetReference("Microsoft.EntityFrameworkCore", "8.0.0", "lib/net8.0/"); AddNuGetReference("Dapper", "2.1.28", "lib/net7.0/"); AddNuGetReference("ServiceStack.Text", "8.2.2", "lib/net8.0"); - + AddNuGetReference("MongoDB.Bson", "2.27.0", "lib/netstandard2.0"); break; case TargetFramework.AspNetCore8_0: @@ -196,20 +192,20 @@ private void AddNuGetReferences() AddNuGetReference("Microsoft.AspNetCore.App.Ref", "8.0.0", "ref/net8.0/"); AddNuGetReference("Swashbuckle.AspNetCore.SwaggerGen", "6.4.0", "lib/net6.0/"); AddNuGetReference("Microsoft.OpenApi", "1.4.3.0", "lib/netstandard2.0/"); + AddNuGetReference("MongoDB.Bson", "2.27.0", "lib/netstandard2.0"); break; - } AddNuGetReference("System.Collections.Immutable", "1.5.0", "lib/netstandard2.0/"); - + if (_targetFramework is not TargetFramework.Net7_0 and not TargetFramework.Net8_0) { AddNuGetReference("System.Numerics.Vectors", "4.5.0", "ref/netstandard2.0/"); } - AddNuGetReference("Microsoft.CSharp", "4.7.0", "lib/netstandard2.0/"); // To support dynamic type - AddNuGetReference("Newtonsoft.Json", "13.0.2", "lib/netstandard2.0/"); + AddNuGetReference("Microsoft.CSharp", "4.7.0", "lib/netstandard2.0/"); // To support dynamic type + AddNuGetReference("Newtonsoft.Json", "13.0.2", "lib/netstandard2.0/"); } private void AddStjIfNeeded(string version, string pathPrefix) @@ -238,7 +234,8 @@ async Task Download() { Directory.CreateDirectory(tempFolder); using var httpClient = new HttpClient(); - using var stream = await httpClient.GetStreamAsync(new Uri($"https://www.nuget.org/api/v2/package/{packageName}/{version}")).ConfigureAwait(false); + using var stream = await httpClient.GetStreamAsync(new Uri($"https://www.nuget.org/api/v2/package/{packageName}/{version}")) + .ConfigureAwait(false); using var zip = new ZipArchive(stream, ZipArchiveMode.Read); foreach (var entry in zip.Entries.Where(file => file.FullName.StartsWith(path, StringComparison.Ordinal))) @@ -252,7 +249,7 @@ async Task Download() // Filter invalid .NET assembly var result = new List(); foreach (string eachDllNameAndPath in nameAndPathsForDlls) - { + { if (Path.GetFileName(eachDllNameAndPath) == "System.EnterpriseServices.Wrapper.dll") continue; @@ -269,7 +266,7 @@ async Task Download() } } - if(result.Count == 0) + if (result.Count == 0) { throw new InvalidOperationException($"Did not add any DLLs as references for {packageName}, v {version}, at {path}!"); } @@ -277,7 +274,7 @@ async Task Download() return result.ToArray(); } } - + public ProjectBuilder WithAnalyzer(string? id = null, string? message = null) where T : DiagnosticAnalyzer, new() => WithAnalyzer(new T(), id, message); @@ -302,7 +299,7 @@ public ProjectBuilder WithNugetPackages(IEnumerable packages) { AddNuGetReference(nuGetPackage.PackageName, nuGetPackage.Version, nuGetPackage.PathPrefix); } - + return this; } @@ -314,15 +311,18 @@ public ProjectBuilder WithNugetPackages(IEnumerable packages) where T : IIncrementalGenerator, new() { var parseOptions = new CSharpParseOptions(languageVersion: _languageVersion); - + var usersSyntaxTree = CSharpSyntaxTree.ParseText(_userSource, parseOptions); - var isExternalInitSyntaxTree = CSharpSyntaxTree.ParseText(@" namespace System.Runtime.CompilerServices + var isExternalInitSyntaxTree = CSharpSyntaxTree.ParseText( + @" namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } -", parseOptions); +", + parseOptions); - MetadataReference r = valueObjectAttributeMetadata ?? MetadataReference.CreateFromFile(typeof(ValueObjectAttribute).Assembly.Location); + MetadataReference r = valueObjectAttributeMetadata ?? + MetadataReference.CreateFromFile(typeof(ValueObjectAttribute).Assembly.Location); _references.Add(r); @@ -330,16 +330,17 @@ internal static class IsExternalInit {} var options = new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, - moduleName: "VogenTests", + moduleName: "VogenTests", specificDiagnosticOptions: _suppressedDiagnostics); var diagnostics = this.DiagnosticAnalyzers.SelectMany( - analyzer => analyzer.SupportedDiagnostics.Select(diag => new KeyValuePair(diag.Id, GetReportDiagnostic(diag)))); + analyzer => analyzer.SupportedDiagnostics.Select( + diag => new KeyValuePair(diag.Id, GetReportDiagnostic(diag)))); diagnostics = diagnostics.Concat(_suppressedDiagnostics); - + options = options.WithSpecificDiagnosticOptions(diagnostics); - + var compilation = CSharpCompilation.Create( assemblyName: "generator", syntaxTrees: new[] { usersSyntaxTree, isExternalInitSyntaxTree }, @@ -375,7 +376,7 @@ internal static class IsExternalInit {} var driver = CSharpGeneratorDriver .Create(generator) .WithUpdatedParseOptions(parseOptions.WithDocumentationMode(DocumentationMode.Diagnose)); - + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiags); var finalDiags = outputCompilation.GetDiagnostics(); @@ -405,7 +406,7 @@ private static void DumpSource(Compilation outputCompilation) { var s = st.GetText().ToString(); var fp = st.FilePath; - if (fp.Length == 0) fp = $"file{++i}.cs"; + if (fp.Length == 0) fp = $"file{++i}.cs"; string p = Path.Combine(path, fp); Directory.CreateDirectory(Path.GetDirectoryName(p)!); File.WriteAllText(p, s.ToString()); @@ -423,10 +424,8 @@ private static ReportDiagnostic GetReportDiagnostic(DiagnosticDescriptor descrip _ => ReportDiagnostic.Info, // Ensure the analyzer is enabled for the test }; } - } - internal sealed class TestAnalyzerConfigOptionsProvider(Dictionary values) : AnalyzerConfigOptionsProvider { private readonly Dictionary _values = values ?? []; @@ -444,4 +443,4 @@ public override bool TryGetValue(string key, out string value) return _values.TryGetValue(key, out value); } } -} +} \ No newline at end of file diff --git a/tests/SnapshotTests/BsonSerializationGeneration/BsonSerializationGenerationTests.cs b/tests/SnapshotTests/BsonSerializationGeneration/BsonSerializationGenerationTests.cs new file mode 100644 index 0000000000..61baecbbd3 --- /dev/null +++ b/tests/SnapshotTests/BsonSerializationGeneration/BsonSerializationGenerationTests.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Shared; +using VerifyXunit; +using Vogen; + +namespace SnapshotTests.BsonSerializationGeneration; + +// contrib: An idea place to start a new feature. Write a new test for the feature here to get it working, then +// add more tests. Move these tests if there are several of them, and it makes sense to group them. + +public class BsonSerializationGenerationTests +{ + [Fact] + public async Task Writes_bson_serializers() + { + var source = """ + using System; + using Vogen; + + [assembly: VogenDefaults(conversions: Conversions.Bson)] + + namespace Whatever; + [ValueObject] + public partial struct Age; + + [ValueObject] + public partial struct Name; + """; + + await new SnapshotRunner() + .WithSource(source) + .IgnoreInitialCompilationErrors() + .RunOn(TargetFramework.Net8_0); + } +} \ No newline at end of file diff --git a/tests/SnapshotTests/BsonSerializationGeneration/snapshots/snap-v8.0/BsonSerializationGenerationTests.Writes_bson_serializers.verified.txt b/tests/SnapshotTests/BsonSerializationGeneration/snapshots/snap-v8.0/BsonSerializationGenerationTests.Writes_bson_serializers.verified.txt new file mode 100644 index 0000000000..8f86b7637c --- /dev/null +++ b/tests/SnapshotTests/BsonSerializationGeneration/snapshots/snap-v8.0/BsonSerializationGenerationTests.Writes_bson_serializers.verified.txt @@ -0,0 +1,1021 @@ +[ +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +namespace Whatever; + +public partial class AgeBsonSerializer : global::MongoDB.Bson.Serialization.Serializers.SerializerBase +{ + private readonly global::MongoDB.Bson.Serialization.IBsonSerializer _serializer = global::MongoDB.Bson.Serialization.BsonSerializer.LookupSerializer(); + + public override Age Deserialize(global::MongoDB.Bson.Serialization.BsonDeserializationContext context, global::MongoDB.Bson.Serialization.BsonDeserializationArgs args) + { + var newArgs = new global::MongoDB.Bson.Serialization.BsonDeserializationArgs { NominalType = typeof(System.Int32) }; + + return Deserialize(_serializer.Deserialize(context, newArgs)); + } + + public override void Serialize(global::MongoDB.Bson.Serialization.BsonSerializationContext context, global::MongoDB.Bson.Serialization.BsonSerializationArgs args, Age value) => + _serializer.Serialize(context, args, value.Value); + + static Age Deserialize(System.Int32 value) => UnsafeDeserialize(default, value); + + [global::System.Runtime.CompilerServices.UnsafeAccessor(global::System.Runtime.CompilerServices.UnsafeAccessorKind.StaticMethod, Name = "__Deserialize")] + static extern Age UnsafeDeserialize(Age @this, System.Int32 value); + +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +namespace Whatever; + +public partial class NameBsonSerializer : global::MongoDB.Bson.Serialization.Serializers.SerializerBase +{ + private readonly global::MongoDB.Bson.Serialization.IBsonSerializer _serializer = global::MongoDB.Bson.Serialization.BsonSerializer.LookupSerializer(); + + public override Name Deserialize(global::MongoDB.Bson.Serialization.BsonDeserializationContext context, global::MongoDB.Bson.Serialization.BsonDeserializationArgs args) + { + var newArgs = new global::MongoDB.Bson.Serialization.BsonDeserializationArgs { NominalType = typeof(System.String) }; + + return Deserialize(_serializer.Deserialize(context, newArgs)); + } + + public override void Serialize(global::MongoDB.Bson.Serialization.BsonSerializationContext context, global::MongoDB.Bson.Serialization.BsonSerializationArgs args, Name value) => + _serializer.Serialize(context, args, value.Value); + + static Name Deserialize(System.String value) => UnsafeDeserialize(default, value); + + [global::System.Runtime.CompilerServices.UnsafeAccessor(global::System.Runtime.CompilerServices.UnsafeAccessorKind.StaticMethod, Name = "__Deserialize")] + static extern Name UnsafeDeserialize(Name @this, System.String value); + +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +public static class BsonSerializationRegisterForgenerator +{ + static BsonSerializationRegisterForgenerator() + { + global::MongoDB.Bson.Serialization.BsonSerializer.TryRegisterSerializer(new Whatever.AgeBsonSerializer()); +global::MongoDB.Bson.Serialization.BsonSerializer.TryRegisterSerializer(new Whatever.NameBsonSerializer()); + + } + + public static void TryRegister() { } +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +namespace generator; + +public class VogenTypesFactory : global::System.Text.Json.Serialization.JsonConverterFactory +{ + public VogenTypesFactory() { } + private static readonly global::System.Collections.Generic.Dictionary> _lookup = + new global::System.Collections.Generic.Dictionary> { + + }; + + public override bool CanConvert(global::System.Type typeToConvert) => _lookup.ContainsKey(typeToConvert); + + public override global::System.Text.Json.Serialization.JsonConverter CreateConverter(global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) => + _lookup[typeToConvert].Value; +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +using Vogen; + +namespace Whatever +{ + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(AgeDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.Int32, Value = { _value }")] + public partial struct Age : global::System.IEquatable, global::System.IEquatable , global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.ISpanParsable, global::System.IUtf8SpanParsable + { +#if DEBUG + private readonly global::System.Diagnostics.StackTrace _stackTrace = null; +#endif + +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + + private readonly System.Int32 _value; + + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.Int32 Value + { + [global::System.Diagnostics.DebuggerStepThroughAttribute] + get + { + EnsureInitialized(); + return _value; + } + } + + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Age() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif + +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private Age(System.Int32 value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + public static Age From(System.Int32 value) + { + + + + + Age instance = new Age(value); + + return instance; + } + + /// +/// Tries to build an instance from the provided underlying type. +/// If a normalization method is provided, it will be called. +/// If validation is provided, and it fails, false will be returned. +/// +/// The underlying type. +/// An instance of the value object. +/// True if the value object can be built, otherwise false. +public static bool TryFrom(System.Int32 value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age vo) +{ + + + + + + vo = new Age(value); + + return true; +}/// +/// Tries to build an instance from the provided underlying value. +/// If a normalization method is provided, it will be called. +/// If validation is provided, and it fails, an error will be returned. +/// +/// The primitive value. +/// A containing either the value object, or an error. +public static ValueObjectOrError TryFrom(System.Int32 value) +{ + + + + + + + return new ValueObjectOrError(new Age(value)); +} + +[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION + public readonly bool IsInitialized() => true; +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + + + + public static explicit operator Age(System.Int32 value) => From(value); + public static explicit operator System.Int32(Age value) => value.Value; + + // only called internally when something has been deserialized into + // its primitive type. + private static Age __Deserialize(System.Int32 value) + { + + + + + return new Age(value); + } + public readonly global::System.Boolean Equals(Age other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if(!IsInitialized() || !other.IsInitialized()) return false; + + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + public global::System.Boolean Equals(Age other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + + public readonly global::System.Boolean Equals(System.Int32 primitive) + { + return Value.Equals(primitive); + } + + public readonly override global::System.Boolean Equals(global::System.Object obj) + { + return obj is Age && Equals((Age) obj); + } + + public static global::System.Boolean operator ==(Age left, Age right) => Equals(left, right); + public static global::System.Boolean operator !=(Age left, Age right) => !(left == right); + + public static global::System.Boolean operator ==(Age left, System.Int32 right) => Equals(left.Value, right); + public static global::System.Boolean operator !=(Age left, System.Int32 right) => !Equals(left.Value, right); + + public static global::System.Boolean operator ==(System.Int32 left, Age right) => Equals(left, right.Value); + public static global::System.Boolean operator !=(System.Int32 left, Age right) => !Equals(left, right.Value); + + public int CompareTo(Age other) => Value.CompareTo(other.Value); + public int CompareTo(object other) { + if(other is null) return 1; + if(other is Age x) return CompareTo(x); + throw new global::System.ArgumentException("Cannot compare to object as it is not of type Age", nameof(other)); + } + + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(utf8Text, style, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(utf8Text, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(utf8Text, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, style, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, style, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, provider, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Age result) { + if(System.Int32.TryParse(s, out var __v)) { + + + result = new Age(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(global::System.ReadOnlySpan utf8Text, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(utf8Text, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(utf8Text, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(global::System.ReadOnlySpan s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(s, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(s, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(string s) { + var r = System.Int32.Parse(s); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(string s, global::System.Globalization.NumberStyles style) { + var r = System.Int32.Parse(s, style); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(string s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(s, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Age Parse(string s, global::System.IFormatProvider provider) { + var r = System.Int32.Parse(s, provider); + return From(r); + } + + + + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + /// Returns the string representation of the underlying . + public readonly override global::System.String ToString() =>IsInitialized() ? Value.ToString() : "[UNINITIALIZED]"; + + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + global::System.String message = "Use of uninitialized Value Object at: " + _stackTrace ?? ""; +#else + global::System.String message = "Use of uninitialized Value Object."; +#endif + + throw new global::Vogen.ValueObjectValidationException(message); + } + } + + + + + + + + + + + + + internal sealed class AgeDebugView + { + private readonly Age _t; + + AgeDebugView(Age t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.Int32"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]" ; + + #if DEBUG + public global::System.String CreatedWith => _t._stackTrace?.ToString() ?? "the From method"; + #endif + + public global::System.String Conversions => @"Bson"; + } + +} + +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +using Vogen; + +namespace Whatever +{ + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(NameDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.String, Value = { _value }")] + public partial struct Name : global::System.IEquatable, global::System.IEquatable , global::System.IComparable, global::System.IComparable, global::System.IParsable + { +#if DEBUG + private readonly global::System.Diagnostics.StackTrace _stackTrace = null; +#endif + +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + + private readonly System.String _value; + + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.String Value + { + [global::System.Diagnostics.DebuggerStepThroughAttribute] + get + { + EnsureInitialized(); + return _value; + } + } + + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public Name() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif + +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private Name(System.String value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + public static Name From(System.String value) + { + + + + + Name instance = new Name(value); + + return instance; + } + + /// +/// Tries to build an instance from the provided underlying type. +/// If a normalization method is provided, it will be called. +/// If validation is provided, and it fails, false will be returned. +/// +/// The underlying type. +/// An instance of the value object. +/// True if the value object can be built, otherwise false. +public static bool TryFrom(System.String value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Name vo) +{ + + + + + + vo = new Name(value); + + return true; +}/// +/// Tries to build an instance from the provided underlying value. +/// If a normalization method is provided, it will be called. +/// If validation is provided, and it fails, an error will be returned. +/// +/// The primitive value. +/// A containing either the value object, or an error. +public static ValueObjectOrError TryFrom(System.String value) +{ + + + + + + + return new ValueObjectOrError(new Name(value)); +} + +[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION + public readonly bool IsInitialized() => true; +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + + + + public static explicit operator Name(System.String value) => From(value); + public static explicit operator System.String(Name value) => value.Value; + + // only called internally when something has been deserialized into + // its primitive type. + private static Name __Deserialize(System.String value) + { + + + + + return new Name(value); + } + public readonly global::System.Boolean Equals(Name other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if(!IsInitialized() || !other.IsInitialized()) return false; + + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + public global::System.Boolean Equals(Name other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + + public readonly global::System.Boolean Equals(System.String primitive) + { + return Value.Equals(primitive); + } + + public readonly global::System.Boolean Equals(System.String primitive, global::System.StringComparer comparer) + { + return comparer.Equals(Value, primitive); + } + public readonly override global::System.Boolean Equals(global::System.Object obj) + { + return obj is Name && Equals((Name) obj); + } + + public static global::System.Boolean operator ==(Name left, Name right) => Equals(left, right); + public static global::System.Boolean operator !=(Name left, Name right) => !(left == right); + + public static global::System.Boolean operator ==(Name left, System.String right) => Equals(left.Value, right); + public static global::System.Boolean operator !=(Name left, System.String right) => !Equals(left.Value, right); + + public static global::System.Boolean operator ==(System.String left, Name right) => Equals(left, right.Value); + public static global::System.Boolean operator !=(System.String left, Name right) => !Equals(left, right.Value); + + public int CompareTo(Name other) => Value.CompareTo(other.Value); + public int CompareTo(object other) { + if(other is null) return 1; + if(other is Name x) return CompareTo(x); + throw new global::System.ArgumentException("Cannot compare to object as it is not of type Name", nameof(other)); + } + + + /// + /// + /// + /// True if the value passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.String s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out Name result) { + + + result = new Name(s); + return true; + } + /// + /// + /// + /// The value created via the method. + /// + /// Thrown when the value can be parsed, but is not valid. + public static Name Parse(global::System.String s, global::System.IFormatProvider provider) { + return From(s); + } + + + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + /// Returns the string representation of the underlying . + public readonly override global::System.String ToString() =>IsInitialized() ? Value.ToString() : "[UNINITIALIZED]"; + + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + global::System.String message = "Use of uninitialized Value Object at: " + _stackTrace ?? ""; +#else + global::System.String message = "Use of uninitialized Value Object."; +#endif + + throw new global::Vogen.ValueObjectValidationException(message); + } + } + + + + + + + + + + + + + internal sealed class NameDebugView + { + private readonly Name _t; + + NameDebugView(Name t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.String"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]" ; + + #if DEBUG + public global::System.String CreatedWith => _t._stackTrace?.ToString() ?? "the From method"; + #endif + + public global::System.String Conversions => @"Bson"; + } + +} + +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/SnapshotTests.csproj b/tests/SnapshotTests/SnapshotTests.csproj index 69c501aa06..a1bce54b29 100644 --- a/tests/SnapshotTests/SnapshotTests.csproj +++ b/tests/SnapshotTests/SnapshotTests.csproj @@ -58,4 +58,19 @@ + + + EfCoreGenerationTests + EfCoreGenerationTests.cs + + + EfCoreGenerationTests + EfCoreGenerationTests.cs + + + EfCoreGenerationTests + EfCoreGenerationTests.cs + + +