diff --git a/Directory.Packages.props b/Directory.Packages.props index 1a86e041b..dea93aa97 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -57,6 +57,7 @@ + diff --git a/Directory.Packages.support.props b/Directory.Packages.support.props index 400df97b8..05bf4a470 100644 --- a/Directory.Packages.support.props +++ b/Directory.Packages.support.props @@ -2,10 +2,13 @@ + + + diff --git a/sample/Sample.Core/DataConvention.cs b/sample/Sample.Core/DataConvention.cs index 23a591fd4..fa80c1a93 100644 --- a/sample/Sample.Core/DataConvention.cs +++ b/sample/Sample.Core/DataConvention.cs @@ -10,6 +10,7 @@ using IConventionContext = Rocket.Surgery.Conventions.IConventionContext; [assembly: Convention(typeof(DataConvention))] + namespace Sample.Core { class DataConvention : IServiceConvention @@ -18,12 +19,18 @@ public void Register(IConventionContext context, IConfiguration configuration, I { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); - services.AddPooledDbContextFactory(x => x - .EnableDetailedErrors() - .EnableSensitiveDataLogging() - .EnableServiceProviderCaching() - .UseSqlite(connection) - ); + services +#if NETSTANDARD + .AddDbContextPool( +#else + .AddPooledDbContextFactory( +#endif + x => x + .EnableDetailedErrors() + .EnableSensitiveDataLogging() + .EnableServiceProviderCaching() + .UseSqlite(connection) + ); } } } \ No newline at end of file diff --git a/sample/Sample.Core/Domain/RocketDbContext.cs b/sample/Sample.Core/Domain/RocketDbContext.cs index c33778e62..661f690f7 100644 --- a/sample/Sample.Core/Domain/RocketDbContext.cs +++ b/sample/Sample.Core/Domain/RocketDbContext.cs @@ -7,7 +7,7 @@ namespace Sample.Core.Domain { public class RocketDbContext : LpContext { - public RocketDbContext(DbContextOptions options) : base(options) { } + public RocketDbContext(DbContextOptions? options = null) : base(options ?? new DbContextOptions()) { } public DbSet Rockets { get; set; } = null!; public DbSet LaunchRecords { get; set; } = null!; } diff --git a/sample/Sample.Graphql/Sample.Graphql.csproj b/sample/Sample.Graphql/Sample.Graphql.csproj index 9a8a85bfb..16b43682d 100644 --- a/sample/Sample.Graphql/Sample.Graphql.csproj +++ b/sample/Sample.Graphql/Sample.Graphql.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0 + net5.0 diff --git a/src/AspNetCore.NewtonsoftJson/Conventions/NewtonsoftJsonConvention.cs b/src/AspNetCore.NewtonsoftJson/Conventions/NewtonsoftJsonConvention.cs index 9b5bdd782..67c0ef86f 100644 --- a/src/AspNetCore.NewtonsoftJson/Conventions/NewtonsoftJsonConvention.cs +++ b/src/AspNetCore.NewtonsoftJson/Conventions/NewtonsoftJsonConvention.cs @@ -11,6 +11,7 @@ using Rocket.Surgery.Conventions; using Rocket.Surgery.Conventions.DependencyInjection; using Rocket.Surgery.LaunchPad.AspNetCore.NewtonsoftJson.Conventions; +using Rocket.Surgery.LaunchPad.Foundation; using System; using System.Collections.Generic; using System.Linq; @@ -28,6 +29,12 @@ namespace Rocket.Surgery.LaunchPad.AspNetCore.NewtonsoftJson.Conventions [PublicAPI] public class NewtonsoftJsonConvention : IServiceConvention { + private readonly FoundationOptions _options; + + public NewtonsoftJsonConvention(FoundationOptions? options = null) + { + _options = options ?? new(); + } /// /// Registers the specified context. /// @@ -45,111 +52,9 @@ public void Register(IConventionContext context, IConfiguration configuration, I services.Configure( options => { - options.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); - options.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - InstantPattern.ExtendedIso, - InstantPattern.General, - new DateTimeOffsetPattern(), - new DateTimePattern() - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - LocalDatePattern.Iso, - LocalDatePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - LocalDateTimePattern.ExtendedIso, - LocalDateTimePattern.GeneralIso, - LocalDateTimePattern.BclRoundtrip, - LocalDateTimePattern.FullRoundtrip, - LocalDateTimePattern.FullRoundtripWithoutCalendar - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - LocalTimePattern.ExtendedIso, - LocalTimePattern.GeneralIso, - LocalTimePattern.LongExtendedIso - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - OffsetPattern.GeneralInvariant, - OffsetPattern.GeneralInvariantWithZ - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - DurationPattern.JsonRoundtrip, - DurationPattern.Roundtrip - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - DurationPattern.JsonRoundtrip, - DurationPattern.Roundtrip - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - PeriodPattern.Roundtrip, - PeriodPattern.NormalizingIso - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - OffsetDateTimePattern.GeneralIso, - OffsetDateTimePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - OffsetDatePattern.GeneralIso, - OffsetDatePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - OffsetTimePattern.Rfc3339, - OffsetTimePattern.GeneralIso, - OffsetTimePattern.ExtendedIso - ) - ); - ReplaceConverter( - options.SerializerSettings.Converters, - new CompositeNodaPatternConverter( - ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo z", DateTimeZoneProviders.Tzdb), - ZonedDateTimePattern.ExtendedFormatOnlyIso, - ZonedDateTimePattern.GeneralFormatOnlyIso - ) - ); + options.SerializerSettings.ConfigureForLaunchPad(_options.DateTimeZoneProvider); } ); } - - private static void ReplaceConverter(ICollection converters, CompositeNodaPatternConverter converter) - { - foreach (var c in converters.Where(z => z.CanConvert(typeof(T))).ToArray()) - { - converters.Remove(c); - } - converters.Add(converter); - } } } \ No newline at end of file diff --git a/src/AspNetCore.NewtonsoftJson/DateTimeOffsetPattern.cs b/src/AspNetCore.NewtonsoftJson/DateTimeOffsetPattern.cs deleted file mode 100644 index fc2930ef8..000000000 --- a/src/AspNetCore.NewtonsoftJson/DateTimeOffsetPattern.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NodaTime; -using NodaTime.Extensions; -using NodaTime.Text; -using System; -using System.Text; - -namespace Rocket.Surgery.LaunchPad.AspNetCore.NewtonsoftJson -{ - class DateTimeOffsetPattern : IPattern - { - public ParseResult Parse(string text) => DateTimeOffset.TryParse(text, out var value) - ? ParseResult.ForValue(value.ToInstant()) - : ParseResult.ForException(() => new FormatException("Could not parse DateTimeOffset")); - - public string Format(Instant value) => InstantPattern.ExtendedIso.Format(value); - public StringBuilder AppendFormat(Instant value, StringBuilder builder) => InstantPattern.ExtendedIso.AppendFormat(value, builder); - } -} \ No newline at end of file diff --git a/src/AspNetCore.NewtonsoftJson/DateTimePattern.cs b/src/AspNetCore.NewtonsoftJson/DateTimePattern.cs deleted file mode 100644 index b1693249e..000000000 --- a/src/AspNetCore.NewtonsoftJson/DateTimePattern.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NodaTime; -using NodaTime.Extensions; -using NodaTime.Text; -using System; -using System.Text; - -namespace Rocket.Surgery.LaunchPad.AspNetCore.NewtonsoftJson -{ - class DateTimePattern : IPattern - { - public ParseResult Parse(string text) => DateTime.TryParse(text, out var value) - ? ParseResult.ForValue(value.ToInstant()) - : ParseResult.ForException(() => new FormatException("Could not parse DateTimeOffset")); - - public string Format(Instant value) => InstantPattern.ExtendedIso.Format(value); - public StringBuilder AppendFormat(Instant value, StringBuilder builder) => InstantPattern.ExtendedIso.AppendFormat(value, builder); - } -} \ No newline at end of file diff --git a/src/AspNetCore/Conventions/SystemJsonTextConvention.cs b/src/AspNetCore/Conventions/SystemJsonTextConvention.cs index 421f92289..8a09824b6 100644 --- a/src/AspNetCore/Conventions/SystemJsonTextConvention.cs +++ b/src/AspNetCore/Conventions/SystemJsonTextConvention.cs @@ -12,6 +12,7 @@ using Rocket.Surgery.Conventions; using Rocket.Surgery.Conventions.DependencyInjection; using Rocket.Surgery.LaunchPad.AspNetCore.Conventions; +using Rocket.Surgery.LaunchPad.Foundation; using System.Collections.Generic; using System.Linq; using JsonConverter = System.Text.Json.Serialization.JsonConverter; @@ -30,6 +31,12 @@ namespace Rocket.Surgery.LaunchPad.AspNetCore.Conventions [PublicAPI] public class SystemJsonTextConvention : IServiceConvention { + private readonly FoundationOptions _options; + + public SystemJsonTextConvention(FoundationOptions? options = null) + { + _options = options ?? new(); + } /// /// Registers the specified context. /// @@ -44,105 +51,9 @@ public void Register(IConventionContext context, IConfiguration configuration, I services.Configure( options => { - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); - options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - InstantPattern.General, - InstantPattern.ExtendedIso, - new DateTimeOffsetPattern(), - new DateTimePattern() - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - LocalDatePattern.Iso, - LocalDatePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - LocalDateTimePattern.GeneralIso, - LocalDateTimePattern.ExtendedIso, - LocalDateTimePattern.BclRoundtrip, - LocalDateTimePattern.FullRoundtrip, - LocalDateTimePattern.FullRoundtripWithoutCalendar - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - LocalTimePattern.ExtendedIso, - LocalTimePattern.GeneralIso, - LocalTimePattern.LongExtendedIso - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - OffsetPattern.GeneralInvariant, - OffsetPattern.GeneralInvariantWithZ - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - DurationPattern.JsonRoundtrip, - DurationPattern.Roundtrip - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - PeriodPattern.Roundtrip, - PeriodPattern.NormalizingIso - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - OffsetDateTimePattern.GeneralIso, - OffsetDateTimePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - OffsetDatePattern.GeneralIso, - OffsetDatePattern.FullRoundtrip - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - OffsetTimePattern.Rfc3339, - OffsetTimePattern.GeneralIso, - OffsetTimePattern.ExtendedIso - ) - ); - ReplaceConverter( - options.JsonSerializerOptions.Converters, - new CompositeNodaPatternConverter( - ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo z", DateTimeZoneProviders.Tzdb), - ZonedDateTimePattern.ExtendedFormatOnlyIso, - ZonedDateTimePattern.GeneralFormatOnlyIso - ) - ); + options.JsonSerializerOptions.ConfigureForLaunchPad(_options.DateTimeZoneProvider); } ); } - - private static void ReplaceConverter(ICollection converters, CompositeNodaPatternConverter converter) - { - foreach (var c in converters.Where(z => z.CanConvert(typeof(T))).ToArray()) - { - converters.Remove(c); - } - converters.Add(converter); - } } } \ No newline at end of file diff --git a/src/AspNetCore/Rocket.Surgery.LaunchPad.AspNetCore.csproj b/src/AspNetCore/Rocket.Surgery.LaunchPad.AspNetCore.csproj index 28d81e0cd..3039e8926 100644 --- a/src/AspNetCore/Rocket.Surgery.LaunchPad.AspNetCore.csproj +++ b/src/AspNetCore/Rocket.Surgery.LaunchPad.AspNetCore.csproj @@ -16,7 +16,4 @@ - - - diff --git a/src/EntityFramework.HotChocolate/Rocket.Surgery.LaunchPad.EntityFramework.HotChocolate.csproj b/src/EntityFramework.HotChocolate/Rocket.Surgery.LaunchPad.EntityFramework.HotChocolate.csproj index bff941bdc..d9a0fbbf4 100644 --- a/src/EntityFramework.HotChocolate/Rocket.Surgery.LaunchPad.EntityFramework.HotChocolate.csproj +++ b/src/EntityFramework.HotChocolate/Rocket.Surgery.LaunchPad.EntityFramework.HotChocolate.csproj @@ -1,6 +1,6 @@  - netstandard2.1;net5.0 + net5.0 $(PackageTags) diff --git a/src/AspNetCore.NewtonsoftJson/CompositeNodaPatternConverter.cs b/src/Foundation.NewtonsoftJson/NewtonsoftJsonCompositeNodaPatternConverter.cs similarity index 88% rename from src/AspNetCore.NewtonsoftJson/CompositeNodaPatternConverter.cs rename to src/Foundation.NewtonsoftJson/NewtonsoftJsonCompositeNodaPatternConverter.cs index b5380140e..eb494ec73 100644 --- a/src/AspNetCore.NewtonsoftJson/CompositeNodaPatternConverter.cs +++ b/src/Foundation.NewtonsoftJson/NewtonsoftJsonCompositeNodaPatternConverter.cs @@ -4,14 +4,14 @@ using NodaTime.Utility; using System; -namespace Rocket.Surgery.LaunchPad.AspNetCore.NewtonsoftJson +namespace Rocket.Surgery.LaunchPad.Foundation { /// /// A JSON converter for types which can be represented by a single string value, parsed or formatted /// from an . /// /// The type to convert to/from JSON. - public sealed class CompositeNodaPatternConverter : NodaConverterBase + public sealed class NewtonsoftJsonCompositeNodaPatternConverter : NodaConverterBase { private readonly IPattern[] _patterns; private readonly Action? _validator; @@ -20,7 +20,7 @@ public sealed class CompositeNodaPatternConverter : NodaConverterBase /// Creates a new instance with a pattern and no validator. /// /// The patterns to use for parsing and formatting. - public CompositeNodaPatternConverter(params IPattern[] patterns) : this(null, patterns) { } + public NewtonsoftJsonCompositeNodaPatternConverter(params IPattern[] patterns) : this(null, patterns) { } /// /// Creates a new instance with a pattern and an optional validator. The validator will be called before each @@ -28,7 +28,7 @@ public CompositeNodaPatternConverter(params IPattern[] patterns) : this(null, /// /// The patterns to use for parsing and formatting. /// The validator to call before writing values. May be null, indicating that no validation is required. - public CompositeNodaPatternConverter(Action? validator, params IPattern[] patterns) + public NewtonsoftJsonCompositeNodaPatternConverter(Action? validator, params IPattern[] patterns) { this._patterns = patterns; this._validator = validator; diff --git a/src/AspNetCore/DateTimeOffsetPattern.cs b/src/Foundation.NewtonsoftJson/NewtonsoftJsonDateTimeOffsetPattern.cs similarity index 84% rename from src/AspNetCore/DateTimeOffsetPattern.cs rename to src/Foundation.NewtonsoftJson/NewtonsoftJsonDateTimeOffsetPattern.cs index 8600d080f..07e471da0 100644 --- a/src/AspNetCore/DateTimeOffsetPattern.cs +++ b/src/Foundation.NewtonsoftJson/NewtonsoftJsonDateTimeOffsetPattern.cs @@ -4,9 +4,9 @@ using System; using System.Text; -namespace Rocket.Surgery.LaunchPad.AspNetCore +namespace Rocket.Surgery.LaunchPad.Foundation { - class DateTimeOffsetPattern : IPattern + class NewtonsoftJsonDateTimeOffsetPattern : IPattern { public ParseResult Parse(string text) => DateTimeOffset.TryParse(text, out var value) ? ParseResult.ForValue(value.ToInstant()) diff --git a/src/Foundation.NewtonsoftJson/NodaTimeNewtonsoftSerializationExtensions.cs b/src/Foundation.NewtonsoftJson/NodaTimeNewtonsoftSerializationExtensions.cs new file mode 100644 index 000000000..28dc4e49d --- /dev/null +++ b/src/Foundation.NewtonsoftJson/NodaTimeNewtonsoftSerializationExtensions.cs @@ -0,0 +1,133 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using NodaTime; +using NodaTime.Serialization.JsonNet; +using NodaTime.Text; +using Rocket.Surgery.LaunchPad.AspNetCore; +using System.Collections.Generic; +using System.Linq; + +namespace Rocket.Surgery.LaunchPad.Foundation +{ + /// + /// Extensions for noda time + /// + [PublicAPI] + public static class NodaTimeSerializationExtensions + { + /// + /// Configure System.Text.Json with defaults for launchpad + /// + /// + /// + /// + public static JsonSerializerSettings ConfigureForLaunchPad(this JsonSerializerSettings options, IDateTimeZoneProvider dateTimeZoneProvider) + { + options.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); + options.ConfigureForNodaTime(dateTimeZoneProvider); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + InstantPattern.ExtendedIso, + InstantPattern.General, + new NewtonsoftJsonDateTimeOffsetPattern() + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + LocalDatePattern.Iso, + LocalDatePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + LocalDateTimePattern.ExtendedIso, + LocalDateTimePattern.GeneralIso, + LocalDateTimePattern.BclRoundtrip, + LocalDateTimePattern.FullRoundtrip, + LocalDateTimePattern.FullRoundtripWithoutCalendar + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + LocalTimePattern.ExtendedIso, + LocalTimePattern.GeneralIso, + LocalTimePattern.LongExtendedIso + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + OffsetPattern.GeneralInvariant, + OffsetPattern.GeneralInvariantWithZ + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + DurationPattern.JsonRoundtrip, + DurationPattern.Roundtrip + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + DurationPattern.JsonRoundtrip, + DurationPattern.Roundtrip + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + PeriodPattern.Roundtrip, + PeriodPattern.NormalizingIso + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + OffsetDateTimePattern.GeneralIso, + OffsetDateTimePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + OffsetDatePattern.GeneralIso, + OffsetDatePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + OffsetTimePattern.Rfc3339, + OffsetTimePattern.GeneralIso, + OffsetTimePattern.ExtendedIso + ) + ); + ReplaceConverter( + options.Converters, + new NewtonsoftJsonCompositeNodaPatternConverter( + ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo z", DateTimeZoneProviders.Tzdb) + ) + ); + + return options; + } + + private static void ReplaceConverter(ICollection converters, NewtonsoftJsonCompositeNodaPatternConverter converter) + { + foreach (var c in converters.Where(z => z.CanConvert(typeof(T))).ToArray()) + { + converters.Remove(c); + } + + converters.Add(converter); + } + } +} \ No newline at end of file diff --git a/src/Foundation.NewtonsoftJson/Rocket.Surgery.LaunchPad.Foundation.NewtonsoftJson.csproj b/src/Foundation.NewtonsoftJson/Rocket.Surgery.LaunchPad.Foundation.NewtonsoftJson.csproj index 7c6297fc4..48c423640 100644 --- a/src/Foundation.NewtonsoftJson/Rocket.Surgery.LaunchPad.Foundation.NewtonsoftJson.csproj +++ b/src/Foundation.NewtonsoftJson/Rocket.Surgery.LaunchPad.Foundation.NewtonsoftJson.csproj @@ -3,12 +3,13 @@ netstandard2.1;net5.0 $(PackageTags) + Rocket.Surgery.LaunchPad.Foundation - + - - + + diff --git a/src/Foundation/Conventions/FluentValidationConvention.cs b/src/Foundation/Conventions/FluentValidationConvention.cs index 860ddf3e2..aadf70582 100644 --- a/src/Foundation/Conventions/FluentValidationConvention.cs +++ b/src/Foundation/Conventions/FluentValidationConvention.cs @@ -24,6 +24,12 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions [AfterConvention(typeof(MediatRConvention))] public class FluentValidationConvention : IServiceConvention { + private readonly FoundationOptions _options; + + public FluentValidationConvention(FoundationOptions? options = null) + { + _options = options ?? new FoundationOptions(); + } /// /// Registers the specified context. /// @@ -37,17 +43,16 @@ public void Register(IConventionContext context, IConfiguration configuration, I throw new ArgumentNullException(nameof(context)); } - var lifetime = context.Get()!.Lifetime!; var assemblies = context .AssemblyCandidateFinder .GetCandidateAssemblies("FluentValidation"); foreach (var item in new AssemblyScanner(assemblies.SelectMany(z => z.DefinedTypes).Select(x => x.AsType()))) { - services.TryAddEnumerable(new ServiceDescriptor(item.InterfaceType, item.ValidatorType, lifetime)); + services.TryAddEnumerable(new ServiceDescriptor(item.InterfaceType, item.ValidatorType, _options.ValidationLifetime)); } services.TryAddSingleton(); - services.TryAddEnumerable(new ServiceDescriptor(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>), lifetime)); + services.TryAddEnumerable(new ServiceDescriptor(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>), _options.MediatorLifetime)); } } } \ No newline at end of file diff --git a/src/Foundation/Conventions/MediatRConvention.cs b/src/Foundation/Conventions/MediatRConvention.cs index 993457b86..227cac8a5 100644 --- a/src/Foundation/Conventions/MediatRConvention.cs +++ b/src/Foundation/Conventions/MediatRConvention.cs @@ -6,6 +6,7 @@ using Rocket.Surgery.Conventions.DependencyInjection; using Rocket.Surgery.Conventions.Reflection; using Rocket.Surgery.LaunchPad.Foundation.Conventions; +using System; using System.Linq; [assembly: Convention(typeof(MediatRConvention))] @@ -19,21 +20,41 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions /// public class MediatRConvention : IServiceConvention { + private readonly FoundationOptions _options; + + public MediatRConvention(FoundationOptions? options = null) + { + _options = options ?? new FoundationOptions(); + } + /// /// Registers the specified context. /// /// The context. public void Register(IConventionContext context, IConfiguration configuration, IServiceCollection services) { - var serviceConfig = context.GetOrAdd(() => new MediatRServiceConfiguration()); - context.Set(serviceConfig); - - var assemblies = context.AssemblyCandidateFinder - .GetCandidateAssemblies(nameof(MediatR)) - .ToArray(); - - ServiceRegistrar.AddRequiredServices(services, serviceConfig); - ServiceRegistrar.AddMediatRClasses(services, assemblies); + services.AddMediatR( + c => + { + switch (_options.MediatorLifetime) + { + case ServiceLifetime.Singleton: + c.AsSingleton(); + break; + case ServiceLifetime.Scoped: + c.AsScoped(); + break; + case ServiceLifetime.Transient: + c.AsTransient(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + }, + context.AssemblyCandidateFinder + .GetCandidateAssemblies(nameof(MediatR)) + .ToArray() + ); } } } \ No newline at end of file diff --git a/src/Foundation/Conventions/NodaTimeConvention.cs b/src/Foundation/Conventions/NodaTimeConvention.cs index 4febc0056..9e8da1ed5 100644 --- a/src/Foundation/Conventions/NodaTimeConvention.cs +++ b/src/Foundation/Conventions/NodaTimeConvention.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using NodaTime; using NodaTime.TimeZones; using Rocket.Surgery.Conventions; @@ -20,6 +21,12 @@ namespace Rocket.Surgery.LaunchPad.Foundation.Conventions [LiveConvention] public class NodaTimeConvention : IServiceConvention { + private readonly FoundationOptions _options; + + public NodaTimeConvention(FoundationOptions? options = null) + { + _options = options ?? new FoundationOptions(); + } /// /// Registers the specified context. /// @@ -31,9 +38,9 @@ public void Register(IConventionContext context, IConfiguration configuration, I throw new ArgumentNullException(nameof(context)); } - services.AddSingleton(SystemClock.Instance); - services.AddSingleton(); - services.AddSingleton(TzdbDateTimeZoneSource.Default); + services.TryAddSingleton(SystemClock.Instance); + services.TryAddSingleton(); + services.TryAddSingleton(_options.DateTimeZoneSource); } } } \ No newline at end of file diff --git a/src/Foundation/FoundationOptions.cs b/src/Foundation/FoundationOptions.cs index 282c60154..aa7a3e299 100644 --- a/src/Foundation/FoundationOptions.cs +++ b/src/Foundation/FoundationOptions.cs @@ -1,4 +1,8 @@ using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using NodaTime; +using NodaTime.TimeZones; +using System.Collections.Generic; using System.Reflection; namespace Rocket.Surgery.LaunchPad.Foundation @@ -16,5 +20,25 @@ public class FoundationOptions /// Useful so that applications and conventions can know the "true" executing assembly when running in an environment like azure functions /// public Assembly? EntryAssembly { get; set; } = null!; + + /// + /// The NodaTime timezone source + /// + public IDateTimeZoneProvider DateTimeZoneProvider { get; set; } = DateTimeZoneProviders.Tzdb; + + /// + /// The NodaTime timezone source + /// + public IDateTimeZoneSource DateTimeZoneSource { get; set; } = TzdbDateTimeZoneSource.Default; + + /// + /// The Mediator lifetime + /// + public ServiceLifetime MediatorLifetime { get; set; } = ServiceLifetime.Transient; + + /// + /// The lifetime for validation services + /// + public ServiceLifetime ValidationLifetime { get; set; } = ServiceLifetime.Transient; } } \ No newline at end of file diff --git a/src/Foundation/NodaTimeSystemTextJsonSerializationExtensions.cs b/src/Foundation/NodaTimeSystemTextJsonSerializationExtensions.cs new file mode 100644 index 000000000..f50bf4d5e --- /dev/null +++ b/src/Foundation/NodaTimeSystemTextJsonSerializationExtensions.cs @@ -0,0 +1,126 @@ +using JetBrains.Annotations; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; +using NodaTime.Text; +using Rocket.Surgery.LaunchPad.AspNetCore; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Rocket.Surgery.LaunchPad.Foundation +{ + /// + /// Extensions for noda time + /// + [PublicAPI] + public static class NodaTimeSystemTextJsonSerializationExtensions + { + /// + /// Configure System.Text.Json with defaults for launchpad + /// + /// + /// + /// + public static JsonSerializerOptions ConfigureForLaunchPad(this JsonSerializerOptions options, IDateTimeZoneProvider dateTimeZoneProvider) + { + options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.ConfigureForNodaTime(dateTimeZoneProvider); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + InstantPattern.General, + InstantPattern.ExtendedIso, + new SystemTextJsonDateTimeOffsetPattern() + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + LocalDatePattern.Iso, + LocalDatePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + LocalDateTimePattern.GeneralIso, + LocalDateTimePattern.ExtendedIso, + LocalDateTimePattern.BclRoundtrip, + LocalDateTimePattern.FullRoundtrip, + LocalDateTimePattern.FullRoundtripWithoutCalendar + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + LocalTimePattern.ExtendedIso, + LocalTimePattern.GeneralIso, + LocalTimePattern.LongExtendedIso + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + OffsetPattern.GeneralInvariant, + OffsetPattern.GeneralInvariantWithZ + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + DurationPattern.JsonRoundtrip, + DurationPattern.Roundtrip + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + PeriodPattern.Roundtrip, + PeriodPattern.NormalizingIso + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + OffsetDateTimePattern.GeneralIso, + OffsetDateTimePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + OffsetDatePattern.GeneralIso, + OffsetDatePattern.FullRoundtrip + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + OffsetTimePattern.Rfc3339, + OffsetTimePattern.GeneralIso, + OffsetTimePattern.ExtendedIso + ) + ); + ReplaceConverter( + options.Converters, + new SystemTextJsonCompositeNodaPatternConverter( + ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo z", DateTimeZoneProviders.Tzdb) + ) + ); + + return options; + } + + private static void ReplaceConverter(ICollection converters, SystemTextJsonCompositeNodaPatternConverter converter) + { + foreach (var c in converters.Where(z => z.CanConvert(typeof(T))).ToArray()) + { + converters.Remove(c); + } + + converters.Add(converter); + } + } +} \ No newline at end of file diff --git a/src/AspNetCore/CompositeNodaPatternConverter.cs b/src/Foundation/SystemTextJsonCompositeNodaPatternConverter.cs similarity index 84% rename from src/AspNetCore/CompositeNodaPatternConverter.cs rename to src/Foundation/SystemTextJsonCompositeNodaPatternConverter.cs index b4c5607d9..435cb9310 100644 --- a/src/AspNetCore/CompositeNodaPatternConverter.cs +++ b/src/Foundation/SystemTextJsonCompositeNodaPatternConverter.cs @@ -3,14 +3,14 @@ using System; using System.Text.Json; -namespace Rocket.Surgery.LaunchPad.AspNetCore +namespace Rocket.Surgery.LaunchPad.Foundation { /// /// A JSON converter for types which can be represented by a single string value, parsed or formatted /// from an . /// /// The type to convert to/from JSON. - public sealed class CompositeNodaPatternConverter : NodaConverterBase + public sealed class SystemTextJsonCompositeNodaPatternConverter : NodaConverterBase { private readonly IPattern[] _patterns; private readonly Action? _validator; @@ -19,7 +19,7 @@ public sealed class CompositeNodaPatternConverter : NodaConverterBase /// Creates a new instance with a pattern and no validator. /// /// The patterns to use for parsing and formatting. - public CompositeNodaPatternConverter(params IPattern[] patterns) : this(null, patterns) { } + public SystemTextJsonCompositeNodaPatternConverter(params IPattern[] patterns) : this(null, patterns) { } /// /// Creates a new instance with a pattern and an optional validator. The validator will be called before each @@ -27,7 +27,7 @@ public CompositeNodaPatternConverter(params IPattern[] patterns) : this(null, /// /// The patterns to use for parsing and formatting. /// The validator to call before writing values. May be null, indicating that no validation is required. - public CompositeNodaPatternConverter(Action? validator, params IPattern[] patterns) + public SystemTextJsonCompositeNodaPatternConverter(Action? validator, params IPattern[] patterns) { this._patterns = patterns; this._validator = validator; diff --git a/src/AspNetCore/DateTimePattern.cs b/src/Foundation/SystemTextJsonDateTimeOffsetPattern.cs similarity index 63% rename from src/AspNetCore/DateTimePattern.cs rename to src/Foundation/SystemTextJsonDateTimeOffsetPattern.cs index 9ba673223..ca66ea9e7 100644 --- a/src/AspNetCore/DateTimePattern.cs +++ b/src/Foundation/SystemTextJsonDateTimeOffsetPattern.cs @@ -6,13 +6,13 @@ namespace Rocket.Surgery.LaunchPad.AspNetCore { - class DateTimePattern : IPattern + class SystemTextJsonDateTimeOffsetPattern : IPattern { - public ParseResult Parse(string text) => DateTime.TryParse(text, out var value) + public ParseResult Parse(string text) => DateTimeOffset.TryParse(text, out var value) ? ParseResult.ForValue(value.ToInstant()) : ParseResult.ForException(() => new FormatException("Could not parse DateTimeOffset")); - public string Format(Instant value) => InstantPattern.ExtendedIso.Format(value); - public StringBuilder AppendFormat(Instant value, StringBuilder builder) => InstantPattern.ExtendedIso.AppendFormat(value, builder); + public string Format(Instant value) => InstantPattern.General.Format(value); + public StringBuilder AppendFormat(Instant value, StringBuilder builder) => InstantPattern.General.AppendFormat(value, builder); } } \ No newline at end of file diff --git a/src/Mapping.NewtonsoftJson/AutoMapperConvention.cs b/src/Mapping.NewtonsoftJson/AutoMapperNewtonsoftJsonConvention.cs similarity index 100% rename from src/Mapping.NewtonsoftJson/AutoMapperConvention.cs rename to src/Mapping.NewtonsoftJson/AutoMapperNewtonsoftJsonConvention.cs diff --git a/src/Mapping.NewtonsoftJson/Rocket.Surgery.LaunchPad.Mapping.NewtonsoftJson.csproj b/src/Mapping.NewtonsoftJson/Rocket.Surgery.LaunchPad.Mapping.NewtonsoftJson.csproj index cb2c4de14..541ffd774 100644 --- a/src/Mapping.NewtonsoftJson/Rocket.Surgery.LaunchPad.Mapping.NewtonsoftJson.csproj +++ b/src/Mapping.NewtonsoftJson/Rocket.Surgery.LaunchPad.Mapping.NewtonsoftJson.csproj @@ -1,13 +1,14 @@  - - netstandard2.1;net5.0 - - $(PackageTags) - - - - - - - + + netstandard2.1;net5.0 + + $(PackageTags) + Rocket.Surgery.LaunchPad.Mapping + + + + + + + diff --git a/test/Extensions.Tests/MediatRTests.cs b/test/Extensions.Tests/MediatRTests.cs index 5bfba2467..c73df551b 100644 --- a/test/Extensions.Tests/MediatRTests.cs +++ b/test/Extensions.Tests/MediatRTests.cs @@ -51,8 +51,10 @@ public async Task Test2() .UseAssemblies(new TestAssemblyProvider().GetAssemblies()); var context = ConventionContext.From(builder); var services = new ServiceCollection(); - context.Set(new MediatRServiceConfiguration().AsSingleton()); - new MediatRConvention().Register(context, new ConfigurationBuilder().Build(), services); + new MediatRConvention(new FoundationOptions() + { + MediatorLifetime = ServiceLifetime.Singleton + }).Register(context, new ConfigurationBuilder().Build(), services); var sub = A.Fake>(); diff --git a/test/Extensions.Tests/NewtonsoftJsonNodaTimeTests.cs b/test/Extensions.Tests/NewtonsoftJsonNodaTimeTests.cs new file mode 100644 index 000000000..12586401e --- /dev/null +++ b/test/Extensions.Tests/NewtonsoftJsonNodaTimeTests.cs @@ -0,0 +1,137 @@ +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using NodaTime; +using NodaTime.Text; +using Rocket.Surgery.Extensions.Testing; +using Rocket.Surgery.LaunchPad.Foundation; +using Serilog; +using System; +using Xunit; +using Xunit.Abstractions; + +namespace Extensions.Tests +{ + public class NewtonsoftJsonNodaTimeTests : LoggerTest + { + private JsonSerializerSettings _settings; + + public NewtonsoftJsonNodaTimeTests(ITestOutputHelper outputHelper) : base(outputHelper) + { + _settings = new JsonSerializerSettings() + .ConfigureForLaunchPad(DateTimeZoneProviders.Tzdb); + } + + [Theory] + [InlineData("2020-01-01T12:12:12Z")] + [InlineData("2020-01-01T12:12:12.000000000Z")] + [InlineData("01/01/2020 12:12:12 +00:00")] + public void Instant_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(Instant.FromUtc(2020, 01, 01, 12, 12, 12)); + } + + [Theory] + [InlineData("2020-01-01")] + [InlineData("2020-01-01 (ISO)")] + public void LocalDate_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalDate(2020, 01, 01)); + } + + [Theory] + [InlineData("2020-01-01T12:12:12")] + [InlineData("2020-01-01T12:12:12.000000000")] + [InlineData("2020-01-01T12:12:12.0000000")] + [InlineData("2020-01-01T12:12:12.000000000 (ISO)")] + public void LocalDateTime_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalDateTime(2020, 01, 01, 12, 12, 12)); + } + + [Theory] + [InlineData("12:12:12")] + [InlineData("12:12:12.0000000")] + [InlineData("12:12:12.000000000")] + public void LocalTime_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalTime(12, 12, 12)); + } + + [Theory] + [InlineData("+03:25:45")] + public void Offset_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(Offset.FromTimeSpan(TimeSpan.FromSeconds(12345))); + } + + [Theory] + [InlineData("3:25:45")] + [InlineData("0:03:25:45")] + public void Duration_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(Duration.FromTimeSpan(TimeSpan.FromSeconds(12345))); + } + + [Theory] + [InlineData("PT12345S")] + [InlineData("PT3H25M45S")] + public void Period_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Normalize().Should() + .Be(Period.FromSeconds(12345).Normalize()); + } + + [Theory] + [InlineData("2020-01-01T12:12:12+01")] + [InlineData("2020-01-01T12:12:12+01 (ISO)")] + public void OffsetDateTime_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetDateTime(new LocalDateTime(2020, 01, 01, 12, 12, 12), Offset.FromHours(1))); + } + + [Theory] + [InlineData("2020-01-01+01")] + [InlineData("2020-01-01+01 (ISO)")] + public void OffsetDate_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetDate(new LocalDate(2020, 01, 01), Offset.FromHours(1))); + } + + [Theory] + [InlineData("12:12:12+01:00")] + [InlineData("12:12:12+01")] + public void OffsetTime_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetTime(new LocalTime(12, 12, 12), Offset.FromHours(1))); + } + + [Theory] + [InlineData("2020-01-01T07:12:12-05 America/New_York")] + public void ZonedDateTime_Tests(string value) + { + JsonConvert.DeserializeObject("\"" + value + "\"", _settings)! + .Should() + .Be(new ZonedDateTime(Instant.FromUtc(2020, 01, 01, 12, 12, 12), DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York"))); + } + } +} \ No newline at end of file diff --git a/test/Extensions.Tests/SystemTextJsonNodaTimeTests.cs b/test/Extensions.Tests/SystemTextJsonNodaTimeTests.cs new file mode 100644 index 000000000..e2759241c --- /dev/null +++ b/test/Extensions.Tests/SystemTextJsonNodaTimeTests.cs @@ -0,0 +1,137 @@ +using FluentAssertions; +using Microsoft.Extensions.Logging; +using NodaTime; +using NodaTime.Text; +using Rocket.Surgery.Extensions.Testing; +using Rocket.Surgery.LaunchPad.Foundation; +using Serilog; +using System; +using System.Text.Json; +using Xunit; +using Xunit.Abstractions; + +namespace Extensions.Tests +{ + public class SystemTextJsonNodaTimeTests : LoggerTest + { + private JsonSerializerOptions _settings; + + public SystemTextJsonNodaTimeTests(ITestOutputHelper outputHelper) : base(outputHelper) + { + _settings = new JsonSerializerOptions() + .ConfigureForLaunchPad(DateTimeZoneProviders.Tzdb); + } + + [Theory] + [InlineData("2020-01-01T12:12:12Z")] + [InlineData("2020-01-01T12:12:12.000000000Z")] + [InlineData("01/01/2020 12:12:12 +00:00")] + public void Instant_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(Instant.FromUtc(2020, 01, 01, 12, 12, 12)); + } + + [Theory] + [InlineData("2020-01-01")] + [InlineData("2020-01-01 (ISO)")] + public void LocalDate_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalDate(2020, 01, 01)); + } + + [Theory] + [InlineData("2020-01-01T12:12:12")] + [InlineData("2020-01-01T12:12:12.000000000")] + [InlineData("2020-01-01T12:12:12.0000000")] + [InlineData("2020-01-01T12:12:12.000000000 (ISO)")] + public void LocalDateTime_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalDateTime(2020, 01, 01, 12, 12, 12)); + } + + [Theory] + [InlineData("12:12:12")] + [InlineData("12:12:12.0000000")] + [InlineData("12:12:12.000000000")] + public void LocalTime_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new LocalTime(12, 12, 12)); + } + + [Theory] + [InlineData("+03:25:45")] + public void Offset_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(Offset.FromTimeSpan(TimeSpan.FromSeconds(12345))); + } + + [Theory] + [InlineData("3:25:45")] + [InlineData("0:03:25:45")] + public void Duration_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(Duration.FromTimeSpan(TimeSpan.FromSeconds(12345))); + } + + [Theory] + [InlineData("PT12345S")] + [InlineData("PT3H25M45S")] + public void Period_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Normalize().Should() + .Be(Period.FromSeconds(12345).Normalize()); + } + + [Theory] + [InlineData("2020-01-01T12:12:12+01")] + [InlineData("2020-01-01T12:12:12+01 (ISO)")] + public void OffsetDateTime_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetDateTime(new LocalDateTime(2020, 01, 01, 12, 12, 12), Offset.FromHours(1))); + } + + [Theory] + [InlineData("2020-01-01+01")] + [InlineData("2020-01-01+01 (ISO)")] + public void OffsetDate_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetDate(new LocalDate(2020, 01, 01), Offset.FromHours(1))); + } + + [Theory] + [InlineData("12:12:12+01:00")] + [InlineData("12:12:12+01")] + public void OffsetTime_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new OffsetTime(new LocalTime(12, 12, 12), Offset.FromHours(1))); + } + + [Theory] + [InlineData("2020-01-01T07:12:12-05 America/New_York")] + public void ZonedDateTime_Tests(string value) + { + JsonSerializer.Deserialize("\"" + value + "\"", _settings)! + .Should() + .Be(new ZonedDateTime(Instant.FromUtc(2020, 01, 01, 12, 12, 12), DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York"))); + } + } +} \ No newline at end of file diff --git a/test/Sample.Core.Tests/LaunchRecords/CreateLaunchRecordTests.cs b/test/Sample.Core.Tests/LaunchRecords/CreateLaunchRecordTests.cs index ba1ac73d1..3f7278b95 100644 --- a/test/Sample.Core.Tests/LaunchRecords/CreateLaunchRecordTests.cs +++ b/test/Sample.Core.Tests/LaunchRecords/CreateLaunchRecordTests.cs @@ -22,18 +22,24 @@ public CreateLaunchRecordTests(ITestOutputHelper outputHelper) : base(outputHelp [Fact] public async Task Should_Create_A_LaunchRecord() { - var context = ServiceProvider.GetRequiredService(); - var rocket = new ReadyRocket() - { - Type = RocketType.Falcon9, - SerialNumber = "12345678901234" - }; - context.Add(rocket); + var rocket = await ServiceProvider.WithScoped() + .Invoke( + async (context, ct) => + { + var rocket = new ReadyRocket() + { + Type = RocketType.Falcon9, + SerialNumber = "12345678901234" + }; + context.Add(rocket); - await context.SaveChangesAsync(); + await context.SaveChangesAsync(ct); + return rocket; + } + ); var response = await ServiceProvider.WithScoped().Invoke( - (mediator, clock) => mediator.Send( + async (mediator, clock, ct) => await mediator.Send( new CreateLaunchRecord.Request() { Partner = "partner", @@ -41,7 +47,8 @@ public async Task Should_Create_A_LaunchRecord() RocketId = rocket.Id, ScheduledLaunchDate = clock.GetCurrentInstant(), PayloadWeightKg = 100, - } + }, + ct ) ); diff --git a/test/Sample.Graphql.Tests/Sample.Graphql.Tests.csproj b/test/Sample.Graphql.Tests/Sample.Graphql.Tests.csproj index 737da3ead..5df848c72 100644 --- a/test/Sample.Graphql.Tests/Sample.Graphql.Tests.csproj +++ b/test/Sample.Graphql.Tests/Sample.Graphql.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1;net5.0 + net5.0