Skip to content

Commit

Permalink
Merge pull request #779 from tmenier/dev
Browse files Browse the repository at this point in the history
Flurl.Http.Newtonsoft
  • Loading branch information
tmenier authored Dec 5, 2023
2 parents b2a7b06 + 672c358 commit 73464bd
Show file tree
Hide file tree
Showing 15 changed files with 457 additions and 65 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

[*.csproj]
indent_style = space
indent_size = 2

# VS/.NET extensions
# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference

Expand Down
32 changes: 31 additions & 1 deletion .github/workflows/draft-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
run: dotnet build -c Release --no-restore

- name: Test
run: dotnet test --no-build -c Release /p:CollectCoverage=true /p:Threshold=80 /p:Include=\"[Flurl]*,[Flurl.Http]*\" /p:Exclude="[*]*.GeneratedExtensions"
run: dotnet test --no-build -c Release /p:CollectCoverage=true /p:Threshold=80 /p:Include=\"[Flurl]*,[Flurl.Http]*,[Flurl.Http.Newtonsoft]*\" /p:Exclude="[*]*.GeneratedExtensions"

# Compare version from csproj with latest release tag.
# If different, create a draft release.
Expand Down Expand Up @@ -57,12 +57,28 @@ jobs:
releases-only: true
regex: '^Flurl\.Http\.\d+'

- name: Get Flurl.Http.Newtonsoft csproj version
id: csproj_ver_flurl_newtonsoft
uses: KageKirin/[email protected]
with:
file: src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj

- name: Get Flurl.Http.Newtonsoft latest release tag
id: release_ver_flurl_newtonsoft
uses: oprypin/[email protected]
with:
repository: tmenier/Flurl
releases-only: true
regex: '^Flurl\.Http\.Newtonsoft\.\d+'

- name: Output versions
run: |
echo "Flurl csproj version: ${{ steps.csproj_ver_flurl.outputs.version }}"
echo "Flurl latest release tag: ${{ steps.release_ver_flurl.outputs.tag }}"
echo "Flurl.Http csproj version: ${{ steps.csproj_ver_flurl_http.outputs.version }}"
echo "Flurl.Http latest release tag: ${{ steps.release_ver_flurl_http.outputs.tag }}"
echo "Flurl.Http.Newtonsoft csproj version: ${{ steps.csproj_ver_flurl_newtonsoft.outputs.version }}"
echo "Flurl.Http.Newtonsoft latest release tag: ${{ steps.release_ver_flurl_newtonsoft.outputs.tag }}"
- name: Draft Flurl release
env:
Expand Down Expand Up @@ -91,3 +107,17 @@ jobs:
generateReleaseNotes: true
artifacts: "**/${{ env.NEXT_TAG }}.nupkg,**/${{ env.NEXT_TAG }}.snupkg"
draft: true

- name: Draft Flurl.Http.Newtonsoft release
env:
CURRENT_TAG: ${{ steps.release_ver_flurl_newtonsoft.outputs.tag }}
NEXT_TAG: "Flurl.Http.${{ steps.csproj_ver_flurl_newtonsoft.outputs.version }}"
RELEASE_NAME: "Flurl.Http ${{ steps.csproj_ver_flurl_newtonsoft.outputs.version }}"
if: env.NEXT_TAG != env.CURRENT_TAG
uses: ncipollo/release-action@v1
with:
name: ${{ env.RELEASE_NAME }}
tag: ${{ env.NEXT_TAG }}
generateReleaseNotes: true
artifacts: "**/${{ env.NEXT_TAG }}.nupkg,**/${{ env.NEXT_TAG }}.snupkg"
draft: true
7 changes: 7 additions & 0 deletions Flurl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flurl.Test", "test\Flurl.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flurl.CodeGen", "src\Flurl.CodeGen\Flurl.CodeGen.csproj", "{BE943E04-705F-42B1-BF95-A0642D9CA51D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flurl.Http.Newtonsoft", "src\Flurl.Http.Newtonsoft\Flurl.Http.Newtonsoft.csproj", "{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -36,6 +38,10 @@ Global
{BE943E04-705F-42B1-BF95-A0642D9CA51D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE943E04-705F-42B1-BF95-A0642D9CA51D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE943E04-705F-42B1-BF95-A0642D9CA51D}.Release|Any CPU.Build.0 = Release|Any CPU
{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -45,6 +51,7 @@ Global
{D7AC6172-73DD-468D-955A-3562F2BE303B} = {B82E8094-AFA9-466E-9E60-473B7B89AFE2}
{DF68EB0E-9566-4577-B709-291520383F8D} = {86A5ACB4-F3B3-4395-A5D5-924C9F35F628}
{BE943E04-705F-42B1-BF95-A0642D9CA51D} = {B82E8094-AFA9-466E-9E60-473B7B89AFE2}
{BDF18B21-6B7B-4945-BB86-9CB8A8B1E93A} = {B82E8094-AFA9-466E-9E60-473B7B89AFE2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {61289482-AC5A-44E1-AEA1-76A3F3CCB6A4}
Expand Down
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};");
});
}
}
}
71 changes: 71 additions & 0 deletions src/Flurl.Http.Newtonsoft/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Threading.Tasks;
using Flurl.Http.Configuration;
using Newtonsoft.Json;

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);
}

/// <summary>
/// Shortcut to use NewtonsoftJsonSerializer with this IFlurlClientBuilder.
/// </summary>
/// <param name="builder">This IFlurlClientBuilder.</param>
/// <param name="settings">Optional custom JsonSerializerSettings.</param>
/// <returns></returns>
public static IFlurlClientBuilder UseNewtonsoft(this IFlurlClientBuilder builder, JsonSerializerSettings settings = null) =>
builder.WithSettings(fs => fs.JsonSerializer = new NewtonsoftJsonSerializer(settings));

/// <summary>
/// Shortcut to use NewtonsoftJsonSerializer with all FlurlClients registered in this cache.
/// </summary>
/// <param name="cache">This IFlurlClientCache.</param>
/// <param name="settings">Optional custom JsonSerializerSettings.</param>
/// <returns></returns>
public static IFlurlClientCache UseNewtonsoft(this IFlurlClientCache cache, JsonSerializerSettings settings = null) =>
cache.WithDefaults(builder => builder.UseNewtonsoft(settings));
}
}
37 changes: 37 additions & 0 deletions src/Flurl.Http.Newtonsoft/Flurl.Http.Newtonsoft.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0;net461</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>Flurl.Http.Newtonsoft</PackageId>
<Version>0.9.0-pre1</Version>
<Authors>Todd Menier</Authors>
<Description>A Newtonsoft-based JSON serializer for Flurl.Http 4.0 and above.</Description>
<PackageProjectUrl>https://flurl.dev</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://github.com/tmenier/Flurl.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>flurl http json</PackageTags>
<PackageReleaseNotes>https://github.com/tmenier/Flurl/releases</PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath="\" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<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>
Loading

0 comments on commit 73464bd

Please sign in to comment.