From 87c29319fcc08d10570f9fb64a604bbe0252c784 Mon Sep 17 00:00:00 2001 From: Tim Schneider <43130816+DerStimmler@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:49:09 +0100 Subject: [PATCH] feat: add ToServerSentEventsHttpResult with IAsyncEnumerableT extension method --- .../Builders/ResultExtensionsClassBuilder.cs | 1 + ...rSentEventsHttpResultIAsyncEnumerableTE.cs | 29 ++++ ...erSentEventsHttpResultIAsyncEnumerableT.cs | 142 ++++++++++++++++++ ...rSentEventsHttpResultIAsyncEnumerableTE.cs | 87 +++++++++++ .../Utils/IEnumerableExtensions.cs | 12 ++ .../Utils/ServerSentEventsExtensions.cs | 57 +++++++ ...erSentEventsHttpResultIAsyncEnumerableT.cs | 56 +++++++ README.md | 67 +++++---- global.json | 2 +- 9 files changed, 421 insertions(+), 32 deletions(-) create mode 100644 CSharpFunctionalExtensions.HttpResults.Generators/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs create mode 100644 CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs create mode 100644 CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs create mode 100644 CSharpFunctionalExtensions.HttpResults.Tests/Utils/IEnumerableExtensions.cs create mode 100644 CSharpFunctionalExtensions.HttpResults.Tests/Utils/ServerSentEventsExtensions.cs create mode 100644 CSharpFunctionalExtensions.HttpResults/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs diff --git a/CSharpFunctionalExtensions.HttpResults.Generators/Builders/ResultExtensionsClassBuilder.cs b/CSharpFunctionalExtensions.HttpResults.Generators/Builders/ResultExtensionsClassBuilder.cs index c7071aa..cc1bf74 100644 --- a/CSharpFunctionalExtensions.HttpResults.Generators/Builders/ResultExtensionsClassBuilder.cs +++ b/CSharpFunctionalExtensions.HttpResults.Generators/Builders/ResultExtensionsClassBuilder.cs @@ -30,5 +30,6 @@ List mapperClasses new ToStatusCodeHttpResultTE(), new ToOkHttpResultTE(), new ToContentHttpResultStringE(), + new ToServerSentEventsHttpResultIAsyncEnumerableTE(), ]; } diff --git a/CSharpFunctionalExtensions.HttpResults.Generators/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs b/CSharpFunctionalExtensions.HttpResults.Generators/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs new file mode 100644 index 0000000..f755a08 --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults.Generators/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs @@ -0,0 +1,29 @@ +namespace CSharpFunctionalExtensions.HttpResults.Generators.ResultExtensions; + +internal class ToServerSentEventsHttpResultIAsyncEnumerableTE : IGenerateMethods +{ + public string Generate(string mapperClassName, string resultErrorType, string httpResultType) + { + return $$""" + #if NET10_0_OR_GREATER + /// + /// Returns a based of a in case of success. Returns custom mapping in case of failure. + /// + public static Results, {{httpResultType}}> ToServerSentEventsHttpResult(this Result,{{resultErrorType}}> result, string? eventType = null) + { + if (result.IsSuccess) return TypedResults.ServerSentEvents(result.Value, eventType); + + return ErrorMapperInstances.{{mapperClassName}}.Map(result.Error); + } + + /// + /// Returns a based of a in case of success. Returns custom mapping in case of failure. + /// + public static async Task, {{httpResultType}}>> ToServerSentEventsHttpResult(this Task,{{resultErrorType}}>> result, string? eventType = null) + { + return (await result).ToServerSentEventsHttpResult(eventType); + } + #endif + """; + } +} diff --git a/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs b/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs new file mode 100644 index 0000000..5cd6303 --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs @@ -0,0 +1,142 @@ +#if NET10_0_OR_GREATER + +using System.Net.Mime; +using CSharpFunctionalExtensions.HttpResults.ResultExtensions; +using CSharpFunctionalExtensions.HttpResults.Tests.Utils; +using FluentAssertions; +using Microsoft.AspNetCore.Http.HttpResults; + +namespace CSharpFunctionalExtensions.HttpResults.Tests.ResultExtensions; + +public class ToServerSentEventsHttpResultIAsyncEnumerableT +{ + [Fact] + public async Task ResultIAsyncEnumerableT_Success_can_be_mapped_to_ServerSentEventsHttpResult() + { + var testValues = new[] { 42, 420 }; + var eventType = "TestEvent"; + + var asyncEnumerable = testValues.AsAsyncEnumerable(); + + var result = + Result.Success(asyncEnumerable).ToServerSentEventsHttpResult(eventType).Result as ServerSentEventsResult; + + var (response, values) = await result!.ExecuteAndGetResponseAndValues(); + + result!.StatusCode.Should().Be(200); + response.ContentType.Should().Be(MediaTypeNames.Text.EventStream); + values.Select(v => v.Data).Should().Contain(testValues.Select(v => v.ToString())); + values.Select(v => v.Event).Should().AllBe(eventType); + } + + [Fact] + public async Task ResultIAsyncEnumerableT_Success_can_be_mapped_to_ServerSentEventsHttpResult_Async() + { + var testValues = new[] { 42, 420 }; + var eventType = "TestEvent"; + + var asyncEnumerable = testValues.AsAsyncEnumerable(); + + var result = + (await Task.FromResult(Result.Success(asyncEnumerable)).ToServerSentEventsHttpResult(eventType)).Result + as ServerSentEventsResult; + + var (response, values) = await result!.ExecuteAndGetResponseAndValues(); + + result!.StatusCode.Should().Be(200); + response.ContentType.Should().Be(MediaTypeNames.Text.EventStream); + values.Select(v => v.Data).Should().Contain(testValues.Select(v => v.ToString())); + values.Select(v => v.Event).Should().AllBe(eventType); + } + + [Fact] + public void ResultIAsyncEnumerableT_Failure_can_be_mapped_to_ServerSentEventsHttpResult() + { + var error = "Error"; + + var result = + Result.Failure>(error).ToServerSentEventsHttpResult().Result as ProblemHttpResult; + + result!.StatusCode.Should().Be(400); + result!.ProblemDetails.Status.Should().Be(400); + result!.ProblemDetails.Detail.Should().Be(error); + } + + [Fact] + public async Task ResultIAsyncEnumerableT_Failure_can_be_mapped_to_ServerSentEventsHttpResult_Async() + { + var error = "Error"; + + var result = + (await Task.FromResult(Result.Failure>(error)).ToServerSentEventsHttpResult()).Result + as ProblemHttpResult; + + result!.StatusCode.Should().Be(400); + result!.ProblemDetails.Status.Should().Be(400); + result!.ProblemDetails.Detail.Should().Be(error); + } + + [Fact] + public void ResultIAsyncEnumerableT_Failure_StatusCode_can_be_changed() + { + var statusCode = 418; + var error = "Error"; + + var result = + Result.Failure>(error).ToServerSentEventsHttpResult(failureStatusCode: statusCode).Result + as ProblemHttpResult; + + result!.StatusCode.Should().Be(statusCode); + result!.ProblemDetails.Status.Should().Be(statusCode); + result!.ProblemDetails.Detail.Should().Be(error); + } + + [Fact] + public async Task ResultIAsyncEnumerableT_Failure_StatusCode_can_be_changed_Async() + { + var statusCode = 418; + var error = "Error"; + + var result = + ( + await Task.FromResult(Result.Failure>(error)) + .ToServerSentEventsHttpResult(failureStatusCode: statusCode) + ).Result as ProblemHttpResult; + + result!.StatusCode.Should().Be(statusCode); + result!.ProblemDetails.Status.Should().Be(statusCode); + result!.ProblemDetails.Detail.Should().Be(error); + } + + [Fact] + public void ResultIAsyncEnumerableT_Failure_ProblemDetails_can_be_customized() + { + var error = "Error"; + var customTitle = "Custom Title"; + + var result = + Result + .Failure>(error) + .ToServerSentEventsHttpResult(customizeProblemDetails: problemDetails => problemDetails.Title = customTitle) + .Result as ProblemHttpResult; + + result!.ProblemDetails.Title.Should().Be(customTitle); + } + + [Fact] + public async Task ResultIAsyncEnumerableT_Failure_ProblemDetails_can_be_customized_Async() + { + var error = "Error"; + var customTitle = "Custom Title"; + + var result = + ( + await Task.FromResult(Result.Failure>(error)) + .ToServerSentEventsHttpResult(customizeProblemDetails: problemDetails => problemDetails.Title = customTitle) + ).Result as ProblemHttpResult; + + result!.ProblemDetails.Title.Should().Be(customTitle); + } +} + +#endif diff --git a/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs b/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs new file mode 100644 index 0000000..4beac36 --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults.Tests/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableTE.cs @@ -0,0 +1,87 @@ +#if NET10_0_OR_GREATER + +using System.Net.Mime; +using CSharpFunctionalExtensions.HttpResults.ResultExtensions; +using CSharpFunctionalExtensions.HttpResults.Tests.Shared; +using CSharpFunctionalExtensions.HttpResults.Tests.Utils; +using FluentAssertions; +using Microsoft.AspNetCore.Http.HttpResults; + +namespace CSharpFunctionalExtensions.HttpResults.Tests.ResultExtensions; + +public class ToServerSentEventsHttpResultIAsyncEnumerableTE +{ + [Fact] + public async Task ResultIAsyncEnumerableTE_Success_can_be_mapped_to_ServerSentEventsHttpResult() + { + var testValues = new[] { 42, 420 }; + var eventType = "TestEvent"; + + var asyncEnumerable = testValues.AsAsyncEnumerable(); + + var result = + Result + .Success, DocumentMissingError>(asyncEnumerable) + .ToServerSentEventsHttpResult(eventType) + .Result as ServerSentEventsResult; + + var (response, values) = await result!.ExecuteAndGetResponseAndValues(); + + result!.StatusCode.Should().Be(200); + response.ContentType.Should().Be(MediaTypeNames.Text.EventStream); + values.Select(v => v.Data).Should().Contain(testValues.Select(v => v.ToString())); + values.Select(v => v.Event).Should().AllBe(eventType); + } + + [Fact] + public async Task ResultIAsyncEnumerableTE_Success_can_be_mapped_to_ServerSentEventsHttpResult_Async() + { + var testValues = new[] { 42, 420 }; + var eventType = "TestEvent"; + + var asyncEnumerable = testValues.AsAsyncEnumerable(); + + var result = + ( + await Task.FromResult(Result.Success, DocumentMissingError>(asyncEnumerable)) + .ToServerSentEventsHttpResult(eventType) + ).Result as ServerSentEventsResult; + + var (response, values) = await result!.ExecuteAndGetResponseAndValues(); + + result!.StatusCode.Should().Be(200); + response.ContentType.Should().Be(MediaTypeNames.Text.EventStream); + values.Select(v => v.Data).Should().Contain(testValues.Select(v => v.ToString())); + values.Select(v => v.Event).Should().AllBe(eventType); + } + + [Fact] + public void ResultIAsyncEnumerableTE_Failure_can_be_mapped_to_ServerSentEventsHttpResult() + { + var error = new DocumentMissingError { DocumentId = Guid.NewGuid().ToString() }; + + var result = + Result.Failure, DocumentMissingError>(error).ToServerSentEventsHttpResult().Result + as NotFound; + + result!.StatusCode.Should().Be(404); + result!.Value.Should().Be(error.DocumentId); + } + + [Fact] + public async Task ResultIAsyncEnumerableTE_Failure_can_be_mapped_to_ServerSentEventsHttpResult_Async() + { + var error = new DocumentMissingError { DocumentId = Guid.NewGuid().ToString() }; + + var result = + ( + await Task.FromResult(Result.Failure, DocumentMissingError>(error)) + .ToServerSentEventsHttpResult() + ).Result as NotFound; + + result!.StatusCode.Should().Be(404); + result!.Value.Should().Be(error.DocumentId); + } +} + +#endif diff --git a/CSharpFunctionalExtensions.HttpResults.Tests/Utils/IEnumerableExtensions.cs b/CSharpFunctionalExtensions.HttpResults.Tests/Utils/IEnumerableExtensions.cs new file mode 100644 index 0000000..cb5353b --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults.Tests/Utils/IEnumerableExtensions.cs @@ -0,0 +1,12 @@ +namespace CSharpFunctionalExtensions.HttpResults.Tests.Utils; + +public static class EnumerableExtensions +{ + public static async IAsyncEnumerable AsAsyncEnumerable(this IEnumerable items) + { + foreach (var item in items) + yield return item; + + await Task.CompletedTask; + } +} diff --git a/CSharpFunctionalExtensions.HttpResults.Tests/Utils/ServerSentEventsExtensions.cs b/CSharpFunctionalExtensions.HttpResults.Tests/Utils/ServerSentEventsExtensions.cs new file mode 100644 index 0000000..25b0c4e --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults.Tests/Utils/ServerSentEventsExtensions.cs @@ -0,0 +1,57 @@ +#if NET10_0_OR_GREATER + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.Extensions.DependencyInjection; + +namespace CSharpFunctionalExtensions.HttpResults.Tests.Utils; + +public static class ServerSentEventsExtensions +{ + public static async Task<( + HttpResponse Response, + IReadOnlyList Values + )> ExecuteAndGetResponseAndValues(this ServerSentEventsResult result) + { + var httpContext = new DefaultHttpContext + { + Response = { Body = new MemoryStream() }, + RequestServices = new ServiceCollection().BuildServiceProvider(), + }; + await result.ExecuteAsync(httpContext); + httpContext.Response.Body.Seek(0, SeekOrigin.Begin); + + var body = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); + + var values = body.Split("\n\n", StringSplitOptions.RemoveEmptyEntries) + .Select(block => + { + var lines = block.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + string? eventType = null; + var data = string.Empty; + + foreach (var line in lines) + { + if (line.StartsWith("event: ")) + eventType = line["event: ".Length..].Trim(); + else if (line.StartsWith("data: ")) + data = line["data: ".Length..].Trim(); + } + + return new EventMessage { Event = eventType, Data = data }; + }) + .ToList() + .AsReadOnly(); + + return (httpContext.Response, values); + } + + public record EventMessage + { + public string? Event { get; init; } + public string Data { get; init; } + } +} + +#endif diff --git a/CSharpFunctionalExtensions.HttpResults/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs b/CSharpFunctionalExtensions.HttpResults/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs new file mode 100644 index 0000000..bcae8ba --- /dev/null +++ b/CSharpFunctionalExtensions.HttpResults/ResultExtensions/ToServerSentEventsHttpResultIAsyncEnumerableT.cs @@ -0,0 +1,56 @@ +#if NET10_0_OR_GREATER + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; + +namespace CSharpFunctionalExtensions.HttpResults.ResultExtensions; + +/// +/// Extension methods for +/// +public static partial class ResultExtensions +{ + /// + /// Returns a based of a in case of success + /// result. Returns in case of failure. You can override the error status code. + /// + public static Results, ProblemHttpResult> ToServerSentEventsHttpResult( + this Result> result, + string? eventType = null, + int failureStatusCode = 400, + Action? customizeProblemDetails = null + ) + { + if (result.IsSuccess) + return TypedResults.ServerSentEvents(result.Value, eventType); + + var problemDetailsInfo = ProblemDetailsMappingProvider.FindMapping(failureStatusCode); + var problemDetails = new ProblemDetails + { + Status = failureStatusCode, + Title = problemDetailsInfo.Title, + Type = problemDetailsInfo.Type, + Detail = result.Error, + }; + + customizeProblemDetails?.Invoke(problemDetails); + + return TypedResults.Problem(problemDetails); + } + + /// + /// Returns a based of a in case of success + /// result. Returns in case of failure. You can override the error status code. + /// + public static async Task, ProblemHttpResult>> ToServerSentEventsHttpResult( + this Task>> result, + string? eventType = null, + int failureStatusCode = 400, + Action? customizeProblemDetails = null + ) + { + return (await result).ToServerSentEventsHttpResult(eventType, failureStatusCode, customizeProblemDetails); + } +} +#endif diff --git a/README.md b/README.md index a69afbe..182927f 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,13 @@ Seamlessly map Results from [CSharpFunctionalExtensions](https://github.com/vkho 1. [Overview](#overview) 2. [Installation](#installation) 3. [Usage](#usage) - 1. [Available methods](#available-methods) - 2. [Default mapping](#default-mapping) - 3. [Custom error mapping](#custom-error-mapping) +1. [Available methods](#available-methods) +2. [Default mapping](#default-mapping) +3. [Custom error mapping](#custom-error-mapping) 4. [Analyzers](#analyzers) 5. [Examples](#examples) 6. [Development](#development) + ## Overview @@ -66,6 +67,7 @@ This library provides you extension methods to map the following `Result` types - `UnitResult` *Example:* + ```csharp app.MapGet("/books", (BookService service) => service.Get() //Result @@ -78,34 +80,37 @@ app.MapGet("/books", (BookService service) =>
Click here to view all available methods. -| Method | Short Description | -|---------------------------------------|------------------------------------------------------------------------------| -| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or `ProblemHttpResult` | -| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or `ProblemHttpResult` | -| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or custom error | -| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or custom error | -| `.ToJsonHttpResult()` | Returns `JsonHttpResult` or `ProblemHttpResult` | -| `.ToJsonHttpResult()` | Returns `JsonHttpResult` or custom error | -| `.ToOkHttpResult()` | Returns `Ok` or `ProblemHttpResult` | -| `.ToOkHttpResult()` | Returns `Ok` or custom error | -| `.ToNoContentHttpResult()` | Returns `NoContent` or `ProblemHttpResult` | -| `.ToNoContentHttpResult()` | Discards value of `Result` and returns `NoContent` or `ProblemHttpResult` | -| `.ToNoContentHttpResult()` | Discards value of `Result` and returns `NoContent` or custom error | -| `.ToNoContentHttpResult()` | Returns `NoContent` or custom error | -| `.ToCreatedHttpResult()` | Returns `Created` or `ProblemHttpResult` | -| `.ToCreatedHttpResult()` | Returns `Created` or custom error | -| `.ToCreatedAtRouteHttpResult()` | Returns `CreatedAtRoute` or `ProblemHttpResult` | -| `.ToCreatedAtRouteHttpResult()` | Returns `CreatedAtRoute` or custom error | -| `.ToAcceptedHttpResult()` | Returns `Accepted` or `ProblemHttpResult` | -| `.ToAcceptedHttpResult()` | Returns `Accepted` or custom error | -| `.ToAcceptedAtRouteHttpResult()` | Returns `AcceptedAtRoute` or `ProblemHttpResult` | -| `.ToAcceptedAtRouteHttpResult()` | Returns `AcceptedAtRoute` or custom error | -| `.ToFileHttpResult()` | Returns `FileContentHttpResult` or `ProblemHttpResult` | -| `.ToFileHttpResult()` | Returns `FileContentHttpResult` or custom error | -| `.ToFileStreamHttpResult()` | Returns `FileStreamHttpResult` or `ProblemHttpResult` | -| `.ToFileStreamHttpResult()` | Returns `FileStreamHttpResult` or custom error | -| `.ToContentHttpResult()` | Returns `ContentHttpResult` or `ProblemHttpResult` | -| `.ToContentHttpResult()` | Returns `ContentHttpResult` or custom error | +| Method | Short Description | +|----------------------------------------------------------|----------------------------------------------------------------------------------| +| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or `ProblemHttpResult` | +| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or `ProblemHttpResult` | +| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or custom error | +| `.ToStatusCodeHttpResult()` | Returns `StatusCodeHttpResult` or custom error | +| `.ToJsonHttpResult()` | Returns `JsonHttpResult` or `ProblemHttpResult` | +| `.ToJsonHttpResult()` | Returns `JsonHttpResult` or custom error | +| `.ToOkHttpResult()` | Returns `Ok` or `ProblemHttpResult` | +| `.ToOkHttpResult()` | Returns `Ok` or custom error | +| `.ToNoContentHttpResult()` | Returns `NoContent` or `ProblemHttpResult` | +| `.ToNoContentHttpResult()` | Discards value of `Result` and returns `NoContent` or `ProblemHttpResult` | +| `.ToNoContentHttpResult()` | Discards value of `Result` and returns `NoContent` or custom error | +| `.ToNoContentHttpResult()` | Returns `NoContent` or custom error | +| `.ToCreatedHttpResult()` | Returns `Created` or `ProblemHttpResult` | +| `.ToCreatedHttpResult()` | Returns `Created` or custom error | +| `.ToCreatedAtRouteHttpResult()` | Returns `CreatedAtRoute` or `ProblemHttpResult` | +| `.ToCreatedAtRouteHttpResult()` | Returns `CreatedAtRoute` or custom error | +| `.ToAcceptedHttpResult()` | Returns `Accepted` or `ProblemHttpResult` | +| `.ToAcceptedHttpResult()` | Returns `Accepted` or custom error | +| `.ToAcceptedAtRouteHttpResult()` | Returns `AcceptedAtRoute` or `ProblemHttpResult` | +| `.ToAcceptedAtRouteHttpResult()` | Returns `AcceptedAtRoute` or custom error | +| `.ToFileHttpResult()` | Returns `FileContentHttpResult` or `ProblemHttpResult` | +| `.ToFileHttpResult()` | Returns `FileContentHttpResult` or custom error | +| `.ToFileStreamHttpResult()` | Returns `FileStreamHttpResult` or `ProblemHttpResult` | +| `.ToFileStreamHttpResult()` | Returns `FileStreamHttpResult` or custom error | +| `.ToContentHttpResult()` | Returns `ContentHttpResult` or `ProblemHttpResult` | +| `.ToContentHttpResult()` | Returns `ContentHttpResult` or custom error | +| `.ToServerSentEventsHttpResult>()` | Returns `ServerSentEventsResult` or `ProblemHttpResult` (requires >= .NET 10) | +| `.ToServerSentEventsHttpResult,E>()` | Returns `ServerSentEventsResult` or custom error (requires >= .NET 10) | +
All methods are available in sync and async variants. diff --git a/global.json b/global.json index 6a6aeda..69e101b 100644 --- a/global.json +++ b/global.json @@ -4,4 +4,4 @@ "rollForward": "latestMinor", "allowPrerelease": false } -} +} \ No newline at end of file