Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BananaCakePop Package #6198

Merged
merged 14 commits into from
Jul 14, 2023
47 changes: 47 additions & 0 deletions .build/Build.BananaCakePop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using NuGet.Versioning;
using Nuke.Common;
using Serilog;

partial class Build
{
Target UpdateBananaCakePop => _ => _
.Executes(async () =>
{
var latestVersion = await GetLatestVersion("BananaCakePop.Middleware");
Log.Information($"Latest BCP Version: {latestVersion}");

var project = Project.FromFile(HotChocolateDirectoryBuildProps, new ProjectOptions());
project.SetProperty("BananaCakePopVersion", latestVersion);
project.Save();
});

static async Task<string> GetLatestVersion(string packageName)
{
using var client = new HttpClient();

var url = $"https://api.nuget.org/v3-flatcontainer/{packageName.ToLower()}/index.json";

var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
return JsonDocument.Parse(jsonString)
.RootElement.GetProperty("versions")
.EnumerateArray()
.Select(x => x.GetString())
.Where(x => !x.Contains("insider"))
.OrderByDescending(x => SemanticVersion.Parse(x))
.First();
}

throw new Exception($"Failed to retrieve package data: {response.StatusCode}");
}
}
2 changes: 2 additions & 0 deletions .build/Build.Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ partial class Build

AbsolutePath StarWarsTemplateNuSpec => RootDirectory / "templates" / "StarWars" / "HotChocolate.Templates.StarWars.nuspec";

AbsolutePath HotChocolateDirectoryBuildProps => SourceDirectory / "HotChocolate" / "Directory.Build.Props";

AbsolutePath StarWarsProj => RootDirectory / "templates" / "StarWars" / "content" / "StarWars.csproj";
AbsolutePath EmptyServerTemplateNuSpec => RootDirectory / "templates" / "Server" / "HotChocolate.Templates.Server.nuspec";
AbsolutePath EmptyServerProj => RootDirectory / "templates" / "Server" / "content" / "HotChocolate.Server.Template.csproj";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.FileProviders;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Extensions;
using BananaCakePop.Middleware;
using static HotChocolate.AspNetCore.MiddlewareRoutingType;
using static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory;

Expand Down Expand Up @@ -77,18 +77,15 @@ public static GraphQLEndpointConventionBuilder MapGraphQL(
var pattern = Parse(path + "/{**slug}");
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
var schemaNameOrDefault = schemaName ?? Schema.DefaultName;
var fileProvider = CreateFileProvider();

requestPipeline
.UseCancellation()
.UseMiddleware<WebSocketSubscriptionMiddleware>(schemaNameOrDefault)
.UseMiddleware<HttpPostMiddleware>(schemaNameOrDefault)
.UseMiddleware<HttpMultipartMiddleware>(schemaNameOrDefault)
.UseMiddleware<HttpGetSchemaMiddleware>(schemaNameOrDefault, Integrated)
.UseMiddleware<ToolDefaultFileMiddleware>(fileProvider, path)
.UseMiddleware<ToolOptionsFileMiddleware>(path)
.UseMiddleware<ToolStaticFileMiddleware>(fileProvider, path)
.UseMiddleware<HttpGetMiddleware>(schemaNameOrDefault, path)
.UseMiddleware<HttpGetSchemaMiddleware>(schemaNameOrDefault, Integrated)
.UseBananaCakePop(path)
.Use(_ => context =>
{
context.Response.StatusCode = 404;
Expand Down Expand Up @@ -323,9 +320,7 @@ public static IEndpointConventionBuilder MapGraphQLSchema(

requestPipeline
.UseCancellation()
.UseMiddleware<HttpGetSchemaMiddleware>(
schemaNameOrDefault,
Explicit)
.UseMiddleware<HttpGetSchemaMiddleware>(schemaNameOrDefault, Explicit)
.Use(_ => context =>
{
context.Response.StatusCode = 404;
Expand Down Expand Up @@ -391,13 +386,9 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(

var pattern = Parse(toolPath + "/{**slug}");
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
var fileProvider = CreateFileProvider();

requestPipeline
.UseCancellation()
.UseMiddleware<ToolDefaultFileMiddleware>(fileProvider, toolPath)
.UseMiddleware<ToolOptionsFileMiddleware>(toolPath)
.UseMiddleware<ToolStaticFileMiddleware>(fileProvider, toolPath)
.UseBananaCakePop(toolPath)
.Use(_ => context =>
{
context.Response.StatusCode = 404;
Expand All @@ -407,7 +398,7 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(
var builder = endpointRouteBuilder
.Map(pattern, requestPipeline.Build())
.WithDisplayName("Banana Cake Pop Pipeline")
.WithMetadata(new GraphQLEndpointOptions { GraphQLEndpoint = relativeRequestPath });
.WithMetadata(new BananaCakePopOptions { GraphQLEndpoint = relativeRequestPath });

return new BananaCakePopEndpointConventionBuilder(builder);
}
Expand All @@ -428,7 +419,7 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(
public static GraphQLEndpointConventionBuilder WithOptions(
this GraphQLEndpointConventionBuilder builder,
GraphQLServerOptions serverOptions) =>
builder.WithMetadata(serverOptions);
builder.WithMetadata(serverOptions).WithMetadata(From(serverOptions.Tool));

/// <summary>
/// Specifies the GraphQL HTTP request options.
Expand Down Expand Up @@ -468,8 +459,26 @@ public static GraphQLHttpEndpointConventionBuilder WithOptions(
/// </returns>
public static BananaCakePopEndpointConventionBuilder WithOptions(
this BananaCakePopEndpointConventionBuilder builder,
GraphQLToolOptions toolOptions) =>
builder.WithMetadata(new GraphQLServerOptions { Tool = toolOptions });
GraphQLToolOptions toolOptions)
{
builder.Add(convention =>
{
// we remove the previous options because we want to replace them
var previousOptions = convention.Metadata.OfType<BananaCakePopOptions>().First();
convention.Metadata.Remove(previousOptions);

var newOptions = From(toolOptions);

// we override the GraphQLEndpoint in case it was set through
// GraphQLEndpointOptions
if (newOptions.GraphQLEndpoint is null)
{
newOptions.GraphQLEndpoint = previousOptions.GraphQLEndpoint;
}
});

return builder;
}

/// <summary>
/// Specifies the GraphQL over Websocket options.
Expand All @@ -489,13 +498,6 @@ public static WebSocketEndpointConventionBuilder WithOptions(
GraphQLSocketOptions socketOptions) =>
builder.WithMetadata(new GraphQLServerOptions { Sockets = socketOptions });

private static IFileProvider CreateFileProvider()
{
var type = typeof(EndpointRouteBuilderExtensions);
var resourceNamespace = typeof(MiddlewareBase).Namespace + ".Resources";
return new EmbeddedFileProvider(type.Assembly, resourceNamespace);
}

private static IApplicationBuilder UseCancellation(this IApplicationBuilder builder)
=> builder.Use(next => async context =>
{
Expand All @@ -508,4 +510,20 @@ private static IApplicationBuilder UseCancellation(this IApplicationBuilder buil
// we just catch cancellations here and do nothing.
}
});

private static BananaCakePopOptions From(GraphQLToolOptions options)
=> new()
{
ServeMode = ServeMode.Version(options.ServeMode.Mode),
Title = options.Title,
Document = options.Document,
UseBrowserUrlAsGraphQLEndpoint = options.UseBrowserUrlAsGraphQLEndpoint,
GraphQLEndpoint = options.GraphQLEndpoint,
IncludeCookies = options.IncludeCookies,
HttpHeaders = options.HttpHeaders,
UseGet = options.HttpMethod == DefaultHttpMethod.Get,
Enable = options.Enable,
GaTrackingId = options.GaTrackingId,
DisableTelemetry = options.DisableTelemetry,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace HotChocolate.AspNetCore;
/// </summary>
public sealed class GraphQLToolOptions
{
/// <inheritdoc cref="GraphQLToolServeMode"/>
public GraphQLToolServeMode ServeMode { get; set; } = GraphQLToolServeMode.Latest;

/// <summary>
/// Gets or sets the website title.
/// </summary>
Expand Down
102 changes: 102 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLToolServeMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using BananaCakePop.Middleware;

namespace HotChocolate.AspNetCore;

/// <summary>
/// Represents the different modes of serving the Banana Cake Pop GraphQL tool. This class enables
/// serving the tool in a variety of predefined ways:
/// <list type="bullet">
/// <item>
/// <description>
/// <see cref="GraphQLToolServeMode.Latest"/>: Uses the latest version of the tool, served over the
/// cdn.
/// </description>
/// </item>
/// <item>
/// <description>
/// <see cref="GraphQLToolServeMode.Insider"/>: Uses the insider version of the tool, served over
/// the CDN.
/// </description>
/// </item>
/// <item>
/// <description>
/// <see cref="GraphQLToolServeMode.Embedded"/>: Uses the tool's embedded files in the package.
/// </description>
/// </item>
/// </list>
/// In addition, a specific version of the tool can be served over the CDN using the
/// <see cref="GraphQLToolServeMode.Version(string)"/> method.
/// <example>
/// <para>
/// The following example shows how to serve the embedded version of the tool:
/// </para>
/// <code>
/// endpoints
/// .MapGraphQL()
/// .WithOptions(new GraphQLServerOptions
/// {
/// Tool = { ServeMode = GraphQLToolServeMode.Embedded }
/// });
/// </code>
/// <para>
/// Or when you want to serve the insider version of the tool:
/// </para>
/// <code>
/// endpoints
/// .MapGraphQL()
/// .WithOptions(new GraphQLServerOptions
/// {
/// Tool = { ServeMode = GraphQLToolServeMode.Insider }
/// });
/// </code>
/// <para>
/// Or when you want to serve a specific version of the tool:
/// </para>
/// <code>
/// endpoints
/// .MapGraphQL()
/// .WithOptions(new GraphQLServerOptions
/// {
/// Tool = { ServeMode = GraphQLToolServeMode.Version("5.0.8") }
/// });
/// </code>
/// </example>
/// </summary>
public sealed class GraphQLToolServeMode
{
/// <summary>
/// Initializes a new instance of the <see cref="GraphQLToolServeMode"/> class using the
/// provided mode.
/// </summary>
/// <param name="mode">The mode to serve the GraphQL tool.</param>
private GraphQLToolServeMode(string mode) { Mode = mode; }

/// <summary>
/// Gets the current mode of serving the GraphQL tool.
/// </summary>
internal string Mode { get; }

/// <summary>
/// Serves the GraphQL tool using the latest version available over the CDN.
/// </summary>
public static readonly GraphQLToolServeMode Latest = new(ServeMode.Constants.Latest);

/// <summary>
/// Serves the GraphQL tool using the insider version available over the CDN.
/// </summary>
public static readonly GraphQLToolServeMode Insider = new(ServeMode.Constants.Insider);

/// <summary>
/// Serves the GraphQL tool using the embedded files from the package.
/// </summary>
public static readonly GraphQLToolServeMode Embedded = new(ServeMode.Constants.Embedded);

/// <summary>
/// Serves the GraphQL tool from a specific version available over the CDN.
/// </summary>
/// <param name="version">The version of the tool to serve.</param>
/// <returns>
/// A new <see cref="GraphQLToolServeMode"/> object for serving the specific version.
/// </returns>
public static GraphQLToolServeMode Version(string version) => new(version);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<ProjectReference Include="..\Transport.Sockets\HotChocolate.Transport.Sockets.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="BananaCakePop.Middleware" Version="$(BananaCakePopVersion)" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,4 @@ await WriteResultAsync(
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,4 @@ private IReadOnlyList<GraphQLRequest> ParseQuery(

return new[] { new GraphQLRequest(document, queryHash) };
}
}
}
Loading