From 3ef55a3406db3ab55db5b24630b0f416716b4049 Mon Sep 17 00:00:00 2001 From: Todd Date: Mon, 4 Dec 2023 19:57:57 -0600 Subject: [PATCH] bring back support for dynamics via Flurl.Http.Newtonsoft --- src/Flurl.CodeGen/HttpExtensionMethod.cs | 6 +- src/Flurl.CodeGen/Metadata.cs | 7 ++ src/Flurl.CodeGen/Program.cs | 99 ++++++++++++----- src/Flurl.Http.Newtonsoft/ExtensionMethods.cs | 51 +++++++++ .../Flurl.Http.Newtonsoft.csproj | 6 +- .../GeneratedExtensions.cs | 104 ++++++++++++++++++ 6 files changed, 242 insertions(+), 31 deletions(-) create mode 100644 src/Flurl.Http.Newtonsoft/ExtensionMethods.cs create mode 100644 src/Flurl.Http.Newtonsoft/GeneratedExtensions.cs diff --git a/src/Flurl.CodeGen/HttpExtensionMethod.cs b/src/Flurl.CodeGen/HttpExtensionMethod.cs index 2dbda5e7..e3dfd843 100644 --- a/src/Flurl.CodeGen/HttpExtensionMethod.cs +++ b/src/Flurl.CodeGen/HttpExtensionMethod.cs @@ -47,7 +47,8 @@ public HttpExtensionMethod(string verb, bool isGeneric, string reqBodyType, stri public string ResponseBodyType { get; } public string TaskArg => ResponseBodyType switch { - "Json" => "T", + "Json" => IsGeneric ? "T" : "dynamic", + "JsonList" => "IList", "String" => "string", "Stream" => "Stream", "Bytes" => "byte[]", @@ -55,7 +56,8 @@ public HttpExtensionMethod(string verb, bool isGeneric, string reqBodyType, stri }; public string ReturnTypeDescription => ResponseBodyType switch { - "Json" => "the JSON response body deserialized to an object of type T", + "Json" => "the JSON response body deserialized to " + (IsGeneric ? "an object of type T" : "a dynamic"), + "JsonList" => "the JSON response body deserialized to a list of dynamics", "String" => "the response body as a string", "Stream" => "the response body as a Stream", "Bytes" => "the response body as a byte array", diff --git a/src/Flurl.CodeGen/Metadata.cs b/src/Flurl.CodeGen/Metadata.cs index d2c0ecaa..6c80e567 100644 --- a/src/Flurl.CodeGen/Metadata.cs +++ b/src/Flurl.CodeGen/Metadata.cs @@ -158,6 +158,13 @@ where IsSupportedCombo(verb, reqType, respType, extendedArg.Type) let isGenenric = (respType == "Json") select new HttpExtensionMethod(verb, isGenenric, reqType, respType) { ExtendedTypeArg = extendedArg }; + /// + /// Additional HTTP-calling methods that return dynamic or IList<dynamic>, supported only with Flurl.Http.Newtonsoft. + /// + public static IEnumerable GetDynamicReturningExtensions(MethodArg extendedArg) => + from respType in new[] { "Json", "JsonList" } + select new HttpExtensionMethod("Get", false, null, respType) { ExtendedTypeArg = extendedArg }; + public static IEnumerable GetMiscAsyncExtensions(MethodArg extendedArg) { // a couple oddballs diff --git a/src/Flurl.CodeGen/Program.cs b/src/Flurl.CodeGen/Program.cs index 280d7957..0d3f1467 100644 --- a/src/Flurl.CodeGen/Program.cs +++ b/src/Flurl.CodeGen/Program.cs @@ -73,6 +73,37 @@ static int Main(string[] args) { .WriteLine("}"); } + path = codeRoot + @"\src\Flurl.Http.Newtonsoft\GeneratedExtensions.cs"; + if (!File.Exists(path)) { + ShowError("Code file not found: " + Path.GetFullPath(path)); + return 2; + } + + File.WriteAllText(path, ""); + using (var writer = new CodeWriter(path)) { + writer + .WriteLine("// This file was auto-generated by Flurl.CodeGen. Do not edit directly.") + .WriteLine("using System;") + .WriteLine("using System.Collections.Generic;") + .WriteLine("using System.Net.Http;") + .WriteLine("using System.Threading;") + .WriteLine("using System.Threading.Tasks;") + .WriteLine("") + .WriteLine("namespace Flurl.Http.Newtonsoft") + .WriteLine("{") + .WriteLine("/// ") + .WriteLine("/// Fluent extension methods on String, Url, Uri, and IFlurlRequest.") + .WriteLine("/// ") + .WriteLine("public static class GeneratedExtensions") + .WriteLine("{"); + + WriteDynamicReturningMethods(writer); + + writer + .WriteLine("}") + .WriteLine("}"); + } + Console.WriteLine("File writing succeeded."); return 0; } @@ -107,42 +138,54 @@ private static void WriteUrlBuilderExtensionMethods(CodeWriter writer) { private static void WriteHttpExtensionMethods(CodeWriter writer) { var reqArg = _extendedArgs[0]; - foreach (var xm in Metadata.GetHttpCallingExtensions(reqArg)) { - Console.WriteLine($"writing {xm.Name} for IFlurlRequest..."); - xm.Write(writer, () => { - var args = new List(); - var genericArg = xm.IsGeneric ? "" : ""; - - args.Add( - xm.HttpVerb == null ? "verb" : - xm.HttpVerb == "Patch" ? "new HttpMethod(\"PATCH\")" : // there's no HttpMethod.Patch - "HttpMethod." + xm.HttpVerb); - - args.Add(xm.HasRequestBody ? "content" : "null"); - args.Add("completionOption"); - args.Add("cancellationToken"); - - if (xm.RequestBodyType != null) { - writer.WriteLine("var content = new Captured@0Content(@1);", - xm.RequestBodyType, - xm.RequestBodyType == "String" ? "body" : $"request.Settings.{xm.RequestBodyType}Serializer.Serialize(body)"); - } - - var receive = (xm.ResponseBodyType != null) ? $".Receive{xm.ResponseBodyType}{genericArg}()" : ""; - writer.WriteLine($"return {reqArg.Name}.SendAsync({string.Join(", ", args)}){receive};"); - }); - } + foreach (var xm in Metadata.GetHttpCallingExtensions(reqArg)) + WriteHttpExtensionMethod(writer, xm); foreach (var xarg in _extendedArgs.Skip(1)) { // skip 1 because these don't apply to IFlurlRequest var all = Metadata.GetHttpCallingExtensions(xarg) .Concat(Metadata.GetMiscAsyncExtensions(xarg)) .Concat(Metadata.GetRequestReturningExtensions(xarg)); - foreach (var xm in all) { - Console.WriteLine($"writing {xm.Name} for {xarg.Type}..."); + foreach (var xm in all) xm.Write(writer, $"new FlurlRequest({xarg.Name})"); - } } } + + private static void WriteDynamicReturningMethods(CodeWriter writer) { + var reqArg = _extendedArgs[0]; + + foreach (var xm in Metadata.GetDynamicReturningExtensions(reqArg)) + WriteHttpExtensionMethod(writer, xm); + + foreach (var xarg in _extendedArgs.Skip(1)) { // skip 1 because these don't apply to IFlurlRequest + foreach (var xm in Metadata.GetDynamicReturningExtensions(xarg)) + xm.Write(writer, $"new FlurlRequest({xarg.Name})"); + } + } + + private static void WriteHttpExtensionMethod(CodeWriter writer, HttpExtensionMethod xm) { + xm.Write(writer, () => { + var args = new List(); + var genericArg = xm.IsGeneric ? "" : ""; + + args.Add( + xm.HttpVerb == null ? "verb" : + xm.HttpVerb == "Patch" ? "new HttpMethod(\"PATCH\")" : // there's no HttpMethod.Patch + "HttpMethod." + xm.HttpVerb); + + args.Add(xm.HasRequestBody ? "content" : "null"); + args.Add("completionOption"); + args.Add("cancellationToken"); + + if (xm.RequestBodyType != null) { + writer.WriteLine("var content = new Captured@0Content(@1);", + xm.RequestBodyType, + xm.RequestBodyType == "String" ? "body" : $"request.Settings.{xm.RequestBodyType}Serializer.Serialize(body)"); + } + + var receive = (xm.ResponseBodyType != null) ? $".Receive{xm.ResponseBodyType}{genericArg}()" : ""; + writer.WriteLine($"return request.SendAsync({string.Join(", ", args)}){receive};"); + }); + } } } \ No newline at end of file diff --git a/src/Flurl.Http.Newtonsoft/ExtensionMethods.cs b/src/Flurl.Http.Newtonsoft/ExtensionMethods.cs new file mode 100644 index 00000000..4e184ebd --- /dev/null +++ b/src/Flurl.Http.Newtonsoft/ExtensionMethods.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Threading.Tasks; + +namespace Flurl.Http.Newtonsoft +{ + /// + /// Extensions to Flurl objects that use Newtonsoft.Json. + /// + public static class ExtensionMethods + { + /// + /// Deserializes JSON-formatted HTTP response body to a dynamic object. + /// + /// A Task whose result is a dynamic object containing data in the response body. + public static async Task GetJsonAsync(this IFlurlResponse resp) { + dynamic d = await resp.GetJsonAsync().ConfigureAwait(false); + return d; + } + + /// + /// Deserializes JSON-formatted HTTP response body to a list of dynamic objects. + /// + /// A Task whose result is a list of dynamic objects containing data in the response body. + public static async Task> GetJsonListAsync(this IFlurlResponse resp) { + dynamic[] d = await resp.GetJsonAsync().ConfigureAwait(false); + return d; + } + + /// + /// Deserializes JSON-formatted HTTP response body to a dynamic object. Intended to chain off an async call. + /// + /// A Task whose result is a dynamic object containing data in the response body. + public static async Task ReceiveJson(this Task response) { + using var resp = await response.ConfigureAwait(false); + if (resp == null) return null; + return await resp.GetJsonAsync().ConfigureAwait(false); + } + + /// + /// Deserializes JSON-formatted HTTP response body to a list of dynamic objects. Intended to chain off an async call. + /// + /// A Task whose result is a list of dynamic objects containing data in the response body. + public static async Task> ReceiveJsonList(this Task response) { + using var resp = await response.ConfigureAwait(false); + if (resp == null) return null; + return await resp.GetJsonListAsync().ConfigureAwait(false); + } + } +} diff --git a/src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj b/src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj index 9c87c813..8621527c 100644 --- a/src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj +++ b/src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj @@ -1,4 +1,4 @@ - + net6.0;netstandard2.0;net461 9.0 @@ -30,4 +30,8 @@ + + + + diff --git a/src/Flurl.Http.Newtonsoft/GeneratedExtensions.cs b/src/Flurl.Http.Newtonsoft/GeneratedExtensions.cs new file mode 100644 index 00000000..547fba68 --- /dev/null +++ b/src/Flurl.Http.Newtonsoft/GeneratedExtensions.cs @@ -0,0 +1,104 @@ +// This file was auto-generated by Flurl.CodeGen. Do not edit directly. +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Flurl.Http.Newtonsoft +{ + /// + /// Fluent extension methods on String, Url, Uri, and IFlurlRequest. + /// + public static class GeneratedExtensions + { + /// + /// Sends an asynchronous GET request. + /// + /// This IFlurlRequest + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a dynamic. + public static Task GetJsonAsync(this IFlurlRequest request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return request.SendAsync(HttpMethod.Get, null, completionOption, cancellationToken).ReceiveJson(); + } + + /// + /// Sends an asynchronous GET request. + /// + /// This IFlurlRequest + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a list of dynamics. + public static Task> GetJsonListAsync(this IFlurlRequest request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return request.SendAsync(HttpMethod.Get, null, completionOption, cancellationToken).ReceiveJsonList(); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This Flurl.Url. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a dynamic. + public static Task GetJsonAsync(this Url url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(url).GetJsonAsync(completionOption, cancellationToken); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This Flurl.Url. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a list of dynamics. + public static Task> GetJsonListAsync(this Url url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(url).GetJsonListAsync(completionOption, cancellationToken); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This URL. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a dynamic. + public static Task GetJsonAsync(this string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(url).GetJsonAsync(completionOption, cancellationToken); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This URL. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a list of dynamics. + public static Task> GetJsonListAsync(this string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(url).GetJsonListAsync(completionOption, cancellationToken); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This System.Uri. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a dynamic. + public static Task GetJsonAsync(this Uri uri, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(uri).GetJsonAsync(completionOption, cancellationToken); + } + + /// + /// Creates a FlurlRequest and sends an asynchronous GET request. + /// + /// This System.Uri. + /// The HttpCompletionOption used in the request. Optional. + /// The token to monitor for cancellation requests. + /// A Task whose result is the JSON response body deserialized to a list of dynamics. + public static Task> GetJsonListAsync(this Uri uri, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) { + return new FlurlRequest(uri).GetJsonListAsync(completionOption, cancellationToken); + } + + } +}