Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ChilliCream/graphql-platform
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 11.2.2
Choose a base ref
...
head repository: ChilliCream/graphql-platform
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 11.3.0
Choose a head ref

Commits on Apr 22, 2021

  1. Copy the full SHA
    19cfba2 View commit details
  2. Copy the full SHA
    4b9be94 View commit details
  3. Copy the full SHA
    bbdfe71 View commit details
  4. Copy the full SHA
    0995368 View commit details
  5. Copy the full SHA
    1de46c1 View commit details

Commits on Apr 26, 2021

  1. Copy the full SHA
    3351ff9 View commit details
  2. Copy the full SHA
    4fd7f7c View commit details
  3. Copy the full SHA
    48570d7 View commit details
  4. Copy the full SHA
    89a07cf View commit details
  5. Copy the full SHA
    1285f1c View commit details

Commits on Apr 27, 2021

  1. Copy the full SHA
    c774d90 View commit details
  2. Copy the full SHA
    79c372d View commit details
  3. Copy the full SHA
    6617ec6 View commit details

Commits on Apr 28, 2021

  1. Copy the full SHA
    7156ab0 View commit details
  2. Copy the full SHA
    99fa769 View commit details
  3. Copy the full SHA
    ac6d865 View commit details
  4. Fixed memory leaks in subscriptions (#3608)

    Co-authored-by: Michael Staib <michael@chillicream.com>
    akolpachev and michaelstaib authored Apr 28, 2021
    Copy the full SHA
    6a79394 View commit details

Commits on Apr 30, 2021

  1. Fixed introspection utils to support that IsRepeatable is not being s…

    …pecified on directives (#3629)
    nloum authored Apr 30, 2021
    Copy the full SHA
    f5f4019 View commit details
  2. Copy the full SHA
    021e8a6 View commit details

Commits on May 1, 2021

  1. Updated Slack Link

    michaelstaib committed May 1, 2021
    Copy the full SHA
    3a0f369 View commit details

Commits on May 2, 2021

  1. Copy the full SHA
    49ab59b View commit details

Commits on May 4, 2021

  1. Fixes the error that caused the side not being scrollable #3415 (#3658)

    Co-authored-by: Frederic Birke <fred@freds.garden>
    rstaib and fredericbirke authored May 4, 2021
    Copy the full SHA
    476d6d6 View commit details
  2. Fixed window undefined

    rstaib committed May 4, 2021
    Copy the full SHA
    80b8efb View commit details
  3. Copy the full SHA
    a5ab95e View commit details

Commits on May 6, 2021

  1. Copy the full SHA
    7ad9d0c View commit details
  2. Copy the full SHA
    e312179 View commit details
  3. Copy the full SHA
    80bcc20 View commit details
  4. Copy the full SHA
    8836a90 View commit details
  5. Copy the full SHA
    a6e45fb View commit details
  6. Updated Slack URL

    michaelstaib committed May 6, 2021
    Copy the full SHA
    1575660 View commit details
  7. Copy the full SHA
    55b5660 View commit details

Commits on May 9, 2021

  1. Add Latitude Scalar (#3493)

    Adds Latitude Scalar
    wonbyte authored May 9, 2021
    Copy the full SHA
    2d119d6 View commit details

Commits on May 10, 2021

  1. Add Longitude scalar (#3535)

    Co-authored-by: PascalSenn <senn.pasc@gmail.com>
    wonbyte and PascalSenn authored May 10, 2021
    Copy the full SHA
    c7e90ef View commit details

Commits on May 13, 2021

  1. Copy the full SHA
    7bcbb08 View commit details
  2. Copy the full SHA
    d6aef6e View commit details

Commits on May 14, 2021

  1. Copy the full SHA
    cddbc4f View commit details

Commits on May 15, 2021

  1. Copy the full SHA
    59908cd View commit details

Commits on May 17, 2021

  1. Fixed website scroll perf issue (#3698)

    * Added dead link analysis
    
    * Fixed scroll perf issues
    
    * Fixed a link
    rstaib authored May 17, 2021
    Copy the full SHA
    32a48c5 View commit details
  2. Copy the full SHA
    0ccd318 View commit details
  3. Copy the full SHA
    82bc367 View commit details

Commits on May 20, 2021

  1. Copy the full SHA
    4dc4132 View commit details
  2. Copy the full SHA
    8b204f7 View commit details

Commits on May 22, 2021

  1. Copy the full SHA
    00931b1 View commit details

Commits on May 31, 2021

  1. Copy the full SHA
    ff900c1 View commit details
  2. Adds new pagination helper to main (#3733)

    Co-authored-by: matthewoconnell <matthew.n.oconnell@gmail.com>
    PascalSenn and moconnell authored May 31, 2021
    Copy the full SHA
    7b27488 View commit details

Commits on Jun 1, 2021

  1. Copy the full SHA
    7c1c9f4 View commit details

Commits on Jun 2, 2021

  1. Copy the full SHA
    2d33a7f View commit details
  2. Update v11.1 label to just v11 in docs menu (#3780)

    * Update docs.json
    
    * Remove 11.1 from WIP banner
    
    * Minor changes
    
    Co-authored-by: Tobias Tengler <45513122+tobias-tengler@users.noreply.github.com>
    benmccallum and tobias-tengler authored Jun 2, 2021
    Copy the full SHA
    d88c81d View commit details

Commits on Jun 3, 2021

  1. Copy the full SHA
    4d8ddbb View commit details

Commits on Jun 8, 2021

  1. Copy the full SHA
    7770890 View commit details
Showing 479 changed files with 29,381 additions and 5,067 deletions.
1 change: 1 addition & 0 deletions .build/Build.Environment.cs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ partial class Build : NukeBuild
AbsolutePath AllSolutionFile => SourceDirectory / "All.sln";
AbsolutePath SonarSolutionFile => SourceDirectory / "Sonar.sln";
AbsolutePath TestSolutionFile => TemporaryDirectory / "All.Test.sln";
AbsolutePath PackSolutionFile => SourceDirectory / "All.Pack.sln";
AbsolutePath SgSolutionFile => SourceDirectory / "StrawberryShake" / "SourceGenerator" / "StrawberryShake.SourceGenerator.sln";

AbsolutePath OutputDirectory => RootDirectory / "output";
177 changes: 177 additions & 0 deletions .build/Build.PublicApiAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System.IO;
using System.Linq;
using Colorful;
using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.Git.GitTasks;
using static Helpers;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using System;
using System.Drawing;

partial class Build : NukeBuild
{
private readonly string _shippedApiFile = "PublicAPI.Shipped.txt";
private readonly string _unshippedApiFile = "PublicAPI.Unshipped.txt";
private readonly string _removedApiPrefix = "*REMOVED*";

[Parameter] readonly string From;
[Parameter] readonly string To;
[Parameter] readonly bool Breaking;

Target CheckPublicApi => _ => _
.DependsOn(Restore)
.Executes(() =>
{
if (!InvokedTargets.Contains(Restore))
{
DotNetBuildSonarSolution(AllSolutionFile);
}

DotNetBuild(c => c
.SetProjectFile(AllSolutionFile)
.SetNoRestore(InvokedTargets.Contains(Restore))
.SetConfiguration(Configuration)
.SetProperty("RequireDocumentationOfPublicApiChanges", true));
});

Target AddUnshipped => _ => _
.DependsOn(Restore)
.Executes(() =>
{
// first we ensure that the All.sln exists.
if (!InvokedTargets.Contains(Restore))
{
DotNetBuildSonarSolution(AllSolutionFile);
}

// new we restore our local dotnet tools including dotnet-format
DotNetToolRestore(c => c.SetProcessWorkingDirectory(RootDirectory));

// last we run the actual dotnet format command.
DotNet($@"format ""{AllSolutionFile}"" --fix-analyzers warn --diagnostics RS0016", workingDirectory: RootDirectory);
});

Target DiffShippedApi => _ => _
.Executes(() =>
{
var from = string.IsNullOrEmpty(From) ? GitRepository.Branch : From;
var to = string.IsNullOrEmpty(To) ? "main" : To;

if (from == to)
{
Colorful.Console.WriteLine("Nothing to diff here.", Color.Yellow);
return;
}

AbsolutePath shippedPath = SourceDirectory / "**" / _shippedApiFile;

Git($@" --no-pager diff --minimal -U0 --word-diff ""{from}"" ""{to}"" -- ""{shippedPath}""", RootDirectory);
});

Target DisplayUnshippedApi => _ => _
.Executes(async () =>
{
var unshippedFiles = Directory.GetFiles(SourceDirectory, _unshippedApiFile, SearchOption.AllDirectories);

if (Breaking)
{
Colorful.Console.WriteLine("Unshipped breaking changes:", Color.Red);
}
else
{
Colorful.Console.WriteLine("Unshipped changes:");
}

Colorful.Console.WriteLine();

foreach (var unshippedFile in unshippedFiles)
{
IEnumerable<string> unshippedApis = await GetNonEmptyLinesAsync(unshippedFile);

if (Breaking)
{
unshippedApis = unshippedApis.Where(u => u.StartsWith(_removedApiPrefix)).ToList();
}

if (!unshippedApis.Any())
{
continue;
}

foreach (var unshippedApi in unshippedApis)
{
if (unshippedApi.StartsWith(_removedApiPrefix))
{
var value = unshippedApi[_removedApiPrefix.Length..];
Colorful.Console.WriteLine(value);
}
else
{
Colorful.Console.WriteLine(unshippedApi);
}
}
}
});

Target MarkApiShipped => _ => _
.Executes(async () =>
{
var shippedFiles = Directory.GetFiles(SourceDirectory, _shippedApiFile, SearchOption.AllDirectories);

foreach (var shippedFile in shippedFiles)
{
var projectDir = Path.GetDirectoryName(shippedFile);
var unshippedFile = Path.Join(projectDir, _unshippedApiFile);

if (!File.Exists(unshippedFile))
{
continue;
}

List<string> unshippedApis = await GetNonEmptyLinesAsync(unshippedFile);

if (!unshippedApis.Any())
{
continue;
}

List<string> shippedApis = await GetNonEmptyLinesAsync(shippedFile);

List<string> removedApis = new();

foreach (var unshippedApi in unshippedApis)
{
if (unshippedApi.StartsWith(_removedApiPrefix))
{
var value = unshippedApi[_removedApiPrefix.Length..];
removedApis.Add(value);
}
else
{
shippedApis.Add(unshippedApi);
}
}

IOrderedEnumerable<string> newShippedApis = shippedApis
.Where(s => !removedApis.Contains(s))
.Distinct()
.OrderBy(s => s);

await File.WriteAllLinesAsync(shippedFile, newShippedApis, Encoding.ASCII);
await File.WriteAllTextAsync(unshippedFile, "", Encoding.ASCII);
}
});

private static async Task<List<string>> GetNonEmptyLinesAsync(string filepath)
{
var lines = await File.ReadAllLinesAsync(filepath);

return lines.Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
}
}
24 changes: 17 additions & 7 deletions .build/Build.Publish.cs
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ partial class Build : NukeBuild
[Parameter("NuGet Api Key")] readonly string NuGetApiKey;

Target Pack => _ => _
.DependsOn(Restore, PackLocal)
.DependsOn(PackLocal)
.Produces(PackageDirectory / "*.nupkg")
.Produces(PackageDirectory / "*.snupkg")
.Requires(() => Configuration.Equals(Release))
@@ -41,14 +41,24 @@ partial class Build : NukeBuild
.Produces(PackageDirectory / "*.snupkg")
.Executes(() =>
{
if (!InvokedTargets.Contains(Restore))
{
DotNetBuildSonarSolution(AllSolutionFile);
}
DotNetBuildSonarSolution(
PackSolutionFile,
include: file =>
!Path.GetFileNameWithoutExtension(file)
.EndsWith("tests", StringComparison.OrdinalIgnoreCase));

DotNetBuild(c => c
.SetProjectFile(PackSolutionFile)
.SetConfiguration(Configuration)
.SetAssemblyVersion(GitVersion.AssemblySemVer)
.SetFileVersion(GitVersion.AssemblySemFileVer)
.SetInformationalVersion(GitVersion.InformationalVersion)
.SetVersion(GitVersion.SemVer));

DotNetPack(c => c
.SetProject(AllSolutionFile)
.SetNoBuild(InvokedTargets.Contains(Compile))
.SetProject(PackSolutionFile)
.SetNoRestore(true)
.SetNoBuild(true)
.SetConfiguration(Configuration)
.SetOutputDirectory(PackageDirectory)
.SetVersion(GitVersion.SemVer));
15 changes: 9 additions & 6 deletions .build/Build.Sonar.cs
Original file line number Diff line number Diff line change
@@ -20,10 +20,10 @@ partial class Build : NukeBuild
.Requires(() => GitHubPRNumber != null)
.Executes(() =>
{
Console.WriteLine($"GitHubRepository: {GitHubRepository}");
Console.WriteLine($"GitHubHeadRef: {GitHubHeadRef}");
Console.WriteLine($"GitHubBaseRef: {GitHubBaseRef}");
Console.WriteLine($"GitHubPRNumber: {GitHubPRNumber}");
Logger.Info($"GitHubRepository: {GitHubRepository}");
Logger.Info($"GitHubHeadRef: {GitHubHeadRef}");
Logger.Info($"GitHubBaseRef: {GitHubBaseRef}");
Logger.Info($"GitHubPRNumber: {GitHubPRNumber}");

DotNetBuildSonarSolution(AllSolutionFile);

@@ -41,8 +41,6 @@ partial class Build : NukeBuild
});

Target Sonar => _ => _
.DependsOn(Cover)
.Consumes(Cover)
.Executes(() =>
{
DotNetBuildSonarSolution(AllSolutionFile);
@@ -52,8 +50,13 @@ partial class Build : NukeBuild
.SetProcessWorkingDirectory(RootDirectory));

Logger.Info("Creating Sonar analysis for version: {0} ...", GitVersion.SemVer);

SonarScannerBegin(SonarBeginFullSettings);
DotNetBuild(SonarBuildAll);
DotNetTest(
c => CoverNoBuildSettingsOnly50(c, TestProjects),
degreeOfParallelism: DegreeOfParallelism,
completeOnFailure: true);
SonarScannerEnd(SonarEndSettings);
});

4 changes: 2 additions & 2 deletions .build/Build.cs
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@
AzurePipelinesImage.UbuntuLatest,
InvokedTargets = new[] { nameof(Sonar) },
PullRequestsAutoCancel = true,
PullRequestsBranchesInclude = new [] { "master" },
AutoGenerate = false)]
PullRequestsBranchesInclude = new[] { "master" },
AutoGenerate = false)]
[GitHubActions(
"sonar-pr-hotchocolate",
GitHubActionsImage.UbuntuLatest,
2 changes: 1 addition & 1 deletion .build/Build.csproj
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nuke.Common" Version="5.0.2" />
<PackageReference Include="Nuke.Common" Version="5.1.2" />
<PackageDownload Include="GitVersion.Tool" Version="[5.6.3]" />
<PackageDownload Include="NuGet.CommandLine" Version="[5.5.1]" />
<PackageDownload Include="dotnet-sonarscanner" Version="[5.0.4]" />
12 changes: 7 additions & 5 deletions .build/Helpers.cs
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ class Helpers
public static readonly string[] Directories =
{
"GreenDonut",
// Path.Combine("HotChocolate", "ApolloFederation"),
Path.Combine("HotChocolate", "AspNetCore"),
Path.Combine("HotChocolate", "Core"),
Path.Combine("HotChocolate", "Language"),
@@ -29,14 +28,16 @@ class Helpers

public static IEnumerable<string> GetAllProjects(
string sourceDirectory,
IEnumerable<string> directories)
IEnumerable<string> directories,
Func<string, bool> include = null)
{
foreach (var directory in directories)
{
var fullDirectory = Path.Combine(sourceDirectory, directory);
foreach (var file in Directory.EnumerateFiles(fullDirectory, "*.csproj", SearchOption.AllDirectories))
{
if (file.Contains("benchmark", StringComparison.OrdinalIgnoreCase)
if (!(include?.Invoke(file) ?? true)
|| file.Contains("benchmark", StringComparison.OrdinalIgnoreCase)
|| file.Contains("demo", StringComparison.OrdinalIgnoreCase)
|| file.Contains("sample", StringComparison.OrdinalIgnoreCase)
|| file.Contains("HotChocolate.Core.Tests", StringComparison.OrdinalIgnoreCase)
@@ -52,7 +53,8 @@ public static IEnumerable<string> GetAllProjects(

public static IReadOnlyCollection<Output> DotNetBuildSonarSolution(
string solutionFile,
IEnumerable<string> directories = null)
IEnumerable<string> directories = null,
Func<string, bool> include = null)
{
if (File.Exists(solutionFile))
{
@@ -61,7 +63,7 @@ public static IReadOnlyCollection<Output> DotNetBuildSonarSolution(

directories ??= Directories;

IEnumerable<string> projects = GetAllProjects(Path.GetDirectoryName(solutionFile), directories);
IEnumerable<string> projects = GetAllProjects(Path.GetDirectoryName(solutionFile), directories, include);
var workingDirectory = Path.GetDirectoryName(solutionFile);
var list = new List<Output>();

14 changes: 13 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -8,8 +8,20 @@
"dotnet-sonarscanner"
]
},
"boost.tool": {
"version": "0.2.6",
"commands": [
"boo"
]
},
"dotnet-format": {
"version": "5.1.225507",
"commands": [
"dotnet-format"
]
},
"nuke.globaltool": {
"version": "0.24.11",
"version": "5.1.2",
"commands": [
"nuke"
]
45 changes: 45 additions & 0 deletions .devops/azure-pipelines.release-hotchocolate.yml
Original file line number Diff line number Diff line change
@@ -14,6 +14,21 @@ stages:
strategy:
parallel: 5
steps:
- task: UseDotNet@2
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
@@ -32,6 +47,21 @@ stages:
displayName: "Pack"
dependsOn: []
steps:
- task: UseDotNet@2
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
@@ -50,6 +80,21 @@ stages:
displayName: "Publish"
dependsOn: [Test, Pack]
steps:
- task: UseDotNet@2
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
52 changes: 12 additions & 40 deletions .devops/azure-pipelines.sonar-hotchocolate.yml
Original file line number Diff line number Diff line change
@@ -13,48 +13,25 @@ stages:
pool:
vmImage: "ubuntu-20.04"
jobs:
- job: Cover
displayName: "Cover"
dependsOn: []
strategy:
parallel: 5
- job: Sonar
displayName: "Sonar"
dependsOn: [ ]
steps:
- task: UseDotNet@2
displayName: "Install .NET Core"
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
useGlobalJson: true
- task: CmdLine@2
displayName: "Run Tests"
inputs:
script: "./build.cmd Cover --test-partition $(System.JobPositionInPhase)"
- task: PublishBuildArtifacts@1
displayName: "Upload Test Results"
inputs:
artifactName: test-results
pathtoPublish: "output/test-results"
- job: ReportCoverage
displayName: "ReportCoverage"
dependsOn: [Cover]
steps:
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core"
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
useGlobalJson: true
- task: DownloadBuildArtifacts@0
displayName: "Download Test Results"
inputs:
artifactName: "test-results"
downloadPath: "$(Build.Repository.LocalPath)/output"
- task: CmdLine@2
displayName: "Create Coverage Report for Azure DevOps"
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
script: "./build.cmd ReportCoverage --skip"
- job: Sonar
displayName: "Sonar"
dependsOn: [Cover]
steps:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
@@ -67,12 +44,7 @@ stages:
jdkArchitectureOption: "x64"
jdkSourceOption: "PreInstalled"
jdkDestinationDirectory: ./.java
- task: DownloadBuildArtifacts@0
displayName: "Download Test Results"
inputs:
artifactName: "test-results"
downloadPath: "$(Build.Repository.LocalPath)/output"
- task: CmdLine@2
displayName: "Run Sonar Analysis"
inputs:
script: "./build.cmd Sonar --skip"
script: "./build.cmd Sonar"
15 changes: 15 additions & 0 deletions .devops/azure-pipelines.sonar-pr-hotchocolate.yml
Original file line number Diff line number Diff line change
@@ -17,6 +17,21 @@ stages:
displayName: "Sonar Pull-Request"
dependsOn: []
steps:
- task: UseDotNet@2
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
15 changes: 15 additions & 0 deletions .devops/azure-pipelines.test-pr-hotchocolate.yml
Original file line number Diff line number Diff line change
@@ -20,6 +20,21 @@ stages:
strategy:
parallel: 5
steps:
- task: UseDotNet@2
displayName: "Install .NET Core 2.1"
inputs:
packageType: 'sdk'
version: '2.1.816'
- task: UseDotNet@2
displayName: "Install .NET Core 3.1"
inputs:
packageType: 'sdk'
version: '3.1.409'
- task: UseDotNet@2
displayName: "Install .NET 5.0"
inputs:
packageType: 'sdk'
version: '5.0.300'
- task: UseDotNet@2
displayName: "Install .NET Core"
inputs:
161 changes: 161 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"properties": {
"Breaking": {
"type": "boolean"
},
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)"
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"From": {
"type": "string"
},
"GitHubBaseRef": {
"type": "string"
},
"GitHubHeadRef": {
"type": "string"
},
"GitHubPRNumber": {
"type": "string"
},
"GitHubRepository": {
"type": "string"
},
"GitHubToken": {
"type": "string"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI"
]
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"NuGetApiKey": {
"type": "string",
"description": "NuGet Api Key"
},
"NuGetSource": {
"type": "string",
"description": "NuGet Source for Packages"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"AddUnshipped",
"CheckPublicApi",
"Clean",
"Compile",
"Cover",
"DiffShippedApi",
"DisplayUnshippedApi",
"MarkApiShipped",
"Pack",
"PackLocal",
"Publish",
"ReportCoverage",
"Restore",
"Sonar",
"SonarPr",
"Test"
]
}
},
"SonarServer": {
"type": "string"
},
"SonarToken": {
"type": "string"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"AddUnshipped",
"CheckPublicApi",
"Clean",
"Compile",
"Cover",
"DiffShippedApi",
"DisplayUnshippedApi",
"MarkApiShipped",
"Pack",
"PackLocal",
"Publish",
"ReportCoverage",
"Restore",
"Sonar",
"SonarPr",
"Test"
]
}
},
"TestPartition": {
"type": "string"
},
"To": {
"type": "string"
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
}
}
}
}
}
4 changes: 4 additions & 0 deletions .nuke/parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": ""
}
104 changes: 104 additions & 0 deletions API-Baselines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# API Baselines

This document contains information regarding API baseline files and how to work with them.

## Files

Each project contains two files tracking the public API surface of this project.

### PublicAPI.Shipped.txt

This file contains APIs that were released in the last major version.

This file should only be modified after a major release by the maintainers and should never be modified otherwise. There is a [script](#scripts) to perform this automatically.

### PublicAPI.Unshipped.txt

This file contains API changes since the last major version.

## Scenarios

There are three types of public API changes that need to be documented.

### New APIs

A new entry needs to be added to the `PublicAPI.Unshipped.txt` file for a new API. For example:

```
#nullable enable
Microsoft.AspNetCore.Builder.NewApplicationBuilder.New() -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
```

Your IDE should warn you about this case and prompt you to add the new API to `PublicAPI.Unshipped.txt`. It will also be displayed as a warning in the build output.

> Note: Currently not every IDE supports Code-Fixes provided by a Roslyn Analyzer. Visual Studio Code for example does not at the moment - Visual Studio 2019 does.
### Removed APIs

A new entry needs to be added to the `PublicAPI.Unshipped.txt` file for a removed API. For example:

```
#nullable enable
*REMOVED*Microsoft.Builder.OldApplicationBuilder.New() -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
```

This change needs to be done by hand. Copy the relevant line from `PublicAPI.Shipped.txt` into `PublicAPI.Unshipped.txt` and place `*REMOVED*` in front of it.

### Updated APIs

Two new entries need to be added to the `PublicAPI.Unshipped.txt` file for an updated API. One to remove the old API and one for the new API. For example:

```
#nullable enable
*REMOVED*Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string!
Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string?
```

The removed case needs to be handled by hand as explained [here](#removed-apis).

## Ignoring projects

Projects ending in `.Tests` or `.Resources` are ignored per default.

If you need to manually ignore a project, include the following in its `.csproj` file:

```xml
<PropertyGroup>
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
</PropertyGroup>
```

## New projects

The two text files mentioned above need to be added to each new project.

There is a template file called `PublicAPI.empty.txt` in the `scripts` directory that can be copied over into a new project.

```sh
cp scripts/PublicAPI.empty.txt src/<new-project-folder>/PublicAPI.Shipped.txt
cp scripts/PublicAPI.empty.txt src/<new-project-folder>/PublicAPI.Unshipped.txt
```

## Scripts

There are three scripts to help you manage the `PublicAPI.*.txt` files. They can be found [here](./scripts).

### mark-api-shipped.ps1

This transfers all changes in the `PublicAPI.Unshipped.txt` to the `PublicAPI.Shipped.txt` files.

It also takes care of removing lines marked with `*REMOVE*` (removals of APIs).

### display-unshipped-api.ps1

This will output the contents of all `PublicAPI.Unshipped.txt` files throughout the project.

### diff-shipped-api.ps1

This shows all changes of `PublicAPI.Shipped.txt` files between git refs. Tags, commit hashes and branch names are supported.

Example:

```sh
diff-shipped-api.ps1 -from 11.0.0 -to 12.0.0
```
File renamed without changes.
4 changes: 4 additions & 0 deletions COMMUNITY.md
Original file line number Diff line number Diff line change
@@ -18,6 +18,10 @@ Various libraries, packages, etc. that developers can add to their own project a

- [AutoGuru.HotChocolate.PolymorphicIds](https://github.com/autoguru-au/hotchocolate-polymorphic-ids) - Polymorphic Relay IDs for HotChocolate

### Types

- [HotChocolate.Types.NodaTime](https://github.com/shoooe/hotchocolate-nodatime) - Adds support for [NodaTime](https://github.com/nodatime/nodatime) types in Hot Chocolate.

### Validation

- [AppAny.HotChocolate.FluentValidation](https://github.com/appany/AppAny.HotChocolate.FluentValidation) - Input field HotChocolate + FluentValidation integration
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# How to contribute

One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes.

## General feedback and discussions?

Start a discussion on the [repository issue tracker](https://github.com/ChilliCream/hotchocolate/issues) or [join us on slack](https://bit.ly/joinchilli).

## Bugs and feature requests?

Before reporting a new issue, try to find an existing issue if one already exists. If it already exists, upvote (👍) it. Also consider adding a comment with your unique scenarios and requirements related to that issue.

If you can't find one, you can file a new issue by choosing the appropriate template [here](https://github.com/ChilliCream/hotchocolate/issues/new/choose).

## How to submit a pull request

We are always happy to see pull requests from community members both for bug fixes as well as new features.

### Finding an issue to work on

We have marked issues which are good candidates for first-time contributors, in case you are not already set on working on a specific issue.

- ["Good first issue" issues](https://github.com/ChilliCream/hotchocolate/labels/%F0%9F%99%8B%20good%20first%20issue) - we think these are a great for newcomers.
- ["Help wanted" issues](https://github.com/ChilliCream/hotchocolate/labels/%F0%9F%99%8B%20help%20wanted) - these issues are up for grabs.

### Before writing code

Before you spend time writing code, make sure of the following things:

- You have commented on the related issue to let others know you are working on it
- You have laid out your solution on a high level and received approval from the maintainers, if you are tackling a bigger change

After this you can fork our repository to implement your changes. If you are unfamiliar with forking, be sure to read [this guide](https://guides.github.com/activities/forking/) first.

### Before submitting a pull request

Before submitting a pull request containing your changes, make sure that it checks the following requirements:

- You add test coverage following existing patterns within the codebase
- Your code matches the existing syntax conventions within the codebase
- You document any changes to the public API surface ([Learn more](./API-Baselines.md))
- Your pull request is small, focused, and avoids making unrelated changes

If your pull request contains any of the below, it's less likely to be merged.

- Changes that break backward compatibility
- Changes that are only wanted by one person/company
- Changes that add entirely new feature areas without prior agreement
- Changes that are mostly about refactoring existing code or code style

### Submitting a pull request

Follow [this guide](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) to submit your pull request. Be sure to mark it as draft if it is in an early stage.

### During pull request review

Core contributors will review your pull request and provide feedback.

## Code of conduct

See [CODE-OF-CONDUCT.md](./CODE-OF-CONDUCT.md)
31 changes: 0 additions & 31 deletions CONTRIBUTION.md

This file was deleted.

2 changes: 1 addition & 1 deletion build.cmd
Original file line number Diff line number Diff line change
@@ -4,4 +4,4 @@
:; exit $?

@ECHO OFF
powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %*
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
17 changes: 4 additions & 13 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -14,16 +14,15 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################

$BuildProjectFile = "$PSScriptRoot\.build\Build.csproj"
$TempDirectory = "$PSScriptRoot\.tmp"
$TempDirectory = "$PSScriptRoot\\.nuke\temp"

$DotNetGlobalFile = "$PSScriptRoot\global.json"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$DotNetChannel = "Current"

$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
$env:DOTNET_MULTILEVEL_LOOKUP = 0
$env:DOTNET_ROLL_FORWARD = "Major"

###########################################################################
# EXECUTION
@@ -34,17 +33,9 @@ function ExecSafe([scriptblock] $cmd) {
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}

# Print environment variables
Get-Item -Path Env:* | Sort-Object -Property Name | ForEach-Object {"{0}={1}" -f $_.Name,$_.Value}

# Check if any dotnet is installed
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue)) {
ExecSafe { & dotnet --info }
}

# If dotnet CLI is installed globally and it matches requested version, use for execution
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
else {
@@ -74,5 +65,5 @@ else {

Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"

ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary }
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
15 changes: 3 additions & 12 deletions build.sh
Original file line number Diff line number Diff line change
@@ -10,16 +10,15 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################

BUILD_PROJECT_FILE="$SCRIPT_DIR/.build/Build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR/.tmp"
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"

DOTNET_GLOBAL_FILE="$SCRIPT_DIR/global.json"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
DOTNET_CHANNEL="Current"

export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_MULTILEVEL_LOOKUP=0
export DOTNET_ROLL_FORWARD="Major"

###########################################################################
# EXECUTION
@@ -29,14 +28,6 @@ function FirstJsonValue {
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
}

# Print environment variables
env | sort

# Check if any dotnet is installed
if [[ -x "$(command -v dotnet)" ]]; then
dotnet --info
fi

# If dotnet CLI is installed globally and it matches requested version, use for execution
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
export DOTNET_EXE="$(command -v dotnet)"
@@ -67,5 +58,5 @@ fi

echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"

"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
1 change: 1 addition & 0 deletions scripts/PublicAPI.empty.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
19 changes: 19 additions & 0 deletions scripts/diff-shipped-api.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[CmdletBinding(PositionalBinding=$false)]
param (
[string]$from,
[string]$to = "main"
)

Set-StrictMode -version 2.0
$ErrorActionPreference = "Stop"

try {
Write-Host "Diffing '$from' to '$to'..."

git --no-pager diff --minimal -U0 --word-diff "$from" "$to" -- "../src/**/PublicAPI.Shipped.txt"
}
catch {
Write-Host $_
Write-Host $_.Exception
exit 1
}
35 changes: 35 additions & 0 deletions scripts/display-unshipped-api.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[CmdletBinding(PositionalBinding = $false)]
param ()

Set-StrictMode -version 2.0
$ErrorActionPreference = "Stop"

function ShowUnshipped([string]$file, [string]$src_dir) {
[string[]]$unshipped = Get-Content $file

if ([string]::IsNullOrWhiteSpace($unshipped)) {
return
}

$dir = (Split-Path -parent $file) -replace [regex]::escape($src_dir + [IO.Path]::DirectorySeparatorChar), ""
Write-Host -Foreground green "## ${dir}"

foreach ($item in $unshipped) {
if ($item.Length -gt 0) {
Write-Host "$item"
}
}
}

try {
$src_dir = Resolve-Path -Path "../src"

foreach ($file in Get-ChildItem -Path "$src_dir" -Recurse -Include "PublicApi.Unshipped.txt") {
ShowUnshipped $file $src_dir
}
}
catch {
Write-Host $_
Write-Host $_.Exception
exit 1
}
51 changes: 51 additions & 0 deletions scripts/mark-api-shipped.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[CmdletBinding(PositionalBinding = $false)]
param ()

Set-StrictMode -version 2.0
$ErrorActionPreference = "Stop"

function MarkShipped([string]$dir) {
$shippedFilePath = Join-Path $dir "PublicAPI.Shipped.txt"
[string[]]$shipped = Get-Content $shippedFilePath
if ($null -eq $shipped) {
$shipped = @()
}

$unshippedFilePath = Join-Path $dir "PublicAPI.Unshipped.txt"
[string[]]$unshipped = Get-Content $unshippedFilePath | Where-Object { $_.trim() -ne "" }
if ($null -eq $unshipped -or $unshipped.Length -lt 1) {
return
}

$removed = @()
$removedPrefix = "*REMOVED*";

Write-Host "Processing $dir"

foreach ($item in $unshipped) {
if ($item.Length -gt 0) {
if ($item.StartsWith($removedPrefix)) {
$item = $item.Substring($removedPrefix.Length)
$removed += $item
}
else {
$shipped += $item
}
}
}

$shipped | Sort-Object -Stable -Unique | Where-Object { -not $removed.Contains($_) } | Out-File $shippedFilePath -Encoding Ascii
"" | Out-File $unshippedFilePath -Encoding Ascii
}

try {
foreach ($file in Get-ChildItem -Path "../src" -Recurse -Include "PublicApi.Shipped.txt") {
$dir = Split-Path -parent $file
MarkShipped $dir
}
}
catch {
Write-Host $_
Write-Host $_.Exception
exit 1
}
36 changes: 35 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
<PropertyGroup>
<LangVersion>preview</LangVersion>
<Version Condition="$(Version) == ''">0.0.0</Version>
<NoWarn>$(NoWarn);CS0436</NoWarn>
<NoWarn>$(NoWarn);CS0436;RS0041</NoWarn>
</PropertyGroup>

<PropertyGroup>
@@ -43,5 +43,39 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>

<!-- PublicApiAnalyzers -->
<PropertyGroup>
<AddPublicApiAnalyzers Condition=" '$(AddPublicApiAnalyzers)' == '' AND !$(MSBuildProjectName.Contains('.Tests')) AND !$(MSBuildProjectName.Contains('.Resources')) ">true</AddPublicApiAnalyzers>
<AddPublicApiAnalyzers Condition=" '$(AddPublicApiAnalyzers)' == '' ">false</AddPublicApiAnalyzers>

<WarningsAsErrors Condition=" '$(RequireDocumentationOfPublicApiChanges)' != '' ">$(WarningsAsErrors);RS0016;RS0017;RS0024</WarningsAsErrors>
</PropertyGroup>

<ItemGroup Condition=" $(AddPublicApiAnalyzers) ">
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.2">
<ExcludeAssets>compile</ExcludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<Target Name="_CheckIgnoredPublicApiFiles"
BeforeTargets="Build;Restore"
Condition=" Exists('$(MSBuildProjectDirectory)\PublicAPI.Shipped.txt') AND ! $(AddPublicApiAnalyzers) ">
<Warning Text="Public API baseline files ignored." />
</Target>

<Target Name="_RemovePublicApiAnalyzer" BeforeTargets="RazorCoreCompile">
<ItemGroup>
<_PublicAPIAnalyzers Include="@(Analyzer->WithMetadataValue('NuGetPackageId','Microsoft.CodeAnalysis.PublicApiAnalyzers'))" />
<Analyzer Remove="@(_PublicAPIAnalyzers)" />
</ItemGroup>
</Target>

<Target Name="_RestorePublicApiAnalyzer" AfterTargets="RazorCoreCompile">
<ItemGroup>
<Analyzer Include="@(_PublicAPIAnalyzers)" />
</ItemGroup>
</Target>

</Project>
84 changes: 84 additions & 0 deletions src/GreenDonut/src/Core/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#nullable enable
abstract GreenDonut.DataLoaderBase<TKey, TValue>.FetchAsync(System.Collections.Generic.IReadOnlyList<TKey>! keys, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IReadOnlyList<GreenDonut.Result<TValue>>!>
GreenDonut.Batch<TKey, TValue>
GreenDonut.Batch<TKey, TValue>.Batch() -> void
GreenDonut.Batch<TKey, TValue>.Get(TKey key) -> System.Threading.Tasks.TaskCompletionSource<TValue>!
GreenDonut.Batch<TKey, TValue>.Keys.get -> System.Collections.Generic.IReadOnlyList<TKey>!
GreenDonut.Batch<TKey, TValue>.Size.get -> int
GreenDonut.Batch<TKey, TValue>.StartDispatchingAsync(System.Func<System.Threading.Tasks.ValueTask>! dispatch) -> System.Threading.Tasks.ValueTask
GreenDonut.Batch<TKey, TValue>.TryGetOrCreate(TKey key, out System.Threading.Tasks.TaskCompletionSource<TValue>? promise) -> bool
GreenDonut.CacheKeyResolverDelegate<TKey>
GreenDonut.DataLoaderBase<TKey, TValue>
GreenDonut.DataLoaderBase<TKey, TValue>.Clear() -> void
GreenDonut.DataLoaderBase<TKey, TValue>.DataLoaderBase(GreenDonut.IBatchScheduler! batchScheduler, GreenDonut.DataLoaderOptions<TKey>? options) -> void
GreenDonut.DataLoaderBase<TKey, TValue>.DataLoaderBase(GreenDonut.IBatchScheduler! batchScheduler) -> void
GreenDonut.DataLoaderBase<TKey, TValue>.Dispose() -> void
GreenDonut.DataLoaderBase<TKey, TValue>.LoadAsync(System.Collections.Generic.IReadOnlyCollection<TKey>! keys, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
GreenDonut.DataLoaderBase<TKey, TValue>.LoadAsync(System.Threading.CancellationToken cancellationToken, params TKey[]! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
GreenDonut.DataLoaderBase<TKey, TValue>.LoadAsync(TKey key, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TValue>!
GreenDonut.DataLoaderBase<TKey, TValue>.Remove(TKey key) -> void
GreenDonut.DataLoaderBase<TKey, TValue>.Set(TKey key, System.Threading.Tasks.Task<TValue>! value) -> void
GreenDonut.DataLoaderExtensions
GreenDonut.DataLoaderOptions<TKey>
GreenDonut.DataLoaderOptions<TKey>.Batch.get -> bool
GreenDonut.DataLoaderOptions<TKey>.Batch.set -> void
GreenDonut.DataLoaderOptions<TKey>.Cache.get -> GreenDonut.ITaskCache?
GreenDonut.DataLoaderOptions<TKey>.Cache.set -> void
GreenDonut.DataLoaderOptions<TKey>.CacheKeyResolver.get -> GreenDonut.CacheKeyResolverDelegate<TKey>?
GreenDonut.DataLoaderOptions<TKey>.CacheKeyResolver.set -> void
GreenDonut.DataLoaderOptions<TKey>.CacheSize.get -> int
GreenDonut.DataLoaderOptions<TKey>.CacheSize.set -> void
GreenDonut.DataLoaderOptions<TKey>.Caching.get -> bool
GreenDonut.DataLoaderOptions<TKey>.Caching.set -> void
GreenDonut.DataLoaderOptions<TKey>.DataLoaderOptions() -> void
GreenDonut.DataLoaderOptions<TKey>.MaxBatchSize.get -> int
GreenDonut.DataLoaderOptions<TKey>.MaxBatchSize.set -> void
GreenDonut.DataLoaderOptionsExtensions
GreenDonut.FetchDataDelegate<TKey, TValue>
GreenDonut.IBatchScheduler
GreenDonut.IBatchScheduler.Schedule(System.Func<System.Threading.Tasks.ValueTask>! dispatch) -> void
GreenDonut.IDataLoader
GreenDonut.IDataLoader.Clear() -> void
GreenDonut.IDataLoader.LoadAsync(object! key, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<object?>!
GreenDonut.IDataLoader.LoadAsync(System.Collections.Generic.IReadOnlyCollection<object!>! keys, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<object?>!>!
GreenDonut.IDataLoader.LoadAsync(System.Threading.CancellationToken cancellationToken, params object![]! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<object?>!>!
GreenDonut.IDataLoader.Remove(object! key) -> void
GreenDonut.IDataLoader.Set(object! key, System.Threading.Tasks.Task<object?>! value) -> void
GreenDonut.IDataLoader<TKey, TValue>
GreenDonut.IDataLoader<TKey, TValue>.LoadAsync(System.Collections.Generic.IReadOnlyCollection<TKey>! keys, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
GreenDonut.IDataLoader<TKey, TValue>.LoadAsync(System.Threading.CancellationToken cancellationToken, params TKey[]! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
GreenDonut.IDataLoader<TKey, TValue>.LoadAsync(TKey key, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<TValue>!
GreenDonut.IDataLoader<TKey, TValue>.Remove(TKey key) -> void
GreenDonut.IDataLoader<TKey, TValue>.Set(TKey key, System.Threading.Tasks.Task<TValue>! value) -> void
GreenDonut.ITaskCache
GreenDonut.ITaskCache.Clear() -> void
GreenDonut.ITaskCache.Remove(object! key) -> void
GreenDonut.ITaskCache.Size.get -> int
GreenDonut.ITaskCache.TryAdd(object! key, object! value) -> bool
GreenDonut.ITaskCache.TryGetValue(object! key, out object? value) -> bool
GreenDonut.ITaskCache.Usage.get -> int
GreenDonut.RequestBufferedEventHandler
GreenDonut.Result<TValue>
GreenDonut.Result<TValue>.Equals(GreenDonut.Result<TValue> other) -> bool
GreenDonut.Result<TValue>.Error.get -> System.Exception?
GreenDonut.Result<TValue>.IsError.get -> bool
GreenDonut.Result<TValue>.Result() -> void
GreenDonut.Result<TValue>.Value.get -> TValue
override GreenDonut.Result<TValue>.Equals(object? obj) -> bool
override GreenDonut.Result<TValue>.GetHashCode() -> int
static GreenDonut.DataLoaderExtensions.LoadAsync(this GreenDonut.IDataLoader! dataLoader, object! key) -> System.Threading.Tasks.Task<object?>!
static GreenDonut.DataLoaderExtensions.LoadAsync(this GreenDonut.IDataLoader! dataLoader, params object![]! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<object?>!>!
static GreenDonut.DataLoaderExtensions.LoadAsync(this GreenDonut.IDataLoader! dataLoader, System.Collections.Generic.IReadOnlyCollection<object!>! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<object?>!>!
static GreenDonut.DataLoaderExtensions.LoadAsync<TKey, TValue>(this GreenDonut.IDataLoader<TKey, TValue>! dataLoader, params TKey[]! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
static GreenDonut.DataLoaderExtensions.LoadAsync<TKey, TValue>(this GreenDonut.IDataLoader<TKey, TValue>! dataLoader, System.Collections.Generic.IReadOnlyCollection<TKey>! keys) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<TValue>!>!
static GreenDonut.DataLoaderExtensions.LoadAsync<TKey, TValue>(this GreenDonut.IDataLoader<TKey, TValue>! dataLoader, TKey key) -> System.Threading.Tasks.Task<TValue>!
static GreenDonut.DataLoaderExtensions.Set(this GreenDonut.IDataLoader! dataLoader, object! key, object? value) -> void
static GreenDonut.DataLoaderExtensions.Set<TKey, TValue>(this GreenDonut.IDataLoader<TKey, TValue>! dataLoader, TKey key, TValue value) -> void
static GreenDonut.DataLoaderOptionsExtensions.GetBatchSize<TKey>(this GreenDonut.DataLoaderOptions<TKey>! options) -> int
static GreenDonut.Result<TValue>.implicit operator GreenDonut.Result<TValue>(System.Exception? error) -> GreenDonut.Result<TValue>
static GreenDonut.Result<TValue>.implicit operator GreenDonut.Result<TValue>(TValue value) -> GreenDonut.Result<TValue>
static GreenDonut.Result<TValue>.implicit operator System.Exception?(GreenDonut.Result<TValue> result) -> System.Exception?
static GreenDonut.Result<TValue>.implicit operator TValue(GreenDonut.Result<TValue> result) -> TValue
static GreenDonut.Result<TValue>.Reject(System.Exception! error) -> GreenDonut.Result<TValue>
static GreenDonut.Result<TValue>.Resolve(TValue value) -> GreenDonut.Result<TValue>
virtual GreenDonut.DataLoaderBase<TKey, TValue>.Dispose(bool disposing) -> void
1 change: 1 addition & 0 deletions src/GreenDonut/src/Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#nullable enable
HotChocolate.AspNetCore.Authorization.ApplyPolicy
HotChocolate.AspNetCore.Authorization.ApplyPolicy.AfterResolver = 1 -> HotChocolate.AspNetCore.Authorization.ApplyPolicy
HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver = 0 -> HotChocolate.AspNetCore.Authorization.ApplyPolicy
HotChocolate.AspNetCore.Authorization.ApplyPolicyType
HotChocolate.AspNetCore.Authorization.ApplyPolicyType.ApplyPolicyType() -> void
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Apply.get -> HotChocolate.AspNetCore.Authorization.ApplyPolicy
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Apply.set -> void
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.AuthorizeAttribute() -> void
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Policy.get -> string?
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Policy.set -> void
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Roles.get -> string![]?
HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.Roles.set -> void
HotChocolate.AspNetCore.Authorization.AuthorizeDirective
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.Apply.get -> HotChocolate.AspNetCore.Authorization.ApplyPolicy
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.AuthorizeDirective(string? policy = null, System.Collections.Generic.IReadOnlyList<string!>? roles = null, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> void
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.AuthorizeDirective(System.Collections.Generic.IReadOnlyList<string!>! roles, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> void
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.AuthorizeDirective(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.GetObjectData(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.Policy.get -> string?
HotChocolate.AspNetCore.Authorization.AuthorizeDirective.Roles.get -> System.Collections.Generic.IReadOnlyList<string!>?
HotChocolate.AspNetCore.Authorization.AuthorizeDirectiveType
HotChocolate.AspNetCore.Authorization.AuthorizeDirectiveType.AuthorizeDirectiveType() -> void
HotChocolate.AuthorizeSchemaBuilderExtensions
HotChocolate.AuthorizeSchemaConfigurationExtensions
HotChocolate.Types.AuthorizeObjectFieldDescriptorExtensions
HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions
HotChocolate.Types.AuthorizeSchemaTypeDescriptorExtensions
Microsoft.Extensions.DependencyInjection.HotChocolateAuthorizeRequestExecutorBuilder
override HotChocolate.AspNetCore.Authorization.AuthorizeAttribute.TryConfigure(HotChocolate.Types.Descriptors.IDescriptorContext! context, HotChocolate.Types.IDescriptor! descriptor, System.Reflection.ICustomAttributeProvider! element) -> void
static HotChocolate.AuthorizeSchemaBuilderExtensions.AddAuthorizeDirectiveType(this HotChocolate.ISchemaBuilder! builder) -> HotChocolate.ISchemaBuilder!
static HotChocolate.AuthorizeSchemaConfigurationExtensions.RegisterAuthorizeDirectiveType(this HotChocolate.Configuration.ISchemaConfiguration! configuration) -> void
static HotChocolate.Types.AuthorizeObjectFieldDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectFieldDescriptor! descriptor, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectFieldDescriptor!
static HotChocolate.Types.AuthorizeObjectFieldDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectFieldDescriptor! descriptor, params string![]! roles) -> HotChocolate.Types.IObjectFieldDescriptor!
static HotChocolate.Types.AuthorizeObjectFieldDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectFieldDescriptor! descriptor, string! policy, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectFieldDescriptor!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectTypeDescriptor! descriptor, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectTypeDescriptor!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectTypeDescriptor! descriptor, params string![]! roles) -> HotChocolate.Types.IObjectTypeDescriptor!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize(this HotChocolate.Types.IObjectTypeDescriptor! descriptor, string! policy, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectTypeDescriptor!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize<T>(this HotChocolate.Types.IObjectTypeDescriptor<T>! descriptor, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectTypeDescriptor<T>!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize<T>(this HotChocolate.Types.IObjectTypeDescriptor<T>! descriptor, params string![]! roles) -> HotChocolate.Types.IObjectTypeDescriptor<T>!
static HotChocolate.Types.AuthorizeObjectTypeDescriptorExtensions.Authorize<T>(this HotChocolate.Types.IObjectTypeDescriptor<T>! descriptor, string! policy, HotChocolate.AspNetCore.Authorization.ApplyPolicy apply = HotChocolate.AspNetCore.Authorization.ApplyPolicy.BeforeResolver) -> HotChocolate.Types.IObjectTypeDescriptor<T>!
static HotChocolate.Types.AuthorizeSchemaTypeDescriptorExtensions.Authorize(this HotChocolate.Types.ISchemaTypeDescriptor! self, params string![]! roles) -> HotChocolate.Types.ISchemaTypeDescriptor!
static HotChocolate.Types.AuthorizeSchemaTypeDescriptorExtensions.Authorize(this HotChocolate.Types.ISchemaTypeDescriptor! self, string! policy, params string![]! roles) -> HotChocolate.Types.ISchemaTypeDescriptor!
static HotChocolate.Types.AuthorizeSchemaTypeDescriptorExtensions.Authorize(this HotChocolate.Types.ISchemaTypeDescriptor! self, string! policy) -> HotChocolate.Types.ISchemaTypeDescriptor!
static HotChocolate.Types.AuthorizeSchemaTypeDescriptorExtensions.Authorize(this HotChocolate.Types.ISchemaTypeDescriptor! self) -> HotChocolate.Types.ISchemaTypeDescriptor!
static Microsoft.Extensions.DependencyInjection.HotChocolateAuthorizeRequestExecutorBuilder.AddAuthorization(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder!
static Microsoft.Extensions.DependencyInjection.HotChocolateAuthorizeRequestExecutorBuilder.AddAuthorizeDirectiveType(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

14 changes: 14 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using HotChocolate.AspNetCore.Properties;
using HotChocolate.Execution;

@@ -25,5 +26,18 @@ public static IQueryResult ResponseTypeNotSupported() =>
ErrorBuilder.New()
.SetMessage(AspNetCoreResources.ErrorHelper_ResponseTypeNotSupported)
.Build());

public static IQueryResult UnknownSubscriptionError(Exception ex)
{
IError error =
ErrorBuilder
.New()
.SetException(ex)
.SetCode(ErrorCodes.Execution.TaskProcessingError)
.SetMessage(AspNetCoreResources.Subscription_SendResultsAsync)
.Build();

return QueryResultBuilder.CreateError(error);
}
}
}
32 changes: 28 additions & 4 deletions src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs
Original file line number Diff line number Diff line change
@@ -75,17 +75,41 @@ private async Task HandleRequestAsync(HttpContext context)
statusCode = HttpStatusCode.BadRequest;
result = QueryResultBuilder.CreateError(errorHandler.Handle(ex.Errors));
}
catch (GraphQLException ex)
{
// This allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
statusCode = null; // we let the serializer determine the status code.
result = QueryResultBuilder.CreateError(ex.Errors);
}
catch (Exception ex)
{
statusCode = HttpStatusCode.InternalServerError;
IError error = errorHandler.CreateUnexpectedError(ex).Build();
result = QueryResultBuilder.CreateError(error);
}

// in any case we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null!, "No GraphQL result was created.");
await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted);
try
{
// in any case we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null!, "No GraphQL result was created.");
await WriteResultAsync(context.Response, result, statusCode,
context.RequestAborted);
}
finally
{
// query results use pooled memory an need to be disposed after we have
// used them.
if (result is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}

if (result is IDisposable disposable)
{
disposable.Dispose();
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ private static HttpMultipartRequest ParseMultipartRequest(IFormCollection form)

try
{
map = JsonSerializer.Deserialize<Dictionary<string, string[]>>(mapString);
map = JsonSerializer.Deserialize<Dictionary<string, string[]>>(mapString!);
}
catch
{
@@ -176,7 +176,7 @@ private static void InsertFilesIntoRequest(
{
var path = VariablePath.Parse(objectPath);

if (!mutableVariables.TryGetValue(path.Key.Value, out object? value))
if (!mutableVariables.TryGetValue(path.Key.Value, out var value))
{
throw ThrowHelper.HttpMultipartMiddleware_VariableNotFound(objectPath);
}
@@ -206,8 +206,7 @@ private static IValueNode RewriteVariable(
object value,
FileValueNode file)
{
if (segment is KeyPathSegment key &&
value is ObjectValueNode ov)
if (segment is KeyPathSegment key && value is ObjectValueNode ov)
{
var pos = -1;

@@ -234,8 +233,7 @@ key.Next is not null
return ov.WithFields(fields);
}

if (segment is IndexPathSegment index &&
value is ListValueNode lv)
if (segment is IndexPathSegment index && value is ListValueNode lv)
{
IValueNode[] items = lv.Items.ToArray();
IValueNode item = items[index.Value];
32 changes: 28 additions & 4 deletions src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs
Original file line number Diff line number Diff line change
@@ -136,17 +136,41 @@ protected async Task HandleRequestAsync(
statusCode = HttpStatusCode.BadRequest;
result = QueryResultBuilder.CreateError(errorHandler.Handle(ex.Errors));
}
catch (GraphQLException ex)
{
// This allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
statusCode = null; // we let the serializer determine the status code.
result = QueryResultBuilder.CreateError(ex.Errors);
}
catch (Exception ex)
{
statusCode = HttpStatusCode.InternalServerError;
IError error = errorHandler.CreateUnexpectedError(ex).Build();
result = QueryResultBuilder.CreateError(error);
}

// in any case we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null, "No GraphQL result was created.");
await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted);
try
{
// in any case we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null!, "No GraphQL result was created.");
await WriteResultAsync(context.Response, result, statusCode,
context.RequestAborted);
}
finally
{
// query results use pooled memory an need to be disposed after we have
// used them.
if (result is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}

if (result is IDisposable disposable)
{
disposable.Dispose();
}
}
}

private static bool TryParseOperations(

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -78,4 +78,13 @@
<data name="ErrorHelper_ResponseTypeNotSupported" xml:space="preserve">
<value>The response type is not supported.</value>
</data>
<data name="Subscription_SendResultsAsync" xml:space="preserve">
<value>Unexpected Execution Error</value>
</data>
<data name="WebSocketSession_SessionEnded" xml:space="preserve">
<value>Session ended.</value>
</data>
<data name="DataStartMessageHandler_Not_A_SubscriptionResult" xml:space="preserve">
<value>The specified result object is not a valid subscription result.</value>
</data>
</root>
251 changes: 251 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/PublicAPI.Shipped.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#nullable enable
**Removed**HotChocolate.AspNetCore.Subscriptions.Messages.DataStartMessageHandler.DataStartMessageHandler(HotChocolate.Execution.IRequestExecutor! requestExecutor, HotChocolate.AspNetCore.ISocketSessionInterceptor! socketSessionInterceptor, HotChocolate.Execution.Instrumentation.IDiagnosticEvents! diagnosticEvents) -> void
HotChocolate.AspNetCore.Subscriptions.Messages.DataStartMessageHandler.DataStartMessageHandler(HotChocolate.Execution.IRequestExecutor! requestExecutor, HotChocolate.AspNetCore.ISocketSessionInterceptor! socketSessionInterceptor, HotChocolate.IErrorHandler! errorHandler, HotChocolate.Execution.Instrumentation.IDiagnosticEvents! diagnosticEvents) -> void

This file was deleted.

Original file line number Diff line number Diff line change
@@ -4,10 +4,10 @@
namespace HotChocolate.AspNetCore.Subscriptions
{
public interface ISubscriptionManager
: IEnumerable<ISubscription>
: IEnumerable<ISubscriptionSession>
, IDisposable
{
void Register(ISubscription subscription);
void Register(ISubscriptionSession subscriptionSession);

void Unregister(string subscriptionId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using HotChocolate.Execution.Processing;

namespace HotChocolate.AspNetCore.Subscriptions
{
/// <summary>
/// Represents a session with an execution engine subscription.
/// A subscription session is created within a <see cref="ISocketSession"/>.
/// Each socket session can have multiple subscription sessions open.
/// </summary>
public interface ISubscriptionSession : IDisposable
{
/// <summary>
/// An event that indicates that the underlying subscription has completed.
/// </summary>
event EventHandler? Completed;

/// <summary>
/// Gets the subscription id that the client has provided.
/// </summary>
string Id { get; }

/// <summary>
/// Gets the underlying subscription.
/// </summary>
ISubscription Subscription { get; }
}
}
Original file line number Diff line number Diff line change
@@ -36,22 +36,19 @@ private async Task KeepConnectionAliveAsync(
{
try
{
while (!_connection.Closed
&& !cancellationToken.IsCancellationRequested)
while (!_connection.Closed && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(_timeout, cancellationToken)
;
await Task.Delay(_timeout, cancellationToken);

if (!_connection.Closed)
{
await _connection.SendAsync(
KeepConnectionAliveMessage.Default.Serialize(),
cancellationToken)
;
cancellationToken);
}
}
}
catch (TaskCanceledException)
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// the message processing was canceled.
}
Original file line number Diff line number Diff line change
@@ -31,41 +31,48 @@ public void Begin(CancellationToken cancellationToken)
TaskScheduler.Default);
}

private async Task ProcessMessagesAsync(
CancellationToken cancellationToken)
private async Task ProcessMessagesAsync(CancellationToken cancellationToken)
{
while (true)
try
{
SequencePosition? position;
ReadResult result = await _reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;

do
while (true)
{
position = buffer.PositionOf(Subscription.Delimiter);
SequencePosition? position;
ReadResult result = await _reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;

if (position is not null)
do
{
await _pipeline.ProcessAsync(
_connection,
buffer.Slice(0, position.Value),
cancellationToken);
position = buffer.PositionOf(SubscriptionSession.Delimiter);

if (position is not null)
{
await _pipeline.ProcessAsync(
_connection,
buffer.Slice(0, position.Value),
cancellationToken);

// Skip the message which was read.
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
// Skip the message which was read.
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
}
}
}
while (position != null);
while (position != null);

_reader.AdvanceTo(buffer.Start, buffer.End);
_reader.AdvanceTo(buffer.Start, buffer.End);

if (result.IsCompleted)
{
break;
if (result.IsCompleted)
{
break;
}
}
}

await _reader.CompleteAsync();
catch(OperationCanceledException) when (cancellationToken.IsCancellationRequested) { }
finally
{
// reader should be completed always, so that related pipe writer can
// stop write new messages
await _reader.CompleteAsync();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -18,20 +18,26 @@ public MessageReceiver(ISocketConnection connection, PipeWriter writer)

public async Task ReceiveAsync(CancellationToken cancellationToken)
{
while (!_connection.Closed &&
!cancellationToken.IsCancellationRequested)
try
{
await _connection.ReceiveAsync(_writer, cancellationToken);
await WriteMessageDelimiterAsync(cancellationToken);
while (!_connection.Closed && !cancellationToken.IsCancellationRequested)
{
await _connection.ReceiveAsync(_writer, cancellationToken);
await WriteMessageDelimiterAsync(cancellationToken);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { }
finally
{
// writer should be always completed
await _writer.CompleteAsync();
}
await _writer.CompleteAsync();
}

private async Task WriteMessageDelimiterAsync(
CancellationToken cancellationToken)
private async Task WriteMessageDelimiterAsync(CancellationToken cancellationToken)
{
Memory<byte> memory = _writer.GetMemory(1);
memory.Span[0] = Subscription.Delimiter;
memory.Span[0] = SubscriptionSession.Delimiter;
_writer.Advance(1);
await _writer.FlushAsync(cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -8,22 +8,21 @@ namespace HotChocolate.AspNetCore.Subscriptions
internal static class MessageSerialization
{
private static readonly JsonSerializerSettings _jsonSettings =
new JsonSerializerSettings
new()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};

private static readonly UTF8Encoding _encoding = new UTF8Encoding();
private static readonly UTF8Encoding _encoding = new();

private static readonly byte[] _keepConnectionAliveMessage =
SerializeInternal(KeepConnectionAliveMessage.Default);

private static readonly byte[] _acceptConnectionMessage =
SerializeInternal(AcceptConnectionMessage.Default);

public static byte[] Serialize(
this OperationMessage message)
public static byte[] Serialize(this OperationMessage message)
{
if (message is KeepConnectionAliveMessage)
{
Original file line number Diff line number Diff line change
@@ -1,58 +1,156 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.AspNetCore.Properties;
using HotChocolate.Execution;
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing;
using static HotChocolate.AspNetCore.ThrowHelper;
using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;

namespace HotChocolate.AspNetCore.Subscriptions.Messages
{
public sealed class DataStartMessageHandler
: MessageHandler<DataStartMessage>
public sealed class DataStartMessageHandler : MessageHandler<DataStartMessage>
{
private readonly IRequestExecutor _requestExecutor;
private readonly ISocketSessionInterceptor _socketSessionInterceptor;
private readonly IErrorHandler _errorHandler;
private readonly IDiagnosticEvents _diagnosticEvents;

public DataStartMessageHandler(
IRequestExecutor requestExecutor,
ISocketSessionInterceptor socketSessionInterceptor)
ISocketSessionInterceptor socketSessionInterceptor,
IErrorHandler errorHandler,
IDiagnosticEvents diagnosticEvents)
{
_requestExecutor = requestExecutor ??
throw new ArgumentNullException(nameof(requestExecutor));
_socketSessionInterceptor = socketSessionInterceptor ??
throw new ArgumentNullException(nameof(socketSessionInterceptor));
_errorHandler = errorHandler ??
throw new ArgumentNullException(nameof(errorHandler));
_diagnosticEvents = diagnosticEvents ??
throw new ArgumentNullException(nameof(diagnosticEvents));
}

protected override async Task HandleAsync(
ISocketConnection connection,
DataStartMessage message,
CancellationToken cancellationToken)
{
IQueryRequestBuilder requestBuilder =
QueryRequestBuilder.From(message.Payload)
.SetServices(connection.RequestServices);
var session = new CancellationTokenSource();
var combined = CancellationTokenSource.CreateLinkedTokenSource(
session.Token, cancellationToken);
var sessionIsHandled = false;

await _socketSessionInterceptor.OnRequestAsync(
connection, requestBuilder, cancellationToken);
IExecutionResult result = await ExecuteAsync(combined.Token);

IExecutionResult result = await _requestExecutor.ExecuteAsync(
requestBuilder.Create(), cancellationToken);
try
{
switch (result)
{
case SubscriptionResult subscriptionResult:
// first we add the cts to the result so that they are disposed when the
// subscription is disposed.
subscriptionResult.RegisterDisposable(combined);

// while a subscription result must be disposed we are not handling it here
// and leave this responsibility to the subscription session.
ISubscription subscription = GetSubscription(result);

var subscriptionSession = new SubscriptionSession(
session,
connection,
subscriptionResult,
subscription,
_diagnosticEvents,
message.Id);

connection.Subscriptions.Register(subscriptionSession);
sessionIsHandled = true;
break;

case IResponseStream streamResult:
// stream results represent deferred execution streams that use execution
// resources. We need to ensure that these are disposed when we are
// finished.
await using (streamResult)
{
await HandleStreamResultAsync(
connection,
message,
streamResult,
cancellationToken);
}

break;

case IQueryResult queryResult:
// query results use pooled memory an need to be disposed after we have
// used them.
using (queryResult)
{
await HandleQueryResultAsync(
connection,
message,
queryResult,
cancellationToken);
}

break;

default:
throw DataStartMessageHandler_RequestTypeNotSupported();
}
}
finally
{
if (!sessionIsHandled)
{
session.Dispose();
combined.Dispose();
}
}

async ValueTask<IExecutionResult> ExecuteAsync(CancellationToken cancellationToken)
{
try
{
IQueryRequestBuilder requestBuilder =
QueryRequestBuilder.From(message.Payload)
.SetServices(connection.RequestServices);

await _socketSessionInterceptor.OnRequestAsync(
connection, requestBuilder, cancellationToken);

return await _requestExecutor.ExecuteAsync(
requestBuilder.Create(), cancellationToken);
}
catch (Exception ex)
{
IErrorBuilder error = _errorHandler.CreateUnexpectedError(ex);
return QueryResultBuilder.CreateError(error.Build());
}
}
}

switch (result)
private static async Task HandleStreamResultAsync(
ISocketConnection connection,
DataStartMessage message,
IResponseStream responseStream,
CancellationToken cancellationToken)
{
await foreach (IQueryResult queryResult in responseStream.ReadResultsAsync()
.WithCancellation(cancellationToken))
{
case IResponseStream responseStream:
var subscription = new Subscription(connection, responseStream, message.Id);
connection.Subscriptions.Register(subscription);
break;

case IQueryResult queryResult:
using (queryResult)
await HandleQueryResultAsync(
connection, message, queryResult, cancellationToken);
break;

default:
throw DataStartMessageHandler_RequestTypeNotSupported();
await connection.SendAsync(
new DataResultMessage(message.Id, queryResult).Serialize(),
cancellationToken);
}

await connection.SendAsync(
new DataCompleteMessage(message.Id).Serialize(),
cancellationToken);
}

private static async Task HandleQueryResultAsync(
@@ -69,5 +167,17 @@ await connection.SendAsync(
new DataCompleteMessage(message.Id).Serialize(),
cancellationToken);
}

private ISubscription GetSubscription(IExecutionResult result)
{
if (result.ContextData is not null &&
result.ContextData.TryGetValue(WellKnownContextData.Subscription, out var value) &&
value is ISubscription subscription)
{
return subscription;
}

throw new InvalidOperationException(DataStartMessageHandler_Not_A_SubscriptionResult);
}
}
}
Original file line number Diff line number Diff line change
@@ -3,8 +3,7 @@

namespace HotChocolate.AspNetCore.Subscriptions.Messages
{
public sealed class DataStopMessageHandler
: MessageHandler<DataStopMessage>
public sealed class DataStopMessageHandler : MessageHandler<DataStopMessage>
{
protected override Task HandleAsync(
ISocketConnection connection,
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ await _socketSessionInterceptor.OnConnectAsync(
}
else
{
var rejectMessage = connectionStatus.Extensions == null
RejectConnectionMessage rejectMessage = connectionStatus.Extensions == null
? new RejectConnectionMessage(connectionStatus.Message)
: new RejectConnectionMessage(
connectionStatus.Message,

This file was deleted.

Original file line number Diff line number Diff line change
@@ -8,8 +8,7 @@ namespace HotChocolate.AspNetCore.Subscriptions
{
public class SubscriptionManager : ISubscriptionManager
{
private readonly ConcurrentDictionary<string, ISubscription> _subs =
new ConcurrentDictionary<string, ISubscription>();
private readonly ConcurrentDictionary<string, ISubscriptionSession> _subs = new();
private readonly ISocketConnection _connection;
private bool _disposed;

@@ -18,23 +17,23 @@ public SubscriptionManager(ISocketConnection connection)
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
}

public void Register(ISubscription subscription)
public void Register(ISubscriptionSession subscriptionSession)
{
if (subscription == null)
if (subscriptionSession == null)
{
throw new ArgumentNullException(nameof(subscription));
throw new ArgumentNullException(nameof(subscriptionSession));
}

if (_disposed)
{
throw new ObjectDisposedException(nameof(SubscriptionManager));
}

if (_subs.TryAdd(subscription.Id, subscription))
if (_subs.TryAdd(subscriptionSession.Id, subscriptionSession))
{
subscription.Completed += (sender, eventArgs) =>
subscriptionSession.Completed += (_, _) =>
{
Unregister(subscription.Id);
Unregister(subscriptionSession.Id);
};
}
}
@@ -51,7 +50,7 @@ public void Unregister(string subscriptionId)
throw new ObjectDisposedException(nameof(SubscriptionManager));
}

if (_subs.TryRemove(subscriptionId, out ISubscription? subscription))
if (_subs.TryRemove(subscriptionId, out ISubscriptionSession? subscription))
{
subscription.Dispose();
}
@@ -69,10 +68,10 @@ protected virtual void Dispose(bool disposing)
{
if (disposing && _subs.Count > 0)
{
ISubscription?[] subs = _subs.Values.ToArray();
ISubscriptionSession?[] subs = _subs.Values.ToArray();
_subs.Clear();

for (int i = 0; i < subs.Length; i++)
for (var i = 0; i < subs.Length; i++)
{
subs[i]?.Dispose();
subs[i] = null;
@@ -82,7 +81,7 @@ protected virtual void Dispose(bool disposing)
}
}

public IEnumerator<ISubscription> GetEnumerator()
public IEnumerator<ISubscriptionSession> GetEnumerator()
{
return _subs.Values.GetEnumerator();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.AspNetCore.Subscriptions.Messages;
using HotChocolate.Execution;
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing;
using static HotChocolate.AspNetCore.ErrorHelper;

namespace HotChocolate.AspNetCore.Subscriptions
{
internal sealed class SubscriptionSession : ISubscriptionSession
{
internal const byte Delimiter = 0x07;
private readonly CancellationTokenSource _session;
private readonly CancellationToken _sessionToken;
private readonly ISocketConnection _connection;
private readonly IResponseStream _responseStream;
private readonly IDiagnosticEvents _diagnosticEvents;
private bool _disposed;

/// <inheritdoc />
public event EventHandler? Completed;

public SubscriptionSession(
CancellationTokenSource session,
ISocketConnection connection,
IResponseStream responseStream,
ISubscription subscription,
IDiagnosticEvents diagnosticEvents,
string clientSubscriptionId)
{
_session = session ??
throw new ArgumentNullException(nameof(session));
_connection = connection ??
throw new ArgumentNullException(nameof(connection));
_responseStream = responseStream ??
throw new ArgumentNullException(nameof(responseStream));
_diagnosticEvents = diagnosticEvents ??
throw new ArgumentNullException(nameof(diagnosticEvents));
Subscription = subscription ??
throw new ArgumentNullException(nameof(subscription));
Id = clientSubscriptionId ??
throw new ArgumentNullException(nameof(clientSubscriptionId));

_sessionToken = _session.Token;

Task.Factory.StartNew(
SendResultsAsync,
_sessionToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}

/// <inheritdoc />
public string Id { get; }

/// <inheritdoc />
public ISubscription Subscription { get; }

private async Task SendResultsAsync()
{
await using IResponseStream responseStream = _responseStream;
CancellationToken cancellationToken = _sessionToken;

try
{
await foreach (IQueryResult result in
responseStream.ReadResultsAsync().WithCancellation(cancellationToken))
{
using (result)
{
if (!cancellationToken.IsCancellationRequested && !_connection.Closed)
{
await _connection.SendAsync(
new DataResultMessage(Id, result),
cancellationToken);
}
}
}

if (!cancellationToken.IsCancellationRequested && !_connection.Closed)
{
await _connection.SendAsync(new DataCompleteMessage(Id), cancellationToken);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { }
catch (ObjectDisposedException) { }
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
{
if (!_connection.Closed)
{
try
{
try
{
await _connection.SendAsync(
new DataResultMessage(Id, UnknownSubscriptionError(ex)),
cancellationToken);
}
finally
{
await _connection.SendAsync(
new DataCompleteMessage(Id),
cancellationToken);
}
}
catch
{
// suppress all errors, so original exception can be rethrown
}
}

_diagnosticEvents.SubscriptionTransportError(Subscription, ex);
}
finally
{
// completed should be always invoked to be ensure that disposed subscription is
// removed from subscription manager
Completed?.Invoke(this, EventArgs.Empty);
Dispose();
}
}

public void Dispose()
{
if (!_disposed)
{
if (!_session.IsCancellationRequested)
{
_session.Cancel();
}

_session.Dispose();
_disposed = true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -8,8 +8,7 @@

namespace HotChocolate.AspNetCore.Subscriptions
{
public class WebSocketConnection
: ISocketConnection
public class WebSocketConnection : ISocketConnection
{
private const string _protocol = "graphql-ws";
private const int _maxMessageSize = 1024 * 4;
Original file line number Diff line number Diff line change
@@ -2,13 +2,14 @@
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.AspNetCore.Properties;
using Microsoft.AspNetCore.Http;

namespace HotChocolate.AspNetCore.Subscriptions
{
public class WebSocketSession : ISocketSession
{
private readonly Pipe _pipe = new Pipe();
private readonly Pipe _pipe = new();
private readonly ISocketConnection _connection;
private readonly KeepConnectionAliveJob _keepAlive;
private readonly MessageProcessor _messageProcessor;
@@ -41,19 +42,31 @@ public async Task HandleAsync(CancellationToken cancellationToken)
_messageProcessor.Begin(cts.Token);
await _messageReceiver.ReceiveAsync(cts.Token);
}
catch(OperationCanceledException)
{
// OperationCanceledException are catched and will not
catch(OperationCanceledException) when (cts.Token.IsCancellationRequested)
{
// OperationCanceledException are caught and will not
// bubble further. We will just close the current subscription
// context.
}
finally
{
cts.Cancel();
await _connection.CloseAsync(
"Session ended.",
try
{
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}

await _connection.CloseAsync(
AspNetCoreResources.WebSocketSession_SessionEnded,
SocketCloseStatus.NormalClosure,
CancellationToken.None);
}
catch
{
// original exception must not be lost if new exception occurs
// during closing session
}
}
}
}
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\Core\test\Utilities\HotChocolate.Tests.Utilities.csproj" />
<ProjectReference Include="..\AspNetCore.Tests\HotChocolate.AspNetCore.Tests.csproj" />
<ProjectReference Include="..\..\..\Core\test\StarWars\HotChocolate.StarWars.csproj" />
<ProjectReference Include="..\..\..\Core\test\StarWars\HotChocolate.StarWars.Tests.csproj" />
<ProjectReference Include="..\..\src\AspNetCore.Authorization\HotChocolate.AspNetCore.Authorization.csproj" />
</ItemGroup>

Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\Core\src\Types.Scalars.Upload\HotChocolate.Types.Scalars.Upload.csproj" />
<ProjectReference Include="..\..\..\Core\test\StarWars\HotChocolate.StarWars.csproj" />
<ProjectReference Include="..\..\..\Core\test\StarWars\HotChocolate.StarWars.Tests.csproj" />
<ProjectReference Include="..\..\src\AspNetCore\HotChocolate.AspNetCore.csproj" />
</ItemGroup>

Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@
using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Execution;
using HotChocolate.Language;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Snapshooter;
using Snapshooter.Xunit;
using Xunit;
@@ -697,5 +699,41 @@ await server.GetStoreActivePersistedQueryAsync(
resultB
}.MatchSnapshot();
}

[Fact]
public async Task Throw_Custom_GraphQL_Error()
{
// arrange
TestServer server = CreateStarWarsServer(
configureServices: s => s.AddGraphQLServer()
.AddHttpRequestInterceptor<ErrorRequestInterceptor>());

// act
ClientQueryResult result =
await server.GetAsync(new ClientQueryRequest
{
Query = @"
{
hero {
name
}
}"
});

// assert
result.MatchSnapshot();
}

public class ErrorRequestInterceptor : DefaultHttpRequestInterceptor
{
public override ValueTask OnCreateAsync(
HttpContext context,
IRequestExecutor requestExecutor,
IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
throw new GraphQLException("MyCustomError");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.TestHost;
using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Execution;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Snapshooter;
using Snapshooter.Xunit;
@@ -68,8 +71,8 @@ public async Task Simple_IsAlive_Test_On_Non_GraphQL_Path()

// act
ClientQueryResult result = await server.PostAsync(
new ClientQueryRequest { Query = "{ __typename }" },
"/foo");
new ClientQueryRequest { Query = "{ __typename }" },
"/foo");

// assert
result.MatchSnapshot();
@@ -135,10 +138,7 @@ await server.PostAsync(new ClientQueryRequest
name
}
}",
Variables = new Dictionary<string, object>
{
{ "episode", "NEW_HOPE" }
}
Variables = new Dictionary<string, object> { { "episode", "NEW_HOPE" } }
});

// assert
@@ -161,10 +161,7 @@ query h($id: String!) {
name
}
}",
Variables = new Dictionary<string, object>
{
{ "id", "1000" }
}
Variables = new Dictionary<string, object> { { "id", "1000" } }
});

// assert
@@ -224,8 +221,7 @@ mutation CreateReviewForEpisode(
"review",
new Dictionary<string, object>
{
{ "stars", 5 },
{ "commentary", "This is a great movie!" },
{ "stars", 5 }, { "commentary", "This is a great movie!" },
}
}
}
@@ -260,8 +256,7 @@ mutation CreateReviewForEpisode(
"review",
new Dictionary<string, object>
{
{ "stars", 5 },
{ "commentary", "This is a great movie!" },
{ "stars", 5 }, { "commentary", "This is a great movie!" },
}
}
}
@@ -390,10 +385,7 @@ await server.PostAsync(new ClientQueryRequest
name
}
}",
Variables = new Dictionary<string, object>
{
{ "episode", "NEW_HOPE" }
}
Variables = new Dictionary<string, object> { { "episode", "NEW_HOPE" } }
});

// assert
@@ -463,11 +455,7 @@ await server.PostAsync(new ClientQueryRequest
"/arguments");

// assert
new
{
double.MaxValue,
result
}.MatchSnapshot();
new { double.MaxValue, result }.MatchSnapshot();
}

[Fact]
@@ -489,11 +477,7 @@ await server.PostAsync(new ClientQueryRequest
"/arguments");

// assert
new
{
double.MinValue,
result
}.MatchSnapshot();
new { double.MinValue, result }.MatchSnapshot();
}

[Fact]
@@ -515,11 +499,7 @@ await server.PostAsync(new ClientQueryRequest
"/arguments");

// assert
new
{
decimal.MaxValue,
result
}.MatchSnapshot();
new { decimal.MaxValue, result }.MatchSnapshot();
}

[Fact]
@@ -541,11 +521,7 @@ await server.PostAsync(new ClientQueryRequest
"/arguments");

// assert
new
{
decimal.MinValue,
result
}.MatchSnapshot();
new { decimal.MinValue, result }.MatchSnapshot();
}

[Fact]
@@ -778,5 +754,41 @@ query getHuman {
// assert
result.MatchSnapshot();
}

[Fact]
public async Task Throw_Custom_GraphQL_Error()
{
// arrange
TestServer server = CreateStarWarsServer(
configureServices: s => s.AddGraphQLServer()
.AddHttpRequestInterceptor<ErrorRequestInterceptor>());

// act
ClientQueryResult result =
await server.PostAsync(new ClientQueryRequest
{
Query = @"
{
hero {
name
}
}"
});

// assert
result.MatchSnapshot();
}

public class ErrorRequestInterceptor : DefaultHttpRequestInterceptor
{
public override ValueTask OnCreateAsync(
HttpContext context,
IRequestExecutor requestExecutor,
IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
throw new GraphQLException("MyCustomError");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -3,11 +3,11 @@
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.AspNetCore.Utilities;
using Microsoft.Extensions.DependencyInjection;
using HotChocolate.Execution;
using HotChocolate.Language;
using HotChocolate.StarWars;
using Moq;
using Xunit;

namespace HotChocolate.AspNetCore.Subscriptions.Messages
@@ -18,13 +18,18 @@ public class DataStartMessageHandlerTests
public void CanHandle_DataStartMessage_True()
{
// arrange
var errorHandler = new Mock<IErrorHandler>();
var interceptor = new DefaultSocketSessionInterceptor();
IRequestExecutor executor = SchemaBuilder.New()
.AddStarWarsTypes()
.Create()
.MakeExecutable();
DocumentNode query = Utf8GraphQLParser.Parse("{ hero { name } }");
var handler = new DataStartMessageHandler(executor, interceptor);
var handler = new DataStartMessageHandler(
executor,
interceptor,
errorHandler.Object,
new NoopDiagnosticEvents());

var message = new DataStartMessage(
"123",
@@ -42,11 +47,16 @@ public void CanHandle_KeepAliveMessage_False()
{
// arrange
var interceptor = new DefaultSocketSessionInterceptor();
var errorHandler = new Mock<IErrorHandler>();
IRequestExecutor executor = SchemaBuilder.New()
.AddStarWarsTypes()
.Create()
.MakeExecutable();
var handler = new DataStartMessageHandler(executor, interceptor);
var handler = new DataStartMessageHandler(
executor,
interceptor,
errorHandler.Object,
new NoopDiagnosticEvents());
KeepConnectionAliveMessage message = KeepConnectionAliveMessage.Default;

// act
@@ -60,6 +70,8 @@ public void CanHandle_KeepAliveMessage_False()
public async Task Handle_Query_DataReceived_And_Completed()
{
// arrange
var errorHandler = new Mock<IErrorHandler>();

IServiceProvider services = new ServiceCollection()
.AddGraphQL()
.AddStarWarsTypes()
@@ -75,7 +87,11 @@ public async Task Handle_Query_DataReceived_And_Completed()
var interceptor = new SocketSessionInterceptorMock();
var connection = new SocketConnectionMock { RequestServices = services };
DocumentNode query = Utf8GraphQLParser.Parse("{ hero { name } }");
var handler = new DataStartMessageHandler(executor, interceptor);
var handler = new DataStartMessageHandler(
executor,
interceptor,
errorHandler.Object,
new NoopDiagnosticEvents());
var message = new DataStartMessage("123", new GraphQLRequest(query));

var result = (IReadOnlyQueryResult)await executor.ExecuteAsync(
@@ -107,6 +123,8 @@ await handler.HandleAsync(
public async Task Handle_Query_With_Inter_DataReceived_And_Completed()
{
// arrange
var errorHandler = new Mock<IErrorHandler>();

IServiceProvider services = new ServiceCollection()
.AddGraphQL()
.AddStarWarsTypes()
@@ -122,7 +140,11 @@ public async Task Handle_Query_With_Inter_DataReceived_And_Completed()
var interceptor = new SocketSessionInterceptorMock();
var connection = new SocketConnectionMock { RequestServices = services };
DocumentNode query = Utf8GraphQLParser.Parse("{ hero { name } }");
var handler = new DataStartMessageHandler(executor, interceptor);
var handler = new DataStartMessageHandler(
executor,
interceptor,
errorHandler.Object,
new NoopDiagnosticEvents());
var message = new DataStartMessage("123", new GraphQLRequest(query));

var result = (IReadOnlyQueryResult)await executor.ExecuteAsync(
@@ -155,6 +177,8 @@ await handler.HandleAsync(
public async Task Handle_Subscription_DataReceived_And_Completed()
{
// arrange
var errorHandler = new Mock<IErrorHandler>();

IServiceProvider services = new ServiceCollection()
.AddGraphQL()
.AddStarWarsTypes()
@@ -171,7 +195,11 @@ public async Task Handle_Subscription_DataReceived_And_Completed()
var connection = new SocketConnectionMock { RequestServices = services };
DocumentNode query = Utf8GraphQLParser.Parse(
"subscription { onReview(episode: NEW_HOPE) { stars } }");
var handler = new DataStartMessageHandler(executor, interceptor);
var handler = new DataStartMessageHandler(
executor,
interceptor,
errorHandler.Object,
new NoopDiagnosticEvents());
var message = new DataStartMessage("123", new GraphQLRequest(query));

// act
Original file line number Diff line number Diff line change
@@ -3,7 +3,9 @@
using Xunit;
using HotChocolate.StarWars;
using HotChocolate.Execution;
using HotChocolate.Execution.Processing;
using Microsoft.Extensions.DependencyInjection;
using Moq;

namespace HotChocolate.AspNetCore.Subscriptions.Messages
{
@@ -43,6 +45,7 @@ public async Task Handle_Stop_Subscription()
{
// arrange
var connection = new SocketConnectionMock();
var subscription = new Mock<ISubscription>();

IRequestExecutor executor = await new ServiceCollection()
.AddGraphQL()
@@ -58,8 +61,14 @@ public async Task Handle_Stop_Subscription()
(IResponseStream)await executor.ExecuteAsync(
"subscription { onReview(episode: NEW_HOPE) { stars } }");

var subscription = new Subscription(connection, stream, "123");
connection.Subscriptions.Register(subscription);
var subscriptionSession = new SubscriptionSession(
new CancellationTokenSource(),
connection,
stream,
subscription.Object,
new NoopDiagnosticEvents(),
"123");
connection.Subscriptions.Register(subscriptionSession);

var handler = new DataStopMessageHandler();
var message = new DataStopMessage("123");
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using HotChocolate.Execution;
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing;
using HotChocolate.Resolvers;

namespace HotChocolate.AspNetCore.Subscriptions.Messages
{
internal sealed class NoopDiagnosticEvents
: IDiagnosticEvents
, IActivityScope
{
public IActivityScope ExecuteRequest(IRequestContext context) => this;

public void RequestError(IRequestContext context, Exception exception)
{
}

public IActivityScope ParseDocument(IRequestContext context) => this;

public void SyntaxError(IRequestContext context, IError error)
{
}

public IActivityScope ValidateDocument(IRequestContext context) => this;

public void ValidationErrors(IRequestContext context, IReadOnlyList<IError> errors)
{
}

public IActivityScope ResolveFieldValue(IMiddlewareContext context) => this;

public void ResolverError(IMiddlewareContext context, IError error)
{
}

public IActivityScope RunTask(IExecutionTask task) => this;

public void TaskError(IExecutionTask task, IError error)
{
}

public IActivityScope ExecuteSubscription(ISubscription subscription) => this;

public IActivityScope OnSubscriptionEvent(SubscriptionEventContext context) => this;

public void SubscriptionEventResult(SubscriptionEventContext context, IQueryResult result)
{
}

public void SubscriptionEventError(SubscriptionEventContext context, Exception exception)
{
}

public void SubscriptionTransportError(ISubscription subscription, Exception exception)
{
}

public void AddedDocumentToCache(IRequestContext context)
{
}

public void RetrievedDocumentFromCache(IRequestContext context)
{
}

public void RetrievedDocumentFromStorage(IRequestContext context)
{
}

public void AddedOperationToCache(IRequestContext context)
{
}

public void RetrievedOperationFromCache(IRequestContext context)
{
}

public void BatchDispatched(IRequestContext context)
{
}

public void ExecutorCreated(string name, IRequestExecutor executor)
{
}

public void ExecutorEvicted(string name, IRequestExecutor executor)
{
}

public void Dispose()
{
}
}
}
Loading