Skip to content

Commit

Permalink
bring back support for dynamics via Flurl.Http.Newtonsoft
Browse files Browse the repository at this point in the history
  • Loading branch information
Todd committed Dec 5, 2023
1 parent 2599242 commit 3ef55a3
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 31 deletions.
6 changes: 4 additions & 2 deletions src/Flurl.CodeGen/HttpExtensionMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ 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<dynamic>",
"String" => "string",
"Stream" => "Stream",
"Bytes" => "byte[]",
_ => "IFlurlResponse"
};

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",
Expand Down
7 changes: 7 additions & 0 deletions src/Flurl.CodeGen/Metadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ where IsSupportedCombo(verb, reqType, respType, extendedArg.Type)
let isGenenric = (respType == "Json")
select new HttpExtensionMethod(verb, isGenenric, reqType, respType) { ExtendedTypeArg = extendedArg };

/// <summary>
/// Additional HTTP-calling methods that return dynamic or IList&lt;dynamic&gt;, supported only with Flurl.Http.Newtonsoft.
/// </summary>
public static IEnumerable<HttpExtensionMethod> GetDynamicReturningExtensions(MethodArg extendedArg) =>
from respType in new[] { "Json", "JsonList" }
select new HttpExtensionMethod("Get", false, null, respType) { ExtendedTypeArg = extendedArg };

public static IEnumerable<ExtensionMethod> GetMiscAsyncExtensions(MethodArg extendedArg) {
// a couple oddballs

Expand Down
99 changes: 71 additions & 28 deletions src/Flurl.CodeGen/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("/// <summary>")
.WriteLine("/// Fluent extension methods on String, Url, Uri, and IFlurlRequest.")
.WriteLine("/// </summary>")
.WriteLine("public static class GeneratedExtensions")
.WriteLine("{");

WriteDynamicReturningMethods(writer);

writer
.WriteLine("}")
.WriteLine("}");
}

Console.WriteLine("File writing succeeded.");
return 0;
}
Expand Down Expand Up @@ -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<string>();
var genericArg = xm.IsGeneric ? "<T>" : "";

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<string>();
var genericArg = xm.IsGeneric ? "<T>" : "";

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};");
});
}
}
}
51 changes: 51 additions & 0 deletions src/Flurl.Http.Newtonsoft/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Threading.Tasks;

namespace Flurl.Http.Newtonsoft
{
/// <summary>
/// Extensions to Flurl objects that use Newtonsoft.Json.
/// </summary>
public static class ExtensionMethods
{
/// <summary>
/// Deserializes JSON-formatted HTTP response body to a dynamic object.
/// </summary>
/// <returns>A Task whose result is a dynamic object containing data in the response body.</returns>
public static async Task<dynamic> GetJsonAsync(this IFlurlResponse resp) {
dynamic d = await resp.GetJsonAsync<ExpandoObject>().ConfigureAwait(false);
return d;
}

/// <summary>
/// Deserializes JSON-formatted HTTP response body to a list of dynamic objects.
/// </summary>
/// <returns>A Task whose result is a list of dynamic objects containing data in the response body.</returns>
public static async Task<IList<dynamic>> GetJsonListAsync(this IFlurlResponse resp) {
dynamic[] d = await resp.GetJsonAsync<ExpandoObject[]>().ConfigureAwait(false);
return d;
}

/// <summary>
/// Deserializes JSON-formatted HTTP response body to a dynamic object. Intended to chain off an async call.
/// </summary>
/// <returns>A Task whose result is a dynamic object containing data in the response body.</returns>
public static async Task<dynamic> ReceiveJson(this Task<IFlurlResponse> response) {
using var resp = await response.ConfigureAwait(false);
if (resp == null) return null;
return await resp.GetJsonAsync().ConfigureAwait(false);
}

/// <summary>
/// Deserializes JSON-formatted HTTP response body to a list of dynamic objects. Intended to chain off an async call.
/// </summary>
/// <returns>A Task whose result is a list of dynamic objects containing data in the response body.</returns>
public static async Task<IList<dynamic>> ReceiveJsonList(this Task<IFlurlResponse> response) {
using var resp = await response.ConfigureAwait(false);
if (resp == null) return null;
return await resp.GetJsonListAsync().ConfigureAwait(false);
}
}
}
6 changes: 5 additions & 1 deletion src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0;net461</TargetFrameworks>
<LangVersion>9.0</LangVersion>
Expand Down Expand Up @@ -30,4 +30,8 @@
<ProjectReference Include="..\Flurl.Http\Flurl.Http.csproj" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net461'">
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
104 changes: 104 additions & 0 deletions src/Flurl.Http.Newtonsoft/GeneratedExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Fluent extension methods on String, Url, Uri, and IFlurlRequest.
/// </summary>
public static class GeneratedExtensions
{
/// <summary>
/// Sends an asynchronous GET request.
/// </summary>
/// <param name="request">This IFlurlRequest</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a dynamic.</returns>
public static Task<dynamic> GetJsonAsync(this IFlurlRequest request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return request.SendAsync(HttpMethod.Get, null, completionOption, cancellationToken).ReceiveJson();
}

/// <summary>
/// Sends an asynchronous GET request.
/// </summary>
/// <param name="request">This IFlurlRequest</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a list of dynamics.</returns>
public static Task<IList<dynamic>> GetJsonListAsync(this IFlurlRequest request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return request.SendAsync(HttpMethod.Get, null, completionOption, cancellationToken).ReceiveJsonList();
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="url">This Flurl.Url.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a dynamic.</returns>
public static Task<dynamic> GetJsonAsync(this Url url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(url).GetJsonAsync(completionOption, cancellationToken);
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="url">This Flurl.Url.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a list of dynamics.</returns>
public static Task<IList<dynamic>> GetJsonListAsync(this Url url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(url).GetJsonListAsync(completionOption, cancellationToken);
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="url">This URL.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a dynamic.</returns>
public static Task<dynamic> GetJsonAsync(this string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(url).GetJsonAsync(completionOption, cancellationToken);
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="url">This URL.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a list of dynamics.</returns>
public static Task<IList<dynamic>> GetJsonListAsync(this string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(url).GetJsonListAsync(completionOption, cancellationToken);
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="uri">This System.Uri.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a dynamic.</returns>
public static Task<dynamic> GetJsonAsync(this Uri uri, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(uri).GetJsonAsync(completionOption, cancellationToken);
}

/// <summary>
/// Creates a FlurlRequest and sends an asynchronous GET request.
/// </summary>
/// <param name="uri">This System.Uri.</param>
/// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A Task whose result is the JSON response body deserialized to a list of dynamics.</returns>
public static Task<IList<dynamic>> GetJsonListAsync(this Uri uri, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellationToken = default) {
return new FlurlRequest(uri).GetJsonListAsync(completionOption, cancellationToken);
}

}
}

0 comments on commit 3ef55a3

Please sign in to comment.