diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln
index 89782ecc..c8b3bac0 100644
--- a/GraphQL.Client.sln
+++ b/GraphQL.Client.sln
@@ -67,6 +67,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{98D4
.github\dependabot.yml = .github\dependabot.yml
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Client.ApiTests", "tests\GraphQL.Client.ApiTests\GraphQL.Client.ApiTests.csproj", "{DB0C542C-D0CC-4AD5-95AF-50A208C6A885}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -129,6 +131,10 @@ Global
{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB0C542C-D0CC-4AD5-95AF-50A208C6A885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB0C542C-D0CC-4AD5-95AF-50A208C6A885}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB0C542C-D0CC-4AD5-95AF-50A208C6A885}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB0C542C-D0CC-4AD5-95AF-50A208C6A885}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -150,6 +156,7 @@ Global
{7FFFEC00-D751-4FFC-9FD4-E91858F9A1C5} = {47C98B55-08F1-4428-863E-2C5C876DEEFE}
{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD} = {89AD33AB-64F6-4F82-822F-21DF7A10CEC0}
{98D4DDDD-DE15-4997-B888-9BC806C7416C} = {63F75859-4698-4EDE-8B70-4ACBB8BC425A}
+ {DB0C542C-D0CC-4AD5-95AF-50A208C6A885} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {387AC1AC-F90C-4EF8-955A-04D495C75AF4}
diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs
index 9ff0c600..322abb87 100644
--- a/src/GraphQL.Client/GraphQLHttpRequest.cs
+++ b/src/GraphQL.Client/GraphQLHttpRequest.cs
@@ -21,13 +21,6 @@ public GraphQLHttpRequest(GraphQLRequest other)
{
}
- ///
- /// Allows to preprocess a before it is sent, i.e. add custom headers
- ///
- [IgnoreDataMember]
- [Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")]
- public Action PreprocessHttpRequestMessage { get; set; } = message => { };
-
///
/// Creates a from this .
/// Used by to convert GraphQL requests when sending them as regular HTTP requests.
@@ -48,9 +41,6 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions
if (options.DefaultUserAgentRequestHeader != null)
message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader);
-#pragma warning disable CS0618 // Type or member is obsolete
- PreprocessHttpRequestMessage(message);
-#pragma warning restore CS0618 // Type or member is obsolete
return message;
}
}
diff --git a/tests/GraphQL.Client.ApiTests/ApiApprovalTests.cs b/tests/GraphQL.Client.ApiTests/ApiApprovalTests.cs
new file mode 100644
index 00000000..fafa11ab
--- /dev/null
+++ b/tests/GraphQL.Client.ApiTests/ApiApprovalTests.cs
@@ -0,0 +1,89 @@
+using System.Diagnostics;
+using System.Reflection;
+using System.Xml.Linq;
+using GraphQL.Client.Abstractions;
+using GraphQL.Client.Abstractions.Websocket;
+using GraphQL.Client.Http;
+using GraphQL.Client.LocalExecution;
+using GraphQL.Client.Serializer.Newtonsoft;
+using GraphQL.Client.Serializer.SystemTextJson;
+using PublicApiGenerator;
+using Shouldly;
+using Xunit;
+
+namespace GraphQL.ApiTests;
+
+public class ApiApprovalTests
+{
+ [Theory]
+ [InlineData(typeof(NewtonsoftJsonSerializer))]
+ [InlineData(typeof(SystemTextJsonSerializer))]
+ [InlineData(typeof(GraphQLRequest))]
+ [InlineData(typeof(GraphQLLocalExecutionClient))]
+ [InlineData(typeof(IGraphQLWebSocketClient))]
+ [InlineData(typeof(IGraphQLClient))]
+ [InlineData(typeof(GraphQLHttpRequest))]
+ public void PublicApi(Type type)
+ {
+ string baseDir = AppDomain.CurrentDomain.BaseDirectory;
+ string projectName = type.Assembly.GetName().Name!;
+ string testDir = Path.Combine(baseDir, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..");
+ string projectDir = Path.Combine(testDir, "..");
+ string srcDir = Path.Combine(projectDir, "..", "src");
+ string buildDir = Path.Combine(srcDir, projectName, "bin", Environment.GetEnvironmentVariable("CI") == null ? "Debug" : "Release");
+ Debug.Assert(Directory.Exists(buildDir), $"Directory '{buildDir}' doesn't exist");
+ string csProject = Path.Combine(srcDir, projectName, projectName + ".csproj");
+ var project = XDocument.Load(csProject);
+ string[] tfms = project.Descendants("TargetFrameworks").Union(project.Descendants("TargetFramework")).First().Value.Split(";", StringSplitOptions.RemoveEmptyEntries);
+
+ // There may be old stuff from earlier builds like net45, netcoreapp3.0, etc. so filter it out
+ string[] actualTfmDirs = Directory.GetDirectories(buildDir).Where(dir => tfms.Any(tfm => dir.EndsWith(tfm))).ToArray();
+ Debug.Assert(actualTfmDirs.Length > 0, $"Directory '{buildDir}' doesn't contain subdirectories matching {string.Join(";", tfms)}");
+
+ (string tfm, string content)[] publicApi = actualTfmDirs.Select(tfmDir => (new DirectoryInfo(tfmDir).Name.Replace(".", ""), Assembly.LoadFile(Path.Combine(tfmDir, projectName + ".dll")).GeneratePublicApi(new ApiGeneratorOptions
+ {
+ IncludeAssemblyAttributes = false,
+ //AllowNamespacePrefixes = new[] { "Microsoft.Extensions.DependencyInjection" },
+ ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute", "System.Diagnostics.CodeAnalysis.AllowNullAttribute" }
+ }) + Environment.NewLine)).ToArray();
+
+ if (publicApi.DistinctBy(item => item.content).Count() == 1)
+ {
+ AutoApproveOrFail(publicApi[0].content, "");
+ }
+ else
+ {
+ foreach (var item in publicApi.ToLookup(item => item.content))
+ {
+ AutoApproveOrFail(item.Key, string.Join("+", item.Select(x => x.tfm).OrderBy(x => x)));
+ }
+ }
+
+ // Approval test should (re)generate approved.txt files locally if needed.
+ // Approval test should fail on CI.
+ // https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
+ void AutoApproveOrFail(string publicApi, string folder)
+ {
+ string file = null!;
+
+ try
+ {
+ publicApi.ShouldMatchApproved(options => options.SubFolder(folder).NoDiff().WithFilenameGenerator((testMethodInfo, discriminator, fileType, fileExtension) => file = $"{type.Assembly.GetName().Name}.{fileType}.{fileExtension}"));
+ }
+ catch (ShouldMatchApprovedException) when (Environment.GetEnvironmentVariable("CI") == null)
+ {
+ string? received = Path.Combine(testDir, folder, file);
+ string? approved = received.Replace(".received.txt", ".approved.txt");
+ if (File.Exists(received) && File.Exists(approved))
+ {
+ File.Copy(received, approved, overwrite: true);
+ File.Delete(received);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/GraphQL.Client.ApiTests/GraphQL.Client.Abstractions.Websocket.approved.txt b/tests/GraphQL.Client.ApiTests/GraphQL.Client.Abstractions.Websocket.approved.txt
new file mode 100644
index 00000000..7616d52e
--- /dev/null
+++ b/tests/GraphQL.Client.ApiTests/GraphQL.Client.Abstractions.Websocket.approved.txt
@@ -0,0 +1,86 @@
+namespace GraphQL.Client.Abstractions.Websocket
+{
+ public static class GraphQLWebSocketMessageType
+ {
+ public const string GQL_COMPLETE = "complete";
+ public const string GQL_CONNECTION_ACK = "connection_ack";
+ public const string GQL_CONNECTION_ERROR = "connection_error";
+ public const string GQL_CONNECTION_INIT = "connection_init";
+ public const string GQL_CONNECTION_KEEP_ALIVE = "ka";
+ public const string GQL_CONNECTION_TERMINATE = "connection_terminate";
+ public const string GQL_DATA = "data";
+ public const string GQL_ERROR = "error";
+ public const string GQL_NEXT = "next";
+ public const string GQL_PING = "ping";
+ public const string GQL_PONG = "pong";
+ public const string GQL_START = "start";
+ public const string GQL_STOP = "stop";
+ public const string GQL_SUBSCRIBE = "subscribe";
+ }
+ public class GraphQLWebSocketRequest : System.Collections.Generic.Dictionary, System.IEquatable
+ {
+ public const string ID_KEY = "id";
+ public const string PAYLOAD_KEY = "payload";
+ public const string TYPE_KEY = "type";
+ public GraphQLWebSocketRequest() { }
+ public string Id { get; set; }
+ public object? Payload { get; set; }
+ public string Type { get; set; }
+ public bool Equals(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketRequest other) { }
+ public override bool Equals(object obj) { }
+ public override int GetHashCode() { }
+ public void SendCanceled() { }
+ public void SendCompleted() { }
+ public void SendFailed(System.Exception e) { }
+ public System.Threading.Tasks.Task SendTask() { }
+ public static bool operator !=(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketRequest request1, GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketRequest request2) { }
+ public static bool operator ==(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketRequest request1, GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketRequest request2) { }
+ }
+ public class GraphQLWebSocketResponse : System.IEquatable
+ {
+ public GraphQLWebSocketResponse() { }
+ public string Id { get; set; }
+ public string Type { get; set; }
+ public bool Equals(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse other) { }
+ public override bool Equals(object obj) { }
+ public override int GetHashCode() { }
+ public static bool operator !=(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse response1, GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse response2) { }
+ public static bool operator ==(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse response1, GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse response2) { }
+ }
+ public class GraphQLWebSocketResponse : GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse, System.IEquatable>
+ {
+ public GraphQLWebSocketResponse() { }
+ public TPayload Payload { get; set; }
+ public bool Equals(GraphQL.Client.Abstractions.Websocket.GraphQLWebSocketResponse? other) { }
+ public override bool Equals(object? obj) { }
+ public override int GetHashCode() { }
+ }
+ public enum GraphQLWebsocketConnectionState
+ {
+ Disconnected = 0,
+ Connecting = 1,
+ Connected = 2,
+ }
+ public interface IGraphQLWebSocketClient : GraphQL.Client.Abstractions.IGraphQLClient
+ {
+ System.IObservable