From 1c6a5a94ac365361ba0189e2b0675b34cf8509ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Mon, 26 Oct 2026 21:26:51 +0100 Subject: [PATCH 1/6] Key the safe-mode schema cache by schema type Two clients that carry their own copy of the runtime share the app's IMemoryCache. With the same endpoint they produced the same key, and the second read the first's GraphQLSchema and failed with InvalidCastException. --- src/Linq2GraphQL.Client/GraphClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Linq2GraphQL.Client/GraphClient.cs b/src/Linq2GraphQL.Client/GraphClient.cs index d340f105..b97f06b0 100644 --- a/src/Linq2GraphQL.Client/GraphClient.cs +++ b/src/Linq2GraphQL.Client/GraphClient.cs @@ -186,7 +186,9 @@ public async Task GetSchemaForSafeModeAsync(CancellationToken can return null; } - var cacheKey = $"Linq2GraphQL_Schema:{HttpClient.BaseAddress}:{includeDeprecated}:{options.Value.SchemaCacheKey}"; + // The schema type is part of the key: clients that carry their own copy of the runtime + // share the app's IMemoryCache but cannot read each other's GraphQLSchema. + var cacheKey = $"Linq2GraphQL_Schema:{typeof(GraphQLSchema).AssemblyQualifiedName}:{HttpClient.BaseAddress}:{includeDeprecated}:{options.Value.SchemaCacheKey}"; return await cache.GetOrCreateAsync(cacheKey, async entry => { From bb8b86786737f2581f2fba8c86d141dd09190739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Mon, 26 Oct 2026 21:26:54 +0100 Subject: [PATCH 2/6] Spike: standalone clients carrying their own runtime copy scripts/spike-standalone.ps1 copies the runtime source and the generated SampleClient into projects with no Linq2GraphQL package reference, rewriting the namespace. The end-to-end tests run against the copy, and CoexistenceTests loads a 1.0.30 client and two current clients into one process. --- scripts/spike-standalone.ps1 | 81 ++ test/Linq2GraphQL.TestClientLegacy/.gitignore | 3 + .../Linq2GraphQL.TestClientLegacy.csproj | 21 + .../packages.lock.json | 174 +++ test/Linq2GraphQL.TestClientSecond/.gitignore | 3 + .../Linq2GraphQL.TestClientSecond.csproj | 21 + .../packages.lock.json | 174 +++ .../.gitignore | 3 + .../Linq2GraphQL.TestClientStandalone.csproj | 21 + .../packages.lock.json | 174 +++ test/Linq2GraphQL.Tests.Standalone/.gitignore | 2 + .../CoexistenceTests.cs | 52 + .../Linq2GraphQL.Tests.Standalone.csproj | 34 + .../packages.lock.json | 1066 +++++++++++++++++ 14 files changed, 1829 insertions(+) create mode 100644 scripts/spike-standalone.ps1 create mode 100644 test/Linq2GraphQL.TestClientLegacy/.gitignore create mode 100644 test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj create mode 100644 test/Linq2GraphQL.TestClientLegacy/packages.lock.json create mode 100644 test/Linq2GraphQL.TestClientSecond/.gitignore create mode 100644 test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj create mode 100644 test/Linq2GraphQL.TestClientSecond/packages.lock.json create mode 100644 test/Linq2GraphQL.TestClientStandalone/.gitignore create mode 100644 test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj create mode 100644 test/Linq2GraphQL.TestClientStandalone/packages.lock.json create mode 100644 test/Linq2GraphQL.Tests.Standalone/.gitignore create mode 100644 test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs create mode 100644 test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj create mode 100644 test/Linq2GraphQL.Tests.Standalone/packages.lock.json diff --git a/scripts/spike-standalone.ps1 b/scripts/spike-standalone.ps1 new file mode 100644 index 00000000..ea162685 --- /dev/null +++ b/scripts/spike-standalone.ps1 @@ -0,0 +1,81 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Spike: builds "standalone" SampleClients that carry their own copy of the runtime. + +.DESCRIPTION + A standalone client is the generated SampleClient plus the runtime source + (Linq2GraphQL.Client + Linq2GraphQL.Client.Subscriptions) with the Linq2GraphQL.Client + namespace rewritten to .Runtime. Its project has no reference to the + runtime packages. + + Writes three of them: + test/Linq2GraphQL.TestClientStandalone current source, namespace Linq2GraphQL.TestClient + test/Linq2GraphQL.TestClientSecond current source, namespace Linq2GraphQL.SecondClient + test/Linq2GraphQL.TestClientLegacy source from -LegacySource, namespace Linq2GraphQL.LegacyClient + + The existing end-to-end tests that only depend on SampleClient and the runtime are + copied into test/Linq2GraphQL.Tests.Standalone with the same rewrite, so the same + assertions run against the standalone copy. The other two clients exist so a test can + load several standalone clients into one process. + +.PARAMETER LegacySource + Root of an older checkout, e.g. extracted with + git archive 1.0.30-prerelease src test/Linq2GraphQL.TestClient | tar -x -C +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$LegacySource +) + +$ErrorActionPreference = 'Stop' +$repoRoot = Split-Path -Parent $PSScriptRoot + +# Tests that need the generator, the AI package or the nullable client stay in the main suite. +$excludedTests = @( + 'AgentLoopTests.cs', 'AgentToolsExtensionsTests.cs', 'AgentToolsetTests.cs', + 'DescriptionAttributeTests.cs', 'ExampleFormatterTests.cs', 'GeneratorCollection.cs', + 'QueryNullableTests.cs', 'QueryValidatorTests.cs', 'SampleClientNullableFixture.cs', + 'ScalarMappingTests.cs', 'ScalarTests.cs', 'SchemaRendererTests.cs', 'ScopedSchemaTests.cs', + 'SdlInputTests.cs', 'SdlOutputTests.cs' +) + +function Copy-Rewritten([string]$source, [string]$destination, [string]$clientNamespace, [string[]]$exclude = @()) { + if (Test-Path $destination) { Remove-Item -Recurse -Force $destination } + + Get-ChildItem -Path $source -Recurse -Filter *.cs | + Where-Object { $_.FullName -notmatch '[\\/](bin|obj)[\\/]' -and $exclude -notcontains $_.Name } | + ForEach-Object { + $relative = [IO.Path]::GetRelativePath($source, $_.FullName) + $target = Join-Path $destination $relative + New-Item -ItemType Directory -Force (Split-Path $target) | Out-Null + + # Runtime first: its replacement contains the client namespace, which the + # second pattern must not see as the original Linq2GraphQL.TestClient. + $text = Get-Content -Raw $_.FullName + $text = $text -replace '\bLinq2GraphQL\.Client\b', "$clientNamespace.Runtime" + $text = $text -replace '\bLinq2GraphQL\.TestClient\b', $clientNamespace + Set-Content -NoNewline -Path $target -Value $text + } +} + +function New-StandaloneClient([string]$sourceRoot, [string]$project, [string]$clientNamespace) { + Write-Host "==> $project ($clientNamespace) from $sourceRoot" -ForegroundColor Cyan + $projectDir = Join-Path $repoRoot "test/$project" + + # Assembly.cs only holds InternalsVisibleTo for the package assemblies; the copy lives + # in the client's own assembly, so it is not needed. + Copy-Rewritten (Join-Path $sourceRoot 'src/Linq2GraphQL.Client') (Join-Path $projectDir 'Runtime/Client') $clientNamespace @('Assembly.cs') + Copy-Rewritten (Join-Path $sourceRoot 'src/Linq2GraphQL.Client.Subscriptions') (Join-Path $projectDir 'Runtime/Subscriptions') $clientNamespace @('Assembly.cs') + Copy-Rewritten (Join-Path $sourceRoot 'test/Linq2GraphQL.TestClient/Generated') (Join-Path $projectDir 'Generated') $clientNamespace +} + +New-StandaloneClient $repoRoot 'Linq2GraphQL.TestClientStandalone' 'Linq2GraphQL.TestClient' +New-StandaloneClient $repoRoot 'Linq2GraphQL.TestClientSecond' 'Linq2GraphQL.SecondClient' +New-StandaloneClient $LegacySource 'Linq2GraphQL.TestClientLegacy' 'Linq2GraphQL.LegacyClient' + +Write-Host '==> Copying tests' -ForegroundColor Cyan +Copy-Rewritten (Join-Path $repoRoot 'test/Linq2GraphQL.Tests') (Join-Path $repoRoot 'test/Linq2GraphQL.Tests.Standalone/Tests') 'Linq2GraphQL.TestClient' $excludedTests + +Write-Host '==> Done' -ForegroundColor Green diff --git a/test/Linq2GraphQL.TestClientLegacy/.gitignore b/test/Linq2GraphQL.TestClientLegacy/.gitignore new file mode 100644 index 00000000..bc297ce7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientLegacy/.gitignore @@ -0,0 +1,3 @@ +# Written by scripts/spike-standalone.ps1 +Runtime/ +Generated/ diff --git a/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj b/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj new file mode 100644 index 00000000..902c56fd --- /dev/null +++ b/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj @@ -0,0 +1,21 @@ + + + + + net10.0 + enable + disable + + + + + + + + + + + + + diff --git a/test/Linq2GraphQL.TestClientLegacy/packages.lock.json b/test/Linq2GraphQL.TestClientLegacy/packages.lock.json new file mode 100644 index 00000000..99638226 --- /dev/null +++ b/test/Linq2GraphQL.TestClientLegacy/packages.lock.json @@ -0,0 +1,174 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Microsoft.Extensions.Caching.Memory": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Http": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Nerdbank.GitVersioning": { + "type": "Direct", + "requested": "[3.8.118, )", + "resolved": "3.8.118", + "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" + }, + "Websocket.Client": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IO.RecyclableMemoryStream": "3.0.0", + "System.Reactive": "6.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + } + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientSecond/.gitignore b/test/Linq2GraphQL.TestClientSecond/.gitignore new file mode 100644 index 00000000..bc297ce7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientSecond/.gitignore @@ -0,0 +1,3 @@ +# Written by scripts/spike-standalone.ps1 +Runtime/ +Generated/ diff --git a/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj b/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj new file mode 100644 index 00000000..b77a634f --- /dev/null +++ b/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj @@ -0,0 +1,21 @@ + + + + + net10.0 + enable + disable + + + + + + + + + + + + + diff --git a/test/Linq2GraphQL.TestClientSecond/packages.lock.json b/test/Linq2GraphQL.TestClientSecond/packages.lock.json new file mode 100644 index 00000000..99638226 --- /dev/null +++ b/test/Linq2GraphQL.TestClientSecond/packages.lock.json @@ -0,0 +1,174 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Microsoft.Extensions.Caching.Memory": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Http": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Nerdbank.GitVersioning": { + "type": "Direct", + "requested": "[3.8.118, )", + "resolved": "3.8.118", + "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" + }, + "Websocket.Client": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IO.RecyclableMemoryStream": "3.0.0", + "System.Reactive": "6.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + } + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/.gitignore b/test/Linq2GraphQL.TestClientStandalone/.gitignore new file mode 100644 index 00000000..bc297ce7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/.gitignore @@ -0,0 +1,3 @@ +# Written by scripts/spike-standalone.ps1 +Runtime/ +Generated/ diff --git a/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj b/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj new file mode 100644 index 00000000..14f3bda7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj @@ -0,0 +1,21 @@ + + + + + net10.0 + enable + disable + + + + + + + + + + + + + diff --git a/test/Linq2GraphQL.TestClientStandalone/packages.lock.json b/test/Linq2GraphQL.TestClientStandalone/packages.lock.json new file mode 100644 index 00000000..99638226 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/packages.lock.json @@ -0,0 +1,174 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Microsoft.Extensions.Caching.Memory": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Http": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Nerdbank.GitVersioning": { + "type": "Direct", + "requested": "[3.8.118, )", + "resolved": "3.8.118", + "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" + }, + "Websocket.Client": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IO.RecyclableMemoryStream": "3.0.0", + "System.Reactive": "6.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + } + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.Tests.Standalone/.gitignore b/test/Linq2GraphQL.Tests.Standalone/.gitignore new file mode 100644 index 00000000..7726b485 --- /dev/null +++ b/test/Linq2GraphQL.Tests.Standalone/.gitignore @@ -0,0 +1,2 @@ +# Written by scripts/spike-standalone.ps1 +Tests/ diff --git a/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs b/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs new file mode 100644 index 00000000..066e615c --- /dev/null +++ b/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Options; +using Shouldly; +// An app using several clients imports every runtime namespace; extension methods such as +// ExecuteAsync must still resolve because each one targets its own copy's types. +using Linq2GraphQL.TestClient.Runtime; +using Linq2GraphQL.SecondClient.Runtime; +using Linq2GraphQL.LegacyClient.Runtime; +using Current = Linq2GraphQL.TestClient; +using Second = Linq2GraphQL.SecondClient; +using Legacy = Linq2GraphQL.LegacyClient; + +namespace Linq2GraphQL.Tests; + +/// +/// Several standalone clients in one process, sharing one HttpClient base address and one +/// IMemoryCache, as an app that depends on clients from several teams would. +/// +public class CoexistenceTests +{ + private static readonly Uri baseAddress = new("https://localhost:7184/graphql/"); + + [Fact] + public async Task LegacyAndCurrentClient_InOneProcess_BothWork() + { + using var application = new WebApplicationFactory(); + + var legacy = new Legacy.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Legacy.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); + var current = new Current.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Current.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); + + (await legacy.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + (await current.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + (await legacy.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); + (await current.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); + } + + [Fact] + public async Task TwoClientsFromTheSameVersion_SharingCacheAndEndpoint_BothWork() + { + using var application = new WebApplicationFactory(); + + var current = new Current.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Current.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); + var second = new Second.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Second.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); + + (await current.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + (await second.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + } +} diff --git a/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj new file mode 100644 index 00000000..e8704813 --- /dev/null +++ b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj @@ -0,0 +1,34 @@ + + + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/test/Linq2GraphQL.Tests.Standalone/packages.lock.json b/test/Linq2GraphQL.Tests.Standalone/packages.lock.json new file mode 100644 index 00000000..f6934277 --- /dev/null +++ b/test/Linq2GraphQL.Tests.Standalone/packages.lock.json @@ -0,0 +1,1066 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Microsoft.AspNetCore.Mvc.Testing": { + "type": "Direct", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "BJi3rpYETlh0wd14Lg5Is9MWvEf55TAGd7yo+xGHuEZp7ololyvftbQZ1i7ZuITA7DgNPJED6EMhFSWycq7SBg==", + "dependencies": { + "Microsoft.AspNetCore.TestHost": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "Microsoft.Extensions.Hosting": "10.0.11" + } + }, + "Microsoft.Extensions.TimeProvider.Testing": { + "type": "Direct", + "requested": "[10.10.0, )", + "resolved": "10.10.0", + "contentHash": "8duZdxuO3Oo8tlyQXVq1drXee6GJC9DjUU97Pq3YaIqf92jrAHsWxbbRKwvjQI48Mer3IANwUKF0elqvHeY4Rw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "Moq": { + "type": "Direct", + "requested": "[4.20.72, )", + "resolved": "4.20.72", + "contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "Nerdbank.GitVersioning": { + "type": "Direct", + "requested": "[3.8.118, )", + "resolved": "3.8.118", + "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "ChilliCream.Nitro.App": { + "type": "Transitive", + "resolved": "32.0.2", + "contentHash": "wSD2dFFraVpIsQDiy31k1vkGWYAGdzNVXOhtOIujMt/ADAzfkEbJUsPyF4qoktFspepN4VrAN7DMS8QRqkEhTg==", + "dependencies": { + "Yarp.ReverseProxy": "2.3.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "GreenDonut": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "nzv5xP1krDEqhq4GekXWdKlvGPb0NM8JF3B0WpEqIS/tVG7U3sBaQuGCMSNJ02kvrtcde+YEPYtcTh7Y0SJ+CA==", + "dependencies": { + "GreenDonut.Abstractions": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", + "Microsoft.Extensions.ObjectPool": "10.0.0" + } + }, + "GreenDonut.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "egSQAz8BbFmLqOWtX0s/xlaqz/43fHUzdu/9TjpZ3s4F8FIVgO4rBfKNFWt2nsba9tvT1AW2WbAaf+3pY6IN0w==" + }, + "GreenDonut.Data": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "kYYLxAEPJl0/vdWF2i5pNs6pndGbuiz8OFAyUovfsu2Guo3dSomkU91PC0kpOeToXoQyRDdjz3GUKNBFnT9PQw==", + "dependencies": { + "GreenDonut": "16.6.4", + "GreenDonut.Data.Abstractions": "16.6.4" + } + }, + "GreenDonut.Data.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "Ao8i3nm86+vRKlpctKoWvZ4OdjSLPAQmWp3VMFmCuMVF3rbsVR8TxLRcAG/2/B6VdbGQyOO8tJUGSAooncu28g==", + "dependencies": { + "GreenDonut.Abstractions": "16.6.4", + "GreenDonut.Data.Primitives": "16.6.4" + } + }, + "GreenDonut.Data.Primitives": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "p3185VDDm5xV6ZQrkJWkmYaPCVE2BIg6up6R6DrQKnCwIMv3VDzFgNLnVu2eNqoj3iEplBZJBdk1OzvYZO3Xdg==" + }, + "HotChocolate": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "bvEKPvyd0OhZd5usrPxWcGToM860SOlupW+edNobvZiyltlcxnZkfZ7u8Vx8MNtA0OswLyjaL1RI3+JashlE/g==", + "dependencies": { + "HotChocolate.Authorization": "16.6.4", + "HotChocolate.CostAnalysis": "16.6.4", + "HotChocolate.Execution.Projections": "16.6.4", + "HotChocolate.Types": "16.6.4", + "HotChocolate.Types.CursorPagination": "16.6.4", + "HotChocolate.Types.CursorPagination.Extensions": "16.6.4", + "HotChocolate.Types.Mutations": "16.6.4", + "HotChocolate.Types.Queries": "16.6.4", + "HotChocolate.Validation": "16.6.4" + } + }, + "HotChocolate.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "1ELetbKeMEJXK1D/G9kR55XGF4FkaYS8zvLkWIl/3s75BWPTk97vzC/EBgTEC5Q/Wj7zmBDUwdA7a9noezeoFw==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Language": "16.6.4", + "HotChocolate.Primitives": "16.6.4" + } + }, + "HotChocolate.AspNetCore.Pipeline": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "j3T4SnePMXVbrOyniu9SFvA7jOfHlcVjEeyvEh60UD/xaPIP1+L5WofDpP1088JwwvLlpYCWdyEmXhNoFy8Q+g==", + "dependencies": { + "ChilliCream.Nitro.App": "32.0.2", + "HotChocolate.Caching.Memory": "16.6.4", + "HotChocolate.PersistedOperations.Abstractions": "16.6.4", + "HotChocolate.Transport.Formatters": "16.6.4", + "HotChocolate.Transport.Sockets": "16.6.4" + } + }, + "HotChocolate.Authorization": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "2TIXgq49SYmfC+NMV+poOq83JeScz0ZkEhHSgv91lNDTZINz06DlxcCrlsf2q+ns8fIS3rbnnHJswBzobE+qUA==", + "dependencies": { + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Caching.Memory": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "oEOWL5+6wVG7HQHil5EydXcCdV296ZSuUO27mZYAxq5wMx9aX9D/ZkUOWU3To1vo75w767tqrr7+OEGWjPxuiA==" + }, + "HotChocolate.CostAnalysis": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "LcrorDRmMdWjRyCOxnYbqIuDB/qdP5oFXBw4gkgNdnjov0C7BGz5z0M074OvgdzsnqQYb7iDWcaPPtZq6zCVxA==", + "dependencies": { + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Execution.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "pmaAsn6q4C/elx0SVnTGYWZxy3AqY5V7FYf/3CR8fq7i4rMKH/hHe/qfIoAzC6Phbg1tacHlT2YBeOAssQ3GoA==", + "dependencies": { + "HotChocolate.Features": "16.6.4", + "HotChocolate.Language.SyntaxTree": "16.6.4", + "HotChocolate.Language.Web": "16.6.4", + "HotChocolate.Primitives": "16.6.4", + "HotChocolate.Text.Json": "16.6.4", + "HotChocolate.Types.Abstractions": "16.6.4", + "HotChocolate.Utilities.Buffers": "16.6.4", + "HotChocolate.Utilities.Tasks": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Execution.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "OP16Y71IuSuBhGuTNo3gujdBF5HP1WmZYtNV6bUpCv/r4r7dG8rKQYxd2nlnPfTeiFQsvZyqGNKQLxATwxOiCQ==", + "dependencies": { + "HotChocolate.Abstractions": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Execution.Operation.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "yfXI/ps/VYFyVtj3Z29ImoSLxGfn5OTRXmhkpQtKhQLTiFA8DbxU8/swXRx8bTJL8YS3AOAdPn5qiZBD6rz93Q==", + "dependencies": { + "HotChocolate.Features": "16.6.4", + "HotChocolate.Types.Abstractions": "16.6.4" + } + }, + "HotChocolate.Execution.Pipeline": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "ZaJ48M13jKTsVphND7rK+JvWkxM3oDmYLL/JDNj0zjMqMH77PQSLVALK/OOKSI0HxhDbKWdVSvUMKg7MoXrJ5A==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Validation": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Execution.Projections": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "9cqrsEdbY8JfKgLgRGvM3rBfrC7fvb6rl+kORlXVMOvxDTbL75vrvmKyTHC1HmlJdQ56wTUq33Y6ycEJdoCwNw==", + "dependencies": { + "GreenDonut.Data": "16.6.4", + "GreenDonut.Data.Abstractions": "16.6.4", + "GreenDonut.Data.Primitives": "16.6.4", + "HotChocolate.Caching.Memory": "16.6.4", + "HotChocolate.Execution.Configuration.Abstractions": "16.6.4", + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Features": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "0SGN2IDkopXsaxaN0qT+Ww5d/Y+yV4ZvG+xCKU0CvWlTnrq6JrpgeZcJF594EwJnXB4Zja3Wn1piKFEh0ewPDw==" + }, + "HotChocolate.Fusion.Language": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "euXggAJYrOhpd0OpDXLZOQ49AocNkIYUCVEoU6403EDTN46PnaDz2ltGnhcYk8bI1qMq0nm+ZbcmREcdRJfonQ==" + }, + "HotChocolate.Fusion.Utilities": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "HDz/NdyIDNCfUx2JvInVbsr4F3f9a8jyXlnM5mbGV0caZv8Hr1ozyY9NOyltZtkomdk0TKXVN8YaxBWnfwMBLA==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Fusion.Language": "16.6.4", + "HotChocolate.Language": "16.6.4", + "HotChocolate.Types.Abstractions": "16.6.4" + } + }, + "HotChocolate.Language": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "oLEo6gvXVcKN/sDOSKxB0pFYBId1OSdKB+yxVRzg88M3q8N2uq89Q0P7fnpwUuiobay511gvXXihYaBxNt3xVA==", + "dependencies": { + "HotChocolate.Language.SyntaxTree": "16.6.4", + "HotChocolate.Language.Utf8": "16.6.4", + "HotChocolate.Language.Visitors": "16.6.4", + "HotChocolate.Language.Web": "16.6.4" + } + }, + "HotChocolate.Language.SyntaxTree": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "BDsJMdv6LjYEfGVQiAVhSf50xbAIrigoZUzjUoncbXGXzrfAJaVsGkZs6mR1z0UcTWrlSNoFRosxbtY07iPnCg==", + "dependencies": { + "HotChocolate.Utilities.Buffers": "16.6.4", + "Microsoft.Extensions.ObjectPool": "10.0.0" + } + }, + "HotChocolate.Language.Utf8": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "Ria0zRrpy4nXVvuczx14Vbe+8jdVPPryUlKjfUVUZCmU9Ntif/XhaFmVYo3HtS+FVkFHSRA1o0DeTXEoDnzX9Q==", + "dependencies": { + "HotChocolate.Language.SyntaxTree": "16.6.4", + "HotChocolate.Language.Utf8.SyntaxTree": "16.6.4", + "HotChocolate.Utilities.Buffers": "16.6.4" + } + }, + "HotChocolate.Language.Utf8.SyntaxTree": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "2nnk9e2GT4N8HlVXvtYYOEJysPUiZMQnjtSnLSltW9YC1VW/E4IGnxxJ2Gt7Mj+4yUSlQ+61DnzOD+UtSF09Kg==", + "dependencies": { + "HotChocolate.Language.SyntaxTree": "16.6.4", + "HotChocolate.Utilities.Buffers": "16.6.4" + } + }, + "HotChocolate.Language.Visitors": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "MWHB8709Ho2bQkIXuzTGKw91L9z675poWJ1lg6BGmdg3/ZnfsXGAv8cdP/cA4UzB1rOtYJTqrh2YP7xywHvwCQ==", + "dependencies": { + "HotChocolate.Language.SyntaxTree": "16.6.4" + } + }, + "HotChocolate.Language.Web": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "7GWlN019RYlbGJ88j9gw34exwD562PsiJ49zs8RALAzJnmC000KgH5vHikUbPVU5eeO8nUunyCEQafdgypY68A==", + "dependencies": { + "HotChocolate.Language.Utf8": "16.6.4", + "HotChocolate.Language.Visitors": "16.6.4" + } + }, + "HotChocolate.PersistedOperations.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "oUpPCiwIfrlk7Cswh+c9J+iRnmNIbRlA7EmGeL/nOR93xSMiCd9zgrkAtBggP4SdyESY9dD628lQZb8AkqXZdw==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4" + } + }, + "HotChocolate.PersistedOperations.Pipeline": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "h1VeBRoKQVg5BZnhLHwUHhGApOa8Rk31s64PNG1darAqUnX9v8ZNd/oiv+cQ6CabraQHmbvvvR2DfE/XGsygcA==", + "dependencies": { + "HotChocolate.PersistedOperations.Abstractions": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Primitives": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "u4FkIs8tKYWJ3I0vTEDPXaPhy4zfhFNZNWqef3tRPGpnQe1rg+7FdM+V1bNs6j94ucY8fX5DTNDJy9OGVe4e3g==", + "dependencies": { + "HotChocolate.Language": "16.6.4" + } + }, + "HotChocolate.Subscriptions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "L9CzlWy1cHZFixMWyZmtbI5pmqlfwHjGBjF0+pVnbJJaYQv2eSc4Q/8kdQjkhdGuhewPtC44OAMki1br9/Azpw==", + "dependencies": { + "HotChocolate.Abstractions": "16.6.4", + "HotChocolate.Execution.Configuration.Abstractions": "16.6.4", + "HotChocolate.Utilities": "16.6.4" + } + }, + "HotChocolate.Subscriptions.InMemory": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "/3Tm/h3jw+nrv5CHtszE3iAnG3n3odP842QrTid7mxrEmaMEqE74RA00wTmFaO/bbepCnmymXLxox31nC30TAQ==", + "dependencies": { + "HotChocolate.Execution.Configuration.Abstractions": "16.6.4", + "HotChocolate.Subscriptions": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Text.Json": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "sBxtzPsvDjsw9V9VKy/NomlPE1ruprKHU5iTuHJUZ2i20Ihvq4ouxff0O7yCD1iA32NCaLNL3zvNJagZNV/nTQ==" + }, + "HotChocolate.Transport.Formatters": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "fl16wIiX4FAbSs2TSUM7yorg3awV2cUDNBMN9LU/mPcEN9yzgNN7FsIKQdl1TiqiXSAz5/ihVtBqLJBliws8uw==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Utilities.Buffers": "16.6.4", + "HotChocolate.Utilities.Tasks": "16.6.4" + } + }, + "HotChocolate.Transport.Sockets": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "g67fURnanTGuLJwWxRG4E5PJfl+K6TZx8afNc6u+flbbwmTR+7TKEx7b/ZrlzqxCSqASA4H+KN3h4/3nILT+gQ==" + }, + "HotChocolate.Types": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "UEpl+u8WuvQOK1vdlq+2uucLPQpuNXbzt27ij8xcEwOfZAimYC8dGep4MIb1ItdNk2X7PaOegGPQXWoh+oxSiQ==", + "dependencies": { + "GreenDonut": "16.6.4", + "GreenDonut.Data.Primitives": "16.6.4", + "HotChocolate.Abstractions": "16.6.4", + "HotChocolate.Caching.Memory": "16.6.4", + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Execution.Configuration.Abstractions": "16.6.4", + "HotChocolate.Execution.Operation.Abstractions": "16.6.4", + "HotChocolate.Execution.Pipeline": "16.6.4", + "HotChocolate.Features": "16.6.4", + "HotChocolate.Fusion.Language": "16.6.4", + "HotChocolate.Fusion.Utilities": "16.6.4", + "HotChocolate.PersistedOperations.Abstractions": "16.6.4", + "HotChocolate.PersistedOperations.Pipeline": "16.6.4", + "HotChocolate.Text.Json": "16.6.4", + "HotChocolate.Transport.Formatters": "16.6.4", + "HotChocolate.Types.Abstractions": "16.6.4", + "HotChocolate.Utilities": "16.6.4", + "HotChocolate.Utilities.Base36": "16.6.4", + "HotChocolate.Utilities.DependencyInjection": "16.6.4", + "Microsoft.Extensions.DependencyInjection": "10.0.0", + "Microsoft.Extensions.ObjectPool": "10.0.0", + "Microsoft.Extensions.Options": "10.0.0" + } + }, + "HotChocolate.Types.Abstractions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "KrftIDiSImZmhQv+Johy6cGOzIKizZS4TbsJwzlSINrUtj/L8G7RVvFaUTl/iYXYIdganPopITsSCu+fs3UIVA==", + "dependencies": { + "HotChocolate.Features": "16.6.4", + "HotChocolate.Primitives": "16.6.4" + } + }, + "HotChocolate.Types.CursorPagination": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "hNoVzFKkZGzTofL0nPtMeqFpv6+zy2jqY9Ef3vnJuzSwCjD08g4SJNt947ViksDoqMUVPtH3xjZMiOS2vN9Z6w==", + "dependencies": { + "HotChocolate.Execution.Configuration.Abstractions": "16.6.4", + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Types.CursorPagination.Extensions": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "uaWfK7+/WpYF/zOoeLF+QutSVHP3hQEW+MKHRc76Fg4tuhDxSw6c7rW3wEbOys7W1kAgnKfeVWtYkQCj4ifBBg==", + "dependencies": { + "GreenDonut.Data": "16.6.4", + "HotChocolate.Types.CursorPagination": "16.6.4" + } + }, + "HotChocolate.Types.Errors": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "S9KNljhZe94L/OQse+VsVwwIUunoyjBv4IvTfjCPy/6IWvc96q6Pkw5wC9C42X32PrCPqTsXL6vsc7G8mQLh3A==", + "dependencies": { + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Types.Mutations": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "l5umZs2JyB3wq8giyucXLKmKkaFEZxidzhaiZ2DsxXq969v0OlgZiGxdU5puMUJ1XNrWtQUocnURgo8tqOg/pg==", + "dependencies": { + "HotChocolate.Types": "16.6.4", + "HotChocolate.Types.Errors": "16.6.4" + } + }, + "HotChocolate.Types.Queries": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "WiMtlmgToK8/GLFR5zdUT9YdSmpU2kkDTd7l+56r1TB934+2Qp2hzl0zd6THCPx4Hd9BocwMjrUggSCgnDuDng==", + "dependencies": { + "HotChocolate.Types": "16.6.4", + "HotChocolate.Types.Errors": "16.6.4" + } + }, + "HotChocolate.Types.Scalars.Upload": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "GWkhJth8tpx3zF8xXu7oxXB+aeTGclwDZR8/kA0CI/efsIRbAj5k13HxTN7WG9/b7/SdV+iJer6pRS1J1fWOYQ==", + "dependencies": { + "HotChocolate.Types": "16.6.4" + } + }, + "HotChocolate.Utilities": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "8kg+EoA7eS0uIOR/06CzKcEaZprWAxecwUkHFS2cOys7uRkXaxjgVfzwz1wARkAXEHhaMhFEQvVjHghQGQodYA==", + "dependencies": { + "HotChocolate.Utilities.Buffers": "16.6.4", + "HotChocolate.Utilities.Tasks": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "HotChocolate.Utilities.Base36": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "i0zQoDkaSXR07627r1l7SQ0psF9zOa7CUsmR7vrSmwhBItYe2AVR6z4M58JDo1MoHR5Nt6Mp8Y0dgrOL6OM6zg==" + }, + "HotChocolate.Utilities.Buffers": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "oeadDwUfn3oF97oNwF3QD3gEoOPOxl/sKwFGKj9BXNwgQnm9ho4DRFZrY0v4Gk78sGEsSxzfK8YlUz7pl5aAXA==" + }, + "HotChocolate.Utilities.DependencyInjection": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "1mw7EmE6mF/Ui+LGW1cyVPm0Rvko+szyU5q3AsH5NmiT+9iEEReLm3h716nF9f4RsYGnuNdXuUt1+eE6tfs/AQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.0" + } + }, + "HotChocolate.Utilities.Tasks": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "/zsN3ARuQe39QWMEMlaQCV4Td4kSa4Ppzh979YeXATB7S039QyDyExgtzsPw2gu0WamuH+hqPsGdo3hSRqqUIQ==" + }, + "HotChocolate.Validation": { + "type": "Transitive", + "resolved": "16.6.4", + "contentHash": "H104wO+jzcY8OhZE9QEKWs5NGtC4Kj04oJOsgJ+do3KI2go9fKNtrHKfJLFfuR6HaREn6SDqswYViiR4BGItSg==", + "dependencies": { + "HotChocolate.Execution.Abstractions": "16.6.4", + "HotChocolate.Types.Abstractions": "16.6.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + } + }, + "Microsoft.AspNetCore.TestHost": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "IVGNbZVxLuL80y7cmXYfhIFoTGneHkp4D7xXarYe6V/wjo8/2qiSLUwWax1/h2eHtJ3Y5cuqmtpOyWMnXIsUMA==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "1KHr/1L56llwQ/yI0tAisEA31UpPsn8aasjASIwELOaN4JIUcbjuQBMdFOIzfNBBeULoUa0XfBe5QDtRRUY+fg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "PJPtFYsZ+r+uz9qqXWUTEyKeJ1EiBGIJtqavkg9ZXijjGSFAk4Fgi5sqIxj+uAyLZwEKgexDUQXhWhvU6l3+og==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "JOjac6SQQgZmdmB8WGEw61/7siqMZoWJMkmq2p1goJGxqI59lO6oB4bOl0jNsbaPBdYy5Mlkb+6U7T4+CjnD8Q==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Tq/UqMaczePv9yWwSsJZRgKtgA46djVR5xHj/lZBCueQ3ag8f9v5mu0EdhrNx7tXxNk+Y9OurG2oKuSKINjr0A==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "2i6rtW/B5rCnWCnhdmWWEmaM9O0HD0zsPY9eRqa++y4tclI3Uw8zvGbBvhY/LjAdtf8gUHhUPcAWj3DRlWMXmQ==" + }, + "Microsoft.Extensions.Hosting": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "eIDa/Rl+93aj17gMlFsJJx+LhBvb3CP0Mu1PeVYkDp2Y3S4Jock8UynfGQEcx7lrlq+gKW+ECQJHbro/LTPDEQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.11", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.11", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.11", + "Microsoft.Extensions.Configuration.Json": "10.0.11", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.11", + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics": "10.0.11", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11", + "Microsoft.Extensions.FileProviders.Physical": "10.0.11", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Configuration": "10.0.11", + "Microsoft.Extensions.Logging.Console": "10.0.11", + "Microsoft.Extensions.Logging.Debug": "10.0.11", + "Microsoft.Extensions.Logging.EventLog": "10.0.11", + "Microsoft.Extensions.Logging.EventSource": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "pwtpF7iF/NNaOBcX+pvMZ7y2+JAVbH5KkNrH9uMZtuVxVJsFTDiWiCR7Tk3HVptsAijaetipUZVRVK2LLq+nvA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "S7LvLeVHKNPaY2NMyxW7c2TBGsLgxoSUBCV5Ev5iN8kgC7EPR2UB7eW7vHsElGMcIUDwRmoxLfvGDynCn3q6EA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "wr+j1bjdFXhc8lKTLoq+RbwFM8M+orcMS9xrcqLmDGOxJcXpKizEeE5h6v/GKwCZV02FmhaA7OlNjoq072jZpQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "Eck9GpCCpvZ3f6L7IUlN+mPtRVefnf7PsiIG5vi61QawPtLNCEAv2TPD/M3SojcU0PFaef+BxiVGPOShFHtDog==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "System.Diagnostics.EventLog": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "hs6QWECLLohi2VKqUvSGRUvrg7eXR1DqKL95Jrtz3cdD2g2nBA+yJPdRQLZ7SLmnTZWycxfMDK2s0ho+rfst5w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "10.0.0", + "contentHash": "bpeCq0IYmVLACyEUMzFIOQX+zZUElG1t+nu1lSxthe7B+1oNYking7b91305+jNB6iwojp9fqTY9O+Nh7ULQxg==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Binder": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.11", + "contentHash": "QTXEoQBzz00SFWbo7nAg1Ogd4f99lwqcO9uAJ7MYSLEUR28f6As32QktrqG2Fr9cfAfd1GjLyGYspE7Ipj7P6w==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "Yarp.ReverseProxy": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "gxtkN3a+9biu9V9Zd5NaTO6VZWXAnS2mhQ0R/VXmSPoTuiQNZsakKikrKpDtKxrL5nUYzbRsHtl40WNq+ZBKKg==", + "dependencies": { + "System.IO.Hashing": "8.0.0" + } + }, + "linq2graphql.testclientlegacy": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", + "Microsoft.Extensions.Http": "[10.0.11, )", + "Websocket.Client": "[5.3.0, )" + } + }, + "linq2graphql.testclientsecond": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", + "Microsoft.Extensions.Http": "[10.0.11, )", + "Websocket.Client": "[5.3.0, )" + } + }, + "linq2graphql.testclientstandalone": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", + "Microsoft.Extensions.Http": "[10.0.11, )", + "Websocket.Client": "[5.3.0, )" + } + }, + "linq2graphql.testserver": { + "type": "Project", + "dependencies": { + "HotChocolate.AspNetCore": "[16.6.4, )", + "HotChocolate.Data": "[16.6.4, )", + "HotChocolate.Types.Analyzers": "[16.6.4, )", + "Linq2GraphQL.TestServer.Shared": "[1.0.0, )" + } + }, + "linq2graphql.testserver.shared": { + "type": "Project", + "dependencies": { + "HotChocolate.AspNetCore": "[16.6.4, )", + "HotChocolate.Data": "[16.6.4, )" + } + }, + "HotChocolate.AspNetCore": { + "type": "CentralTransitive", + "requested": "[16.6.4, )", + "resolved": "16.6.4", + "contentHash": "sxqZ+B2asfW/vAznKNl+xP+sf8eR3h4lRKYrmDtJPTAbJ4Z8+rWW6DNqbFyoU1LAl1teTbRwPaGPxEqYKumA2Q==", + "dependencies": { + "ChilliCream.Nitro.App": "32.0.2", + "HotChocolate": "16.6.4", + "HotChocolate.AspNetCore.Pipeline": "16.6.4", + "HotChocolate.Subscriptions.InMemory": "16.6.4", + "HotChocolate.Transport.Formatters": "16.6.4", + "HotChocolate.Transport.Sockets": "16.6.4", + "HotChocolate.Types.Scalars.Upload": "16.6.4", + "HotChocolate.Utilities.DependencyInjection": "16.6.4" + } + }, + "HotChocolate.Data": { + "type": "CentralTransitive", + "requested": "[16.6.4, )", + "resolved": "16.6.4", + "contentHash": "ruKaYosX4yTOAVeDEQfHFSjuIhmCeQpq3ritskH8i74wn8hKhPRsFA9iFPom3qpiZkCJxsCIPQZumGJGf6xpkQ==", + "dependencies": { + "GreenDonut.Data": "16.6.4", + "GreenDonut.Data.Abstractions": "16.6.4", + "GreenDonut.Data.Primitives": "16.6.4", + "HotChocolate.Execution.Operation.Abstractions": "16.6.4", + "HotChocolate.Execution.Projections": "16.6.4", + "HotChocolate.Types": "16.6.4", + "HotChocolate.Types.CursorPagination.Extensions": "16.6.4" + } + }, + "HotChocolate.Types.Analyzers": { + "type": "CentralTransitive", + "requested": "[16.6.4, )", + "resolved": "16.6.4", + "contentHash": "bigjWuAkEQvxoQCrUzAvowiqS7YrTciape6++hB9YpA2cFH5RQnG+xyTo/MvWCN5jSvnW4izvdae9mhxtfhZZA==" + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "KICyU3eVi5jvloKm01EXV69L97H/zkhISVtV98cIuzuFOxNx3xTUVcXqvWTz3aq7OvUuDB/MFlPFjmxRaKF7/A==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "mDW7KVFB05M6jiRUyaZiOMWhS31n5HlSZwoYctHAZAucD4sMDJ70IxOmkGDt6RpstchD+keWBjhdzcMpSkWvWQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11", + "Microsoft.Extensions.FileProviders.Physical": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "nSPrT8U/cNoB4coqkmnanAMK9PsL7lsjG+LLUKEwHRFwS6E78b8S1wdv/y88EOxBhasWov1rLd7RTHmmsYPOLg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.11", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "BRliLdUowglV8GS+J1G/QsSofCJYYFg3U8QZx0ACRn+a91az/Qnpy+h6PyHS94WgV2TSazX5D/cuuk6wnCJatw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Configuration.Json": "10.0.11", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.11", + "Microsoft.Extensions.FileProviders.Physical": "10.0.11" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + } + }, + "Microsoft.Extensions.Http": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Diagnostics": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "dFc0yDudyD1iIg6z9XT7ofsT3hVO7Y4ylrxGHIVRR0GaZ4CUk4ujOrMoy7wWEdNHZhvJooySg6hZpOxyS8zEVA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Configuration": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" + } + }, + "System.CodeDom": { + "type": "CentralTransitive", + "requested": "[10.0.11, )", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "Websocket.Client": { + "type": "CentralTransitive", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IO.RecyclableMemoryStream": "3.0.0", + "System.Reactive": "6.0.0" + } + } + } + } +} \ No newline at end of file From 2a0dea3d3a430b9457c78f51b46e30464c0a5fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Mon, 26 Oct 2026 21:39:00 +0100 Subject: [PATCH 3/6] Generator: --standalone writes a copy of the runtime into the output In standalone mode the generated client depends on no Linq2GraphQL package: the runtime source (Client + Subscriptions, embedded in the generator at build time) is written to Runtime/ with its namespace rewritten to .Runtime, and the generated client files point there too. Clients generated by different generator versions can then share an app, because each carries the runtime it was generated against. - Subscriptions runtime is only copied when subscriptions are generated. - Runtime/RuntimeInfo.cs records the runtime version and the packages it needs; the generator also prints them. - A Runtime folder from an earlier standalone run is cleared before writing. - Also settable as "standalone" in the config file. Replaces the spike. TestClientStandalone is now generated by regenerate-test-clients.ps1 (which ignores the version line in -Check), Linq2GraphQL.Tests.Standalone compiles the main suite's tests against it, and CoexistenceTests runs it next to the package-based client in one process. --- CLAUDE.md | 4 +- Linq2GraphQL.CI.slnf | 4 +- Linq2GraphQL.sln | 14 + scripts/regenerate-test-clients.ps1 | 24 +- scripts/spike-standalone.ps1 | 81 - src/Linq2GraphQL.Generator/ClientGenerator.cs | 14 +- src/Linq2GraphQL.Generator/GeneratorConfig.cs | 6 + .../Linq2GraphQL.Generator.csproj | 33 + src/Linq2GraphQL.Generator/Program.cs | 34 +- .../StandaloneRuntime.cs | 142 ++ test/Linq2GraphQL.TestClientLegacy/.gitignore | 3 - .../Linq2GraphQL.TestClientLegacy.csproj | 21 - .../packages.lock.json | 174 -- test/Linq2GraphQL.TestClientSecond/.gitignore | 3 - .../Linq2GraphQL.TestClientSecond.csproj | 21 - .../packages.lock.json | 174 -- .../.gitignore | 3 - .../Generated/Client/MutationMethods.cs | 60 + .../Generated/Client/QueryMethods.cs | 200 ++ .../Generated/Client/SampleClient.cs | 101 + .../Client/SampleClientExtensions.cs | 40 + .../Generated/Client/SampleClientSchema.cs | 588 ++++++ .../Generated/Client/SubscriptionMethods.cs | 60 + .../Generated/Enums/AddressType.cs | 22 + .../Generated/Enums/CustomerStatus.cs | 25 + .../Generated/Enums/SortEnumType.cs | 22 + .../Generated/Inputs/AddressFilterInput.cs | 66 + .../Generated/Inputs/AddressInput.cs | 50 + .../Generated/Inputs/AddressSortInput.cs | 50 + .../Generated/Inputs/CustomerFilterInput.cs | 82 + .../Generated/Inputs/CustomerInput.cs | 66 + .../Generated/Inputs/CustomerSortInput.cs | 58 + .../CustomerStatusOperationFilterInput.cs | 50 + .../Inputs/DateTimeOperationFilterInput.cs | 114 + .../Inputs/DecimalOperationFilterInput.cs | 114 + .../Inputs/FloatOperationFilterInput.cs | 114 + .../Generated/Inputs/IAnimalFilterInput.cs | 50 + .../Generated/Inputs/IAnimalSortInput.cs | 34 + .../Generated/Inputs/InputFactory.cs | 1847 +++++++++++++++++ .../Inputs/IntOperationFilterInput.cs | 114 + .../Generated/Inputs/ItemFilterInput.cs | 58 + .../Generated/Inputs/ItemInput.cs | 42 + .../ListFilterInputTypeOfOrderFilterInput.cs | 50 + ...stFilterInputTypeOfOrderLineFilterInput.cs | 50 + .../ListUnsignedByteOperationFilterInput.cs | 50 + .../Inputs/LocalDateOperationFilterInput.cs | 114 + .../Inputs/LocalTimeOperationFilterInput.cs | 114 + .../Generated/Inputs/OrderFilterInput.cs | 107 + .../Generated/Inputs/OrderInput.cs | 91 + .../Generated/Inputs/OrderLineFilterInput.cs | 74 + .../Generated/Inputs/OrderLineInput.cs | 58 + .../Generated/Inputs/OrderSortInput.cs | 83 + .../Inputs/StringOperationFilterInput.cs | 114 + .../UnsignedByteOperationFilterInput.cs | 114 + .../Inputs/UuidOperationFilterInput.cs | 114 + .../Generated/Interfaces/IAnimal.cs | 120 ++ .../Generated/Interfaces/IMutationMethods.cs | 33 + .../Generated/Interfaces/IQueryMethods.cs | 89 + .../Generated/Interfaces/ISampleClient.cs | 17 + .../Interfaces/ISubscriptionMethods.cs | 34 + .../Generated/Runtime/Client/ArgumentValue.cs | 32 + .../Attributes/GraphArgumentAttribute.cs | 27 + .../Attributes/GraphInterfaceAttribute.cs | 27 + .../Client/Attributes/GraphMethodAttribute.cs | 27 + .../Attributes/GraphQLArgumentAttribute.cs | 23 + .../Attributes/GraphQLMemberAttribute.cs | 35 + .../GraphShadowPropertyAttribute.cs | 21 + .../Generated/Runtime/Client/ClientBuilder.cs | 32 + .../Runtime/Client/Common/ClientJson.cs | 41 + .../Runtime/Client/Common/CustomScalar.cs | 33 + .../Client/Common/GraphEnvelopeReader.cs | 154 ++ .../Runtime/Client/Common/GraphQLTypeBase.cs | 58 + .../Runtime/Client/Common/ICursorPaging.cs | 28 + .../Runtime/Client/Common/IPageInfo.cs | 40 + .../Runtime/Client/Common/LazyProperty.cs | 35 + .../Converters/CustomScalarConverter.cs | 56 + .../Client/Converters/EnumConverter.cs | 359 ++++ .../Client/Converters/GraphInputConverter.cs | 33 + .../Client/Converters/InterfaceConverter.cs | 76 + .../Client/Exceptions/GraphErrorCode.cs | 70 + .../GraphQueryExecutionException.cs | 65 + .../Exceptions/GraphQueryRequestException.cs | 29 + .../GraphQueryTranslationException.cs | 50 + .../Generated/Runtime/Client/Extensions.cs | 80 + .../Generated/Runtime/Client/GraphBase.cs | 55 + .../Runtime/Client/GraphBaseExecute.cs | 212 ++ .../Generated/Runtime/Client/GraphClient.cs | 226 ++ .../Runtime/Client/GraphClientOptions.cs | 115 + .../Runtime/Client/GraphCursorPager.cs | 179 ++ .../Runtime/Client/GraphCursorQuery.cs | 39 + .../Runtime/Client/GraphInputBase.cs | 50 + .../Runtime/Client/GraphQLRequest.cs | 25 + .../Generated/Runtime/Client/GraphQuery.cs | 37 + .../Runtime/Client/GraphQueryExecute.cs | 64 + .../Generated/Runtime/Client/GraphResult.cs | 34 + .../Client/HttpClientBuilderExtensions.cs | 72 + .../Runtime/Client/IGraphClientBuilder.cs | 24 + .../Runtime/Client/IGraphClientSource.cs | 28 + .../Runtime/Client/IGraphSchemaSource.cs | 25 + .../Generated/Runtime/Client/OperationType.cs | 23 + .../Generated/Runtime/Client/QueryExecutor.cs | 226 ++ .../Generated/Runtime/Client/QueryNode.cs | 260 +++ .../Runtime/Client/RequestConfigurations.cs | 42 + .../Runtime/Client/Schema/GraphQLSchema.cs | 51 + .../Runtime/Client/Schema/Helpers.cs | 41 + .../Runtime/Client/SubscriptionSubprotocol.cs | 37 + .../Generated/Runtime/Client/Utilities.cs | 200 ++ .../Client/Visitors/ExpressionEvaluator.cs | 117 ++ .../Runtime/Client/Visitors/LinqOperator.cs | 140 ++ .../Client/Visitors/QueryExpressionVisitor.cs | 384 ++++ .../Generated/Runtime/RuntimeInfo.cs | 20 + .../Subscriptions/GraphSubscription.cs | 36 + .../Subscriptions/GraphSubscriptionExecute.cs | 312 +++ .../Subscriptions/IdleTimeoutStream.cs | 127 ++ .../Runtime/Subscriptions/SSEClient.cs | 325 +++ .../Subscriptions/SubscribeCommands.cs | 24 + .../Subscriptions/SubscriptionEventBuffer.cs | 96 + .../Subscriptions/SubscriptionProtocols.cs | 23 + .../Runtime/Subscriptions/WSClient.cs | 843 ++++++++ .../Runtime/Subscriptions/WebsocketRequest.cs | 41 + .../Subscriptions/WebsocketRequestTypes.cs | 30 + .../Subscriptions/WebsocketResponse.cs | 27 + .../Generated/Types/Address.cs | 35 + .../Generated/Types/AnimalsConnection.cs | 55 + .../Generated/Types/AnimalsEdge.cs | 39 + .../Generated/Types/Customer.cs | 71 + .../Generated/Types/Item.cs | 31 + .../Generated/Types/Order.cs | 98 + .../Generated/Types/OrderLine.cs | 39 + .../Generated/Types/OrdersConnection.cs | 55 + .../Generated/Types/OrdersEdge.cs | 39 + .../OrdersNoBackwardPaginationConnection.cs | 55 + .../Types/OrdersNoBackwardPaginationEdge.cs | 39 + .../Types/OrdersNoTotalCountConnection.cs | 47 + .../Generated/Types/OrdersNoTotalCountEdge.cs | 39 + .../Types/OrdersOffsetPagingConnection.cs | 47 + .../Generated/Types/OrdersOffsetPagingEdge.cs | 39 + .../Generated/Types/Pig.cs | 38 + .../Generated/Types/Spider.cs | 38 + .../Linq2GraphQL.TestClientStandalone.csproj | 8 +- test/Linq2GraphQL.Tests.Standalone/.gitignore | 2 - .../CoexistenceTests.cs | 52 - .../Linq2GraphQL.Tests.Standalone.csproj | 34 +- .../StandaloneNamespaces.cs | 21 + test/Linq2GraphQL.Tests/CoexistenceTests.cs | 42 + .../Linq2GraphQL.Tests.csproj | 1 + .../StandaloneGeneratorTests.cs | 87 + test/Linq2GraphQL.Tests/packages.lock.json | 8 + 148 files changed, 12756 insertions(+), 552 deletions(-) delete mode 100644 scripts/spike-standalone.ps1 create mode 100644 src/Linq2GraphQL.Generator/StandaloneRuntime.cs delete mode 100644 test/Linq2GraphQL.TestClientLegacy/.gitignore delete mode 100644 test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj delete mode 100644 test/Linq2GraphQL.TestClientLegacy/packages.lock.json delete mode 100644 test/Linq2GraphQL.TestClientSecond/.gitignore delete mode 100644 test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj delete mode 100644 test/Linq2GraphQL.TestClientSecond/packages.lock.json delete mode 100644 test/Linq2GraphQL.TestClientStandalone/.gitignore create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/MutationMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/QueryMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClient.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientExtensions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientSchema.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Client/SubscriptionMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Enums/AddressType.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Enums/CustomerStatus.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Enums/SortEnumType.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressSortInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerSortInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerStatusOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DateTimeOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DecimalOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/FloatOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalSortInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/InputFactory.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IntOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderLineFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListUnsignedByteOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalDateOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalTimeOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderSortInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/StringOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UnsignedByteOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UuidOperationFilterInput.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IAnimal.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IMutationMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IQueryMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISampleClient.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISubscriptionMethods.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ArgumentValue.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphArgumentAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphInterfaceAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphMethodAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLArgumentAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLMemberAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphShadowPropertyAttribute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ClientBuilder.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ClientJson.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/CustomScalar.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphEnvelopeReader.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphQLTypeBase.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ICursorPaging.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/IPageInfo.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/LazyProperty.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/CustomScalarConverter.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/EnumConverter.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/GraphInputConverter.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/InterfaceConverter.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphErrorCode.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryExecutionException.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryRequestException.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryTranslationException.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Extensions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBase.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBaseExecute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClient.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClientOptions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorPager.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorQuery.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphInputBase.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQLRequest.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQuery.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQueryExecute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphResult.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/HttpClientBuilderExtensions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientBuilder.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientSource.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphSchemaSource.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/OperationType.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryExecutor.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryNode.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/RequestConfigurations.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/GraphQLSchema.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/Helpers.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/SubscriptionSubprotocol.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Utilities.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/ExpressionEvaluator.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/LinqOperator.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/QueryExpressionVisitor.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/RuntimeInfo.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscription.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscriptionExecute.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/IdleTimeoutStream.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SSEClient.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscribeCommands.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionEventBuffer.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionProtocols.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WSClient.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequest.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequestTypes.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketResponse.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Address.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsConnection.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsEdge.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Customer.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Item.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Order.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrderLine.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersConnection.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersEdge.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationConnection.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationEdge.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountConnection.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountEdge.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingConnection.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingEdge.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Pig.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Types/Spider.cs delete mode 100644 test/Linq2GraphQL.Tests.Standalone/.gitignore delete mode 100644 test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs create mode 100644 test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs create mode 100644 test/Linq2GraphQL.Tests/CoexistenceTests.cs create mode 100644 test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index 706a8eb2..9911361c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,8 @@ Key details that bite: **T4 workflow (from DEVELOPER.md — read it before touching templates):** each template is three files — `X.tt` (source, edit this), `X.tt.cs` (hand-written partial with constructor params and helpers), and `X.g.cs` (preprocessed output, **generated at build time, gitignored**). The generator's csproj runs the pinned `dotnet-t4` local tool (`.config/dotnet-tools.json`) over every `Templates\**\*.tt` before compiling, so editing a `.tt` and building is the whole loop and stale template logic cannot be built. Compile errors inside template code are reported against the `.tt` file and line. A template in a new folder needs a `T4Template` item with a `TemplateNamespace` in the csproj. +**Standalone mode** (`--standalone`, `StandaloneRuntime.cs`) makes the output carry its own copy of the runtime instead of depending on the packages, so clients generated by different versions can share an app. The csproj embeds `src/Linq2GraphQL.Client` and `.Subscriptions` source (minus `Assembly.cs`) as resources; the generator writes it to `Runtime/` with `Linq2GraphQL.Client` rewritten to `.Runtime` everywhere, including the generated client files. So the runtime must stay copyable: no reliance on its own assembly name, no `InternalsVisibleTo` from generated code, no hard-coded `"Linq2GraphQL.Client"` strings, and anything shared across apps (like the safe-mode cache key) must be keyed by type so copies don't collide. `TestClientStandalone` is the checked-in standalone output; `Linq2GraphQL.Tests.Standalone` compiles the main suite's test files against it, with `StandaloneNamespaces.cs` pointing the package namespaces at the copy. + `GeneratorSettings.Current.Nullable` is ambient static state read from inside templates; the nullable and non-nullable clients differ mainly in nullable annotations and `#pragma warning disable CS8618`. `GeneratorSettings.Current.TypeMapping` is the GraphQL-scalar-to-CLR-type table for the run: `Helpers.DefaultTypeMapping` with any `scalarMappings` from the `--config` settings file applied on top (`GeneratorConfig`). It is read in exactly two places - `BaseType.GetCoreType` for the emitted type name and `Schema.GetCustomScalars`, which treats absence from the table as "generate a `CustomScalar` class for this scalar" - so mapping a scalar to a simple type also suppresses its generated class, and mapping it to `null` does the reverse. Override targets are validated against `Helpers.SupportedTargetTypes`, an allow-list, because a null `CoreType.CSharpType` silently changes nullability and the input-factory template rather than failing. @@ -73,6 +75,6 @@ Key details that bite: `Linq2GraphQL.Tests` is xUnit + Shouldly + Moq. It spins up the real GraphQL server in-process with `WebApplicationFactory` — `Linq2GraphQL.TestServer` is HotChocolate over the POCOs in `TestServer.Shared`, and `TestServerNullable` is the same schema for the nullable client. `SampleClientFixture` / `SampleClientNullableFixture` wire the generated client to that in-memory host (safe mode on, SSE subscriptions), and test classes take them via `IClassFixture<>`. So most tests are end-to-end: an assertion failure can come from the expression parser, the query text, or the server's own resolvers. -`Linq2GraphQL.TestClient` / `TestClientNullable` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots both test servers over plain HTTP, regenerates both clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. +`Linq2GraphQL.TestClient` / `TestClientNullable` / `TestClientStandalone` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots both test servers over plain HTTP, regenerates both clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. `docs/` (the Blazor documentation site) and `StartGG/` (a sample client) are outside the CI solution filter and can drift. diff --git a/Linq2GraphQL.CI.slnf b/Linq2GraphQL.CI.slnf index e99fb3e6..be5bfa99 100644 --- a/Linq2GraphQL.CI.slnf +++ b/Linq2GraphQL.CI.slnf @@ -10,10 +10,12 @@ "test\\Linq2GraphQL.Agent.Sample\\Linq2GraphQL.Agent.Sample.csproj", "test\\Linq2GraphQL.TestClient\\Linq2GraphQL.TestClient.csproj", "test\\Linq2GraphQL.TestClientNullable\\Linq2GraphQL.TestClientNullable.csproj", + "test\\Linq2GraphQL.TestClientStandalone\\Linq2GraphQL.TestClientStandalone.csproj", "test\\Linq2GraphQL.TestServer\\Linq2GraphQL.TestServer.csproj", "test\\Linq2GraphQL.TestServer.Shared\\Linq2GraphQL.TestServer.Shared.csproj", "test\\Linq2GraphQL.TestServerNullable\\Linq2GraphQL.TestServerNullable.csproj", - "test\\Linq2GraphQL.Tests\\Linq2GraphQL.Tests.csproj" + "test\\Linq2GraphQL.Tests\\Linq2GraphQL.Tests.csproj", + "test\\Linq2GraphQL.Tests.Standalone\\Linq2GraphQL.Tests.Standalone.csproj" ] } } diff --git a/Linq2GraphQL.sln b/Linq2GraphQL.sln index ef044bf5..51b9a7b4 100644 --- a/Linq2GraphQL.sln +++ b/Linq2GraphQL.sln @@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.TestServer.Sha EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.TestClientNullable", "test\Linq2GraphQL.TestClientNullable\Linq2GraphQL.TestClientNullable.csproj", "{8083E0AF-8429-4EA1-9BC9-8B6EE5EFB9B2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.TestClientStandalone", "test\Linq2GraphQL.TestClientStandalone\Linq2GraphQL.TestClientStandalone.csproj", "{B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.Tests.Standalone", "test\Linq2GraphQL.Tests.Standalone\Linq2GraphQL.Tests.Standalone.csproj", "{DB460038-5D98-46AC-8551-5F928C2EC76A}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.Console", "test\Linq2GraphQL.Console\Linq2GraphQL.Console.csproj", "{058B468F-CE72-4CFF-8847-87CEED642C4E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Linq2GraphQL.Agent.Sample", "test\Linq2GraphQL.Agent.Sample\Linq2GraphQL.Agent.Sample.csproj", "{6E3C2B71-4D8A-4F1E-9B57-2A9C0D4E8F13}" @@ -92,6 +96,14 @@ Global {8083E0AF-8429-4EA1-9BC9-8B6EE5EFB9B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {8083E0AF-8429-4EA1-9BC9-8B6EE5EFB9B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {8083E0AF-8429-4EA1-9BC9-8B6EE5EFB9B2}.Release|Any CPU.Build.0 = Release|Any CPU + {B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60}.Release|Any CPU.Build.0 = Release|Any CPU + {DB460038-5D98-46AC-8551-5F928C2EC76A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB460038-5D98-46AC-8551-5F928C2EC76A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB460038-5D98-46AC-8551-5F928C2EC76A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB460038-5D98-46AC-8551-5F928C2EC76A}.Release|Any CPU.Build.0 = Release|Any CPU {058B468F-CE72-4CFF-8847-87CEED642C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {058B468F-CE72-4CFF-8847-87CEED642C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {058B468F-CE72-4CFF-8847-87CEED642C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -135,6 +147,8 @@ Global {F0D6061C-3D3F-4323-AE4F-39737F5E2247} = {3D9B8FE2-6906-4190-9AB4-845BAB6E403C} {C1908816-34E7-4B41-9E39-2283C59DB017} = {5BE1963B-E57D-4CF5-B03E-644D8F39B4D2} {8083E0AF-8429-4EA1-9BC9-8B6EE5EFB9B2} = {3D9B8FE2-6906-4190-9AB4-845BAB6E403C} + {B6FCB2DB-88F4-44DD-8A77-4675AA6E8D60} = {1F4F9593-3C19-4C2E-AF8C-C269659AA12A} + {DB460038-5D98-46AC-8551-5F928C2EC76A} = {5BE1963B-E57D-4CF5-B03E-644D8F39B4D2} {058B468F-CE72-4CFF-8847-87CEED642C4E} = {5BE1963B-E57D-4CF5-B03E-644D8F39B4D2} {6E3C2B71-4D8A-4F1E-9B57-2A9C0D4E8F13} = {5BE1963B-E57D-4CF5-B03E-644D8F39B4D2} {C0E1A444-A834-4FC0-8436-38066EA8D37D} = {F646B02D-53FC-4C88-8941-2255A2F4DAF6} diff --git a/scripts/regenerate-test-clients.ps1 b/scripts/regenerate-test-clients.ps1 index 14ca4728..068690e8 100644 --- a/scripts/regenerate-test-clients.ps1 +++ b/scripts/regenerate-test-clients.ps1 @@ -4,10 +4,11 @@ Regenerates the checked-in test clients from the test servers' live schemas. .DESCRIPTION - test/Linq2GraphQL.TestClient and test/Linq2GraphQL.TestClientNullable hold - generated output that the test suite compiles against. Nothing in the normal - build refreshes it, so it silently drifts whenever a template or the test - schema changes. + test/Linq2GraphQL.TestClient, test/Linq2GraphQL.TestClientNullable and + test/Linq2GraphQL.TestClientStandalone (standalone mode, with its own copy of + the runtime) hold generated output that the test suites compile against. + Nothing in the normal build refreshes it, so it silently drifts whenever a + template, the runtime or the test schema changes. This script boots each test server over plain HTTP, runs the generator against it with the flags that produced the checked-in output, and writes @@ -47,6 +48,15 @@ $clients = @( Port = 5181 Nullable = $true } + @{ + Name = 'SampleClient' + Server = 'test/Linq2GraphQL.TestServer' + Output = 'test/Linq2GraphQL.TestClientStandalone/Generated' + Namespace = 'Linq2GraphQL.TestClientStandalone' + Port = 5182 + Nullable = $false + Standalone = $true + } ) function Wait-ForServer([string]$url, [System.Diagnostics.Process]$process) { @@ -99,6 +109,7 @@ try { '--agent=true' ) if ($client.Nullable) { $arguments += '-nu=true' } + if ($client.Standalone) { $arguments += '--standalone=true' } $sentinel = Join-Path $repoRoot "$($client.Output)/Client/$($client.Name).cs" $generatedAt = Get-Date @@ -131,7 +142,10 @@ try { # so git status reports every regenerated file as modified even when its content is # unchanged. git diff applies the same normalization git would apply on checkin, so # it reports only real drift. - $diff = @(git diff --stat -- @paths) + # + # The standalone client's RuntimeInfo.cs records the generator version, which changes with every + # commit; -I ignores a change made only of lines matching that one. + $diff = @(git diff --stat -I '^\s*public const string Version = ' -- @paths) # git diff only compares tracked files, so a newly generated file needs its own check. $added = @(git ls-files --others --exclude-standard -- @paths) diff --git a/scripts/spike-standalone.ps1 b/scripts/spike-standalone.ps1 deleted file mode 100644 index ea162685..00000000 --- a/scripts/spike-standalone.ps1 +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env pwsh -<# -.SYNOPSIS - Spike: builds "standalone" SampleClients that carry their own copy of the runtime. - -.DESCRIPTION - A standalone client is the generated SampleClient plus the runtime source - (Linq2GraphQL.Client + Linq2GraphQL.Client.Subscriptions) with the Linq2GraphQL.Client - namespace rewritten to .Runtime. Its project has no reference to the - runtime packages. - - Writes three of them: - test/Linq2GraphQL.TestClientStandalone current source, namespace Linq2GraphQL.TestClient - test/Linq2GraphQL.TestClientSecond current source, namespace Linq2GraphQL.SecondClient - test/Linq2GraphQL.TestClientLegacy source from -LegacySource, namespace Linq2GraphQL.LegacyClient - - The existing end-to-end tests that only depend on SampleClient and the runtime are - copied into test/Linq2GraphQL.Tests.Standalone with the same rewrite, so the same - assertions run against the standalone copy. The other two clients exist so a test can - load several standalone clients into one process. - -.PARAMETER LegacySource - Root of an older checkout, e.g. extracted with - git archive 1.0.30-prerelease src test/Linq2GraphQL.TestClient | tar -x -C -#> -[CmdletBinding()] -param( - [Parameter(Mandatory)] - [string]$LegacySource -) - -$ErrorActionPreference = 'Stop' -$repoRoot = Split-Path -Parent $PSScriptRoot - -# Tests that need the generator, the AI package or the nullable client stay in the main suite. -$excludedTests = @( - 'AgentLoopTests.cs', 'AgentToolsExtensionsTests.cs', 'AgentToolsetTests.cs', - 'DescriptionAttributeTests.cs', 'ExampleFormatterTests.cs', 'GeneratorCollection.cs', - 'QueryNullableTests.cs', 'QueryValidatorTests.cs', 'SampleClientNullableFixture.cs', - 'ScalarMappingTests.cs', 'ScalarTests.cs', 'SchemaRendererTests.cs', 'ScopedSchemaTests.cs', - 'SdlInputTests.cs', 'SdlOutputTests.cs' -) - -function Copy-Rewritten([string]$source, [string]$destination, [string]$clientNamespace, [string[]]$exclude = @()) { - if (Test-Path $destination) { Remove-Item -Recurse -Force $destination } - - Get-ChildItem -Path $source -Recurse -Filter *.cs | - Where-Object { $_.FullName -notmatch '[\\/](bin|obj)[\\/]' -and $exclude -notcontains $_.Name } | - ForEach-Object { - $relative = [IO.Path]::GetRelativePath($source, $_.FullName) - $target = Join-Path $destination $relative - New-Item -ItemType Directory -Force (Split-Path $target) | Out-Null - - # Runtime first: its replacement contains the client namespace, which the - # second pattern must not see as the original Linq2GraphQL.TestClient. - $text = Get-Content -Raw $_.FullName - $text = $text -replace '\bLinq2GraphQL\.Client\b', "$clientNamespace.Runtime" - $text = $text -replace '\bLinq2GraphQL\.TestClient\b', $clientNamespace - Set-Content -NoNewline -Path $target -Value $text - } -} - -function New-StandaloneClient([string]$sourceRoot, [string]$project, [string]$clientNamespace) { - Write-Host "==> $project ($clientNamespace) from $sourceRoot" -ForegroundColor Cyan - $projectDir = Join-Path $repoRoot "test/$project" - - # Assembly.cs only holds InternalsVisibleTo for the package assemblies; the copy lives - # in the client's own assembly, so it is not needed. - Copy-Rewritten (Join-Path $sourceRoot 'src/Linq2GraphQL.Client') (Join-Path $projectDir 'Runtime/Client') $clientNamespace @('Assembly.cs') - Copy-Rewritten (Join-Path $sourceRoot 'src/Linq2GraphQL.Client.Subscriptions') (Join-Path $projectDir 'Runtime/Subscriptions') $clientNamespace @('Assembly.cs') - Copy-Rewritten (Join-Path $sourceRoot 'test/Linq2GraphQL.TestClient/Generated') (Join-Path $projectDir 'Generated') $clientNamespace -} - -New-StandaloneClient $repoRoot 'Linq2GraphQL.TestClientStandalone' 'Linq2GraphQL.TestClient' -New-StandaloneClient $repoRoot 'Linq2GraphQL.TestClientSecond' 'Linq2GraphQL.SecondClient' -New-StandaloneClient $LegacySource 'Linq2GraphQL.TestClientLegacy' 'Linq2GraphQL.LegacyClient' - -Write-Host '==> Copying tests' -ForegroundColor Cyan -Copy-Rewritten (Join-Path $repoRoot 'test/Linq2GraphQL.Tests') (Join-Path $repoRoot 'test/Linq2GraphQL.Tests.Standalone/Tests') 'Linq2GraphQL.TestClient' $excludedTests - -Write-Host '==> Done' -ForegroundColor Green diff --git a/src/Linq2GraphQL.Generator/ClientGenerator.cs b/src/Linq2GraphQL.Generator/ClientGenerator.cs index cb4eff5b..7e233093 100644 --- a/src/Linq2GraphQL.Generator/ClientGenerator.cs +++ b/src/Linq2GraphQL.Generator/ClientGenerator.cs @@ -19,7 +19,8 @@ public class ClientGenerator( bool includeDeprecated, IReadOnlyDictionary scalarMappings = null, bool descriptionAttributes = true, - bool agent = false) + bool agent = false, + bool standalone = false) { private static readonly Dictionary EmptyScalarMappings = new(); @@ -239,6 +240,17 @@ private List Generate(RootSchema rootSchema) fileName = clientName + "Extensions" + ".cs"; AddFile(clientDirName, fileName, clientExtensionsTemplateText); + if (standalone) + { + Console.WriteLine("Generate Runtime (standalone)..."); + foreach (var entry in entries) + { + entry.Content = StandaloneRuntime.RewriteNamespaces(entry.Content, namespaceName); + } + + entries.AddRange(StandaloneRuntime.GetFiles(namespaceName, includeSubscriptions)); + } + return entries; } diff --git a/src/Linq2GraphQL.Generator/GeneratorConfig.cs b/src/Linq2GraphQL.Generator/GeneratorConfig.cs index 6fb945c1..f04faead 100644 --- a/src/Linq2GraphQL.Generator/GeneratorConfig.cs +++ b/src/Linq2GraphQL.Generator/GeneratorConfig.cs @@ -56,6 +56,12 @@ public class GeneratorConfig /// public bool? Agent { get; set; } + /// + /// Standalone: write a copy of the runtime into the output instead of depending on the + /// Linq2GraphQL.Client packages. + /// + public bool? Standalone { get; set; } + /// /// Emit [Description] attributes from schema descriptions. On by default; set to false to /// opt out. Settings file only, there is no command line flag for it. diff --git a/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj b/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj index 209307b2..a4bb853a 100644 --- a/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj +++ b/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj @@ -71,4 +71,37 @@ + + + + + + + + + + $(IntermediateOutputPath)PackageVersions.g.cs + + + <_PackageVersionLine Include="@(PackageVersion->' ["%(Identity)"] = "%(Version)",')"/> + + + + + + + + diff --git a/src/Linq2GraphQL.Generator/Program.cs b/src/Linq2GraphQL.Generator/Program.cs index e8dd1451..40a18bfa 100644 --- a/src/Linq2GraphQL.Generator/Program.cs +++ b/src/Linq2GraphQL.Generator/Program.cs @@ -28,6 +28,9 @@ private static async Task Main(string[] args) "Schema file (SDL) to generate from instead of an endpoint, relative to current location"); var agent = new Option(new[] { "--agent", "-ag" }, "Agent mode: also emit the schema as SDL and implement IGraphSchemaSource on the client"); + var standalone = new Option(new[] { "--standalone", "-sa" }, + "Standalone: write a copy of the runtime into the output (namespace .Runtime) instead of " + + "depending on the Linq2GraphQL.Client packages"); var rootCommand = new RootCommand("Generate GraphQL client") { @@ -42,7 +45,8 @@ private static async Task Main(string[] args) nullable, includeDeprecated, schemaFile, - agent + agent, + standalone }; rootCommand.SetHandler(async context => @@ -70,10 +74,11 @@ private static async Task Main(string[] args) var descriptionAttributesValue = config.DescriptionAttributes ?? true; var agentValue = Resolve(result, agent, config.Agent, false); + var standaloneValue = Resolve(result, standalone, config.Standalone, false); await GenerateClientAsync(uriValue, schemaFileValue, outputFolderValue, namespaceValue, clientNameValue, includeSubscriptionsValue, authTokenValue, enumStrategyValue, nullableValue, deprecatedValue, - scalarMappings, descriptionAttributesValue, agentValue); + scalarMappings, descriptionAttributesValue, agentValue, standaloneValue); } catch (GeneratorConfigurationException ex) { @@ -156,7 +161,8 @@ private static Uri ResolveEndpoint(ParseResult result, Argument argument, s private static async Task GenerateClientAsync(Uri uri, string schemaFile, string outputFolder, string namespaceName, string name, bool includeSubscriptions, string authToken, string enumStrategy, bool nullable, bool includeDeprecated, - IReadOnlyDictionary scalarMappings, bool descriptionAttributes, bool agent) + IReadOnlyDictionary scalarMappings, bool descriptionAttributes, bool agent, + bool standalone) { var enumStrat = enumStrategy != null && enumStrategy.Equals("AddUnknownOption", StringComparison.InvariantCultureIgnoreCase) @@ -164,7 +170,7 @@ private static async Task GenerateClientAsync(Uri uri, string schemaFile, string : EnumGeneratorStrategy.FailIfMissing; var generator = new ClientGenerator(namespaceName, name, includeSubscriptions, enumStrat, nullable, - includeDeprecated, scalarMappings, descriptionAttributes, agent); + includeDeprecated, scalarMappings, descriptionAttributes, agent, standalone); List entries; if (schemaFile != null) { @@ -184,6 +190,15 @@ private static async Task GenerateClientAsync(Uri uri, string schemaFile, string var outputPath = Path.GetFullPath(outputFolder, Environment.CurrentDirectory); Console.WriteLine($"Output path is set to: {outputPath}"); + + // A runtime copy from an earlier standalone run is the generator's own output: clear it, so files + // dropped from the runtime do not linger and turning standalone off does not leave a second runtime. + var runtimePath = Path.Combine(outputPath, StandaloneRuntime.DirectoryName); + if (File.Exists(Path.Combine(runtimePath, StandaloneRuntime.InfoFileName))) + { + Directory.Delete(runtimePath, true); + } + foreach (var dirName in entries.GroupBy(e => e.DirectoryName)) { var directory = Path.Combine(outputPath, dirName.Key); @@ -195,5 +210,16 @@ private static async Task GenerateClientAsync(Uri uri, string schemaFile, string await File.WriteAllTextAsync(filePath, entry.Content.ReplaceLineEndings("\n")); } } + + if (standalone) + { + Console.WriteLine(""); + Console.WriteLine($"Standalone: runtime {StandaloneRuntime.Version} written to {runtimePath}. " + + "Don't reference the Linq2GraphQL.Client packages; the project needs:"); + foreach (var (packageName, version) in StandaloneRuntime.GetPackages(includeSubscriptions)) + { + Console.WriteLine($" "); + } + } } } diff --git a/src/Linq2GraphQL.Generator/StandaloneRuntime.cs b/src/Linq2GraphQL.Generator/StandaloneRuntime.cs new file mode 100644 index 00000000..cd82abd6 --- /dev/null +++ b/src/Linq2GraphQL.Generator/StandaloneRuntime.cs @@ -0,0 +1,142 @@ +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Linq2GraphQL.Generator; + +/// +/// Standalone mode: instead of depending on the Linq2GraphQL.Client packages, the generated client carries +/// its own copy of the runtime under <namespace>.Runtime. Clients generated by different +/// generator versions can then be used in the same app, because each one brings the runtime it was +/// generated against. The runtime source is embedded in the generator at build time. +/// +internal static class StandaloneRuntime +{ + /// + /// The output folder the runtime is written to. The generator owns it: it is cleared before every run, + /// so files removed from the runtime do not linger. + /// + public const string DirectoryName = "Runtime"; + + /// + /// Written into in standalone mode. Its presence is what marks a Runtime + /// folder as the generator's to clear. + /// + public const string InfoFileName = "RuntimeInfo.cs"; + + private const string ResourcePrefix = DirectoryName + "/"; + private const string SubscriptionsPrefix = ResourcePrefix + "Subscriptions/"; + + private static readonly Regex RuntimeNamespace = new(@"\bLinq2GraphQL\.Client\b", RegexOptions.Compiled); + + private static readonly string[] ClientPackages = ["Microsoft.Extensions.Caching.Memory", "Microsoft.Extensions.Http"]; + private static readonly string[] SubscriptionsPackages = ["Websocket.Client"]; + + /// + /// The runtime project has ImplicitUsings enabled, the project the copy lands in might not. + /// + private static readonly string[] ImplicitUsings = + [ + "System", "System.Collections.Generic", "System.IO", "System.Linq", "System.Net.Http", "System.Threading", + "System.Threading.Tasks" + ]; + + public static string Version { get; } = + typeof(StandaloneRuntime).Assembly.GetCustomAttribute() + ?.InformationalVersion.Split('+')[0] ?? "unknown"; + + public static string GetRuntimeNamespace(string namespaceName) => namespaceName + ".Runtime"; + + /// + /// Points every reference to the runtime packages' namespaces at the copy. + /// + public static string RewriteNamespaces(string content, string namespaceName) => + RuntimeNamespace.Replace(content, GetRuntimeNamespace(namespaceName)); + + /// + /// The packages the copied runtime needs, with the versions it was built against. + /// + public static IReadOnlyList<(string Name, string Version)> GetPackages(bool includeSubscriptions) => + (includeSubscriptions ? ClientPackages.Concat(SubscriptionsPackages) : ClientPackages) + .Select(e => (e, PackageVersions.All.GetValueOrDefault(e, "unknown"))) + .ToList(); + + public static IEnumerable GetFiles(string namespaceName, bool includeSubscriptions) + { + var assembly = typeof(StandaloneRuntime).Assembly; + + // RecursiveDir in the resource name uses the build machine's path separator. + var resources = assembly.GetManifestResourceNames() + .Select(e => (Resource: e, Path: e.Replace('\\', '/'))) + .Where(e => e.Path.StartsWith(ResourcePrefix, StringComparison.Ordinal)) + .Where(e => includeSubscriptions || !e.Path.StartsWith(SubscriptionsPrefix, StringComparison.Ordinal)) + .OrderBy(e => e.Path, StringComparer.Ordinal); + + foreach (var (resource, path) in resources) + { + using var stream = assembly.GetManifestResourceStream(resource); + using var reader = new StreamReader(stream); + var source = reader.ReadToEnd().TrimStart(''); + + yield return new FileEntry + { + DirectoryName = Path.GetDirectoryName(path).Replace('\\', '/'), + FileName = Path.GetFileName(path), + Content = Header + MissingImplicitUsings(source) + RewriteNamespaces(source, namespaceName) + }; + } + + yield return new FileEntry + { + DirectoryName = DirectoryName, FileName = InfoFileName, Content = InfoFile(namespaceName, includeSubscriptions) + }; + } + + private const string Header = """ + //--------------------------------------------------------------------- + // + // Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator + // in standalone mode. Please don't edit this file - regenerate instead. + // The runtime version is in RuntimeInfo.cs. + // Github:https://github.com/linq2graphql/linq2graphql.client + //--------------------------------------------------------------------- + #nullable disable + + """; + + private static string MissingImplicitUsings(string source) + { + var missing = ImplicitUsings + .Where(e => !Regex.IsMatch(source, $@"^\s*using {Regex.Escape(e)};", RegexOptions.Multiline)) + .Select(e => $"using {e};\n"); + + return string.Concat(missing); + } + + private static string InfoFile(string namespaceName, bool includeSubscriptions) + { + var packages = string.Join("\n", GetPackages(includeSubscriptions) + .Select(e => $"/// {e.Name} {e.Version}")); + + return $$""" + //--------------------------------------------------------------------- + // + // Written by the Linq2GraphQL generator in standalone mode. + // Please don't edit this file - regenerate instead. + //--------------------------------------------------------------------- + + namespace {{GetRuntimeNamespace(namespaceName)}}; + + /// + /// The Linq2GraphQL runtime this copy was taken from. It needs these packages: + /// + {{packages}} + /// + /// + public static class RuntimeInfo + { + public const string Version = "{{Version}}"; + } + + """; + } +} diff --git a/test/Linq2GraphQL.TestClientLegacy/.gitignore b/test/Linq2GraphQL.TestClientLegacy/.gitignore deleted file mode 100644 index bc297ce7..00000000 --- a/test/Linq2GraphQL.TestClientLegacy/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Written by scripts/spike-standalone.ps1 -Runtime/ -Generated/ diff --git a/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj b/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj deleted file mode 100644 index 902c56fd..00000000 --- a/test/Linq2GraphQL.TestClientLegacy/Linq2GraphQL.TestClientLegacy.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - net10.0 - enable - disable - - - - - - - - - - - - - diff --git a/test/Linq2GraphQL.TestClientLegacy/packages.lock.json b/test/Linq2GraphQL.TestClientLegacy/packages.lock.json deleted file mode 100644 index 99638226..00000000 --- a/test/Linq2GraphQL.TestClientLegacy/packages.lock.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "version": 2, - "dependencies": { - "net10.0": { - "Microsoft.Extensions.Caching.Memory": { - "type": "Direct", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Http": { - "type": "Direct", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Diagnostics": "10.0.11", - "Microsoft.Extensions.Logging": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - }, - "Nerdbank.GitVersioning": { - "type": "Direct", - "requested": "[3.8.118, )", - "resolved": "3.8.118", - "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" - }, - "Websocket.Client": { - "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.IO.RecyclableMemoryStream": "3.0.0", - "System.Reactive": "6.0.0" - } - }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.11", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.11", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.Configuration.Binder": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" - }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" - }, - "System.Reactive": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" - }, - "Microsoft.Extensions.Configuration": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.Logging": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - } - } - } -} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientSecond/.gitignore b/test/Linq2GraphQL.TestClientSecond/.gitignore deleted file mode 100644 index bc297ce7..00000000 --- a/test/Linq2GraphQL.TestClientSecond/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Written by scripts/spike-standalone.ps1 -Runtime/ -Generated/ diff --git a/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj b/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj deleted file mode 100644 index b77a634f..00000000 --- a/test/Linq2GraphQL.TestClientSecond/Linq2GraphQL.TestClientSecond.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - net10.0 - enable - disable - - - - - - - - - - - - - diff --git a/test/Linq2GraphQL.TestClientSecond/packages.lock.json b/test/Linq2GraphQL.TestClientSecond/packages.lock.json deleted file mode 100644 index 99638226..00000000 --- a/test/Linq2GraphQL.TestClientSecond/packages.lock.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "version": 2, - "dependencies": { - "net10.0": { - "Microsoft.Extensions.Caching.Memory": { - "type": "Direct", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Http": { - "type": "Direct", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "ujx8RvcKzkxPFBguwgiygbwWVHVK0P7HFlNJ6I0JBRb28tzwe42jixBoA+dGmqG9IHepPVcm04vv2IDnU93ekA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Diagnostics": "10.0.11", - "Microsoft.Extensions.Logging": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - }, - "Nerdbank.GitVersioning": { - "type": "Direct", - "requested": "[3.8.118, )", - "resolved": "3.8.118", - "contentHash": "cRaG+ICcECG+CzbtQyUV2WftH7yl2B02AjYGGNScXx8TwYavZYwhCewBTiC0qTcsac7m6AzBUYna5xzBWmTGYw==" - }, - "Websocket.Client": { - "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.IO.RecyclableMemoryStream": "3.0.0", - "System.Reactive": "6.0.0" - } - }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "rFn8RuszZn3qquPVkDytMUlPc2+rXl9MCoygwc1XmAgC5vg5/oXJ8hkOosOrLoBLsqdTy4lFwP6iQdPS9uSYOA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.11", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "HT70uGPxMLqqnOzKMcnQtDmeV4r0KHr4qVCLhP7SXil9jMEm8sQXwcybxVVFGXZJ1V44xV0mLqQ54aZbcR2OiQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.11", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.11", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.11" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "se7Kx8QpJEt+nf26L4qIVAofGTDr1wbexxsh/Fm3Xc04xUkqUXK06KUS7FLwSQYSjqb7q9n+T7MEcXYBhI1Y5g==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "syEhXQ/sEaSBFaqzlp9gDGHX/nk6gkQkh1sIUpBO1mlBj3Phu1rmb4ML1uCiyPW9N6Kxfxv3y5FGObC+bV01Qw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.Configuration.Binder": "10.0.11", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" - }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" - }, - "System.Reactive": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" - }, - "Microsoft.Extensions.Configuration": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "wlhRqZW8LcJPa+vk2oLAc/REXDItHtkFQdf/QcXYGZbZOO13izcsKY1pCvuFQYwUiZD+hwSZwsKASjqT+BNaVg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", - "Microsoft.Extensions.Primitives": "10.0.11" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" - } - }, - "Microsoft.Extensions.Logging": { - "type": "CentralTransitive", - "requested": "[10.0.11, )", - "resolved": "10.0.11", - "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.11", - "Microsoft.Extensions.Logging.Abstractions": "10.0.11", - "Microsoft.Extensions.Options": "10.0.11" - } - } - } - } -} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/.gitignore b/test/Linq2GraphQL.TestClientStandalone/.gitignore deleted file mode 100644 index bc297ce7..00000000 --- a/test/Linq2GraphQL.TestClientStandalone/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Written by scripts/spike-standalone.ps1 -Runtime/ -Generated/ diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/MutationMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/MutationMethods.cs new file mode 100644 index 00000000..cf2fd928 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/MutationMethods.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Implementation of MutationMethods GraphQL operations +/// +public class MutationMethods : IMutationMethods +{ + private readonly GraphClient client; + + /// + /// Initializes a new instance of the MutationMethods class + /// + /// The GraphQL client instance + public MutationMethods(GraphClient client) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes setName GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery SetName(string name = null) + { + var arguments = new List + { + new("name", "String", name), + }; + + return new GraphQuery(client, "setName", OperationType.Mutation, arguments); + } + + /// + /// Executes addCustomer GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery AddCustomer(CustomerInput customer = null) + { + var arguments = new List + { + new("customer", "CustomerInput", customer), + }; + + return new GraphQuery(client, "addCustomer", OperationType.Mutation, arguments); + } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/QueryMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/QueryMethods.cs new file mode 100644 index 00000000..b104c05f --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/QueryMethods.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Implementation of QueryMethods GraphQL operations +/// +public class QueryMethods : IQueryMethods +{ + private readonly GraphClient client; + + /// + /// Initializes a new instance of the QueryMethods class + /// + /// The GraphQL client instance + public QueryMethods(GraphClient client) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes hello GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery Hello(string name = null) + { + var arguments = new List + { + new("name", "String", name), + }; + + return new GraphQuery(client, "hello", OperationType.Query, arguments); + } + + /// + /// Executes raiseError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery RaiseError(string message = null) + { + var arguments = new List + { + new("message", "String", message), + }; + + return new GraphQuery(client, "raiseError", OperationType.Query, arguments); + } + + /// + /// Executes raiseAuthError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery RaiseAuthError() + { + var arguments = new List + { + }; + + return new GraphQuery(client, "raiseAuthError", OperationType.Query, arguments); + } + + /// + /// Executes customerReturnNull GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery CustomerReturnNull() + { + var arguments = new List + { + }; + + return new GraphQuery(client, "customerReturnNull", OperationType.Query, arguments); + } + + /// + /// Executes customers GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery> + public GraphQuery> Customers() + { + var arguments = new List + { + }; + + return new GraphQuery>(client, "customers", OperationType.Query, arguments); + } + + /// + /// Executes ordersNoBackwardPagination GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery OrdersNoBackwardPagination([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, OrderFilterInput where = null, List order = null) + { + var arguments = new List + { + new("first", "Int", first), + new("after", "String", after), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), + }; + + return new GraphQuery(client, "ordersNoBackwardPagination", OperationType.Query, arguments); + } + + /// + /// Executes ordersNoTotalCount GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + public GraphCursorQuery OrdersNoTotalCount([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null) + { + var arguments = new List + { + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), + }; + + return new GraphCursorQuery(client, "ordersNoTotalCount", OperationType.Query, arguments); + } + + /// + /// Executes orders GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + public GraphCursorQuery Orders([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null) + { + var arguments = new List + { + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), + }; + + return new GraphCursorQuery(client, "orders", OperationType.Query, arguments); + } + + /// + /// Executes animals GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + public GraphCursorQuery Animals([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, IAnimalFilterInput where = null, List order = null) + { + var arguments = new List + { + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "IAnimalFilterInput", where), + new("order", "[IAnimalSortInput!]", order), + }; + + return new GraphCursorQuery(client, "animals", OperationType.Query, arguments); + } + + /// + /// Executes ordersOffsetPaging GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + public GraphCursorQuery OrdersOffsetPaging([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null) + { + var arguments = new List + { + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), + }; + + return new GraphCursorQuery(client, "ordersOffsetPaging", OperationType.Query, arguments); + } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClient.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClient.cs new file mode 100644 index 00000000..b9d31a1c --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClient.cs @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// GraphQL client for SampleClient operations +/// +/// +/// Provides strongly-typed access to GraphQL queries, mutations, and subscriptions. +/// Supports dependency injection for better testability and flexibility. +/// +public class SampleClient : ISampleClient, IGraphClientSource, IGraphSchemaSource +{ + private readonly GraphClient client; + + /// + /// Constructor with dependency injection support + /// + /// HTTP client for GraphQL requests + /// GraphQL client configuration options + /// Service provider for dependency resolution + /// Optional query methods implementation (uses default if null) + /// Optional mutation methods implementation (uses default if null) + /// Optional subscription methods implementation (uses default if null) + public SampleClient( + HttpClient httpClient, + [FromKeyedServices("SampleClient")] IOptions options, + IServiceProvider provider, + IQueryMethods queryMethods = null, + IMutationMethods mutationMethods = null, + ISubscriptionMethods subscriptionMethods = null) + { + client = new GraphClient(httpClient, options, provider, true); + Query = queryMethods ?? new QueryMethods(client); + Mutation = mutationMethods ?? new MutationMethods(client); + Subscription = subscriptionMethods ?? new SubscriptionMethods(client); + } + + private SampleClient(GraphClient client) + { + this.client = client; + Query = new QueryMethods(client); + Mutation = new MutationMethods(client); + Subscription = new SubscriptionMethods(client); + } + + /// + /// A client that shares this client's connection and settings, and also runs + /// on every request it sends. Use it for per-user or + /// per-conversation headers such as an access token. This client is not changed. + /// + /// + /// The new client uses the generated operation implementations, not ones passed to the constructor. + /// + public SampleClient WithRequestConfiguration(Func configureRequest) + { + return new SampleClient(client.WithRequestConfiguration(configureRequest)); + } + + /// + /// A client that sends Authorization: Bearer <token> on every request, asking + /// for the token each time. This client is not changed. + /// + public SampleClient WithAccessToken(Func> getAccessToken) + { + return WithRequestConfiguration(RequestConfigurations.BearerToken(getAccessToken)); + } + + /// + /// The underlying client, for example to execute a query written as GraphQL text with + /// . + /// + public GraphClient GraphClient => client; + + /// + /// The schema this client was generated from, as SDL. + /// + public static string Sdl => SampleClientSchema.Sdl; + + /// + /// Gets the query methods for this GraphQL client + /// + public IQueryMethods Query { get; private set; } + /// + /// Gets the mutation methods for this GraphQL client + /// + public IMutationMethods Mutation { get; private set; } + /// + /// Gets the subscription methods for this GraphQL client + /// + public ISubscriptionMethods Subscription { get; private set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientExtensions.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientExtensions.cs new file mode 100644 index 00000000..7abc0ec5 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientExtensions.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Linq2GraphQL.TestClientStandalone; + +public static class SampleClientExtensions +{ + private const string ClientName = "SampleClient"; + + public static IGraphClientBuilder AddSampleClient(this IServiceCollection services) + { + var graphClientOptions = new GraphClientOptions(); + return GraphClientBuilder(services, graphClientOptions); + } + + public static IGraphClientBuilder AddSampleClient(this IServiceCollection services, Action opts) + { + var graphClientOptions = new GraphClientOptions(); + opts(graphClientOptions); + + return GraphClientBuilder(services, graphClientOptions); + } + + private static IGraphClientBuilder GraphClientBuilder(IServiceCollection services, + GraphClientOptions graphClientOptions) + { + var opts = Options.Create(graphClientOptions); + services.AddKeyedSingleton(ClientName, opts); + services.AddMemoryCache(); + return new ClientBuilder(ClientName, services); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientSchema.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientSchema.cs new file mode 100644 index 00000000..1dda5edb --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SampleClientSchema.cs @@ -0,0 +1,588 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// The schema SampleClient was generated from, as SDL. +/// +public static partial class SampleClientSchema +{ + public const string Sdl = """ + type Query { + hello(name: String = "World"): String + raiseError(message: String = "Test error"): String + raiseAuthError: String + customerReturnNull: Customer + customers: [Customer] + ordersNoBackwardPagination( + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + where: OrderFilterInput, + order: [OrderSortInput!]): OrdersNoBackwardPaginationConnection + ordersNoTotalCount( + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the last _n_ elements from the list." + last: Int, + "Returns the elements in the list that come before the specified cursor." + before: String, + where: OrderFilterInput, + order: [OrderSortInput!]): OrdersNoTotalCountConnection + orders( + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the last _n_ elements from the list." + last: Int, + "Returns the elements in the list that come before the specified cursor." + before: String, + where: OrderFilterInput, + order: [OrderSortInput!]): OrdersConnection + animals( + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the last _n_ elements from the list." + last: Int, + "Returns the elements in the list that come before the specified cursor." + before: String, + where: IAnimalFilterInput, + order: [IAnimalSortInput!]): AnimalsConnection + ordersOffsetPaging( + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the last _n_ elements from the list." + last: Int, + "Returns the elements in the list that come before the specified cursor." + before: String, + where: OrderFilterInput, + order: [OrderSortInput!]): OrdersOffsetPagingConnection + } + + type Mutation { + setName(name: String): String + addCustomer(customer: CustomerInput): Customer + } + + type Subscription { + customerAdded: Customer + customerNameAdded(name: String): Customer + } + + type Pig implements IAnimal { + spices: String! + name: String! + numberOfLegs: Int! + speed: Int! + } + + type Spider implements IAnimal { + poisonous: Boolean! + name: String! + numberOfLegs: Int! + speed: Int! + } + + input OrderFilterInput { + and: [OrderFilterInput!] + or: [OrderFilterInput!] + orderId: UuidOperationFilterInput + customer: CustomerFilterInput + address: AddressFilterInput + orderDate: DateTimeOperationFilterInput + orderDateTime: DateTimeOperationFilterInput + orderDay: LocalDateOperationFilterInput + lines: ListFilterInputTypeOfOrderLineFilterInput + entryTime: LocalTimeOperationFilterInput + grade: StringOperationFilterInput @deprecated(reason: "This propery is obsolete and should not be used!") + } + + input OrderSortInput { + orderId: SortEnumType + customer: CustomerSortInput + address: AddressSortInput + orderDate: SortEnumType + orderDateTime: SortEnumType + orderDay: SortEnumType + entryTime: SortEnumType + grade: SortEnumType @deprecated(reason: "This propery is obsolete and should not be used!") + } + + input IAnimalFilterInput { + and: [IAnimalFilterInput!] + or: [IAnimalFilterInput!] + name: StringOperationFilterInput + numberOfLegs: IntOperationFilterInput + } + + input IAnimalSortInput { + name: SortEnumType + numberOfLegs: SortEnumType + } + + "A connection to a list of items." + type OrdersNoBackwardPaginationConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [OrdersNoBackwardPaginationEdge!] + "A flattened list of the nodes." + nodes: [Order] + "Identifies the total count of items in the connection." + totalCount: Int! + } + + "A connection to a list of items." + type OrdersNoTotalCountConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [OrdersNoTotalCountEdge!] + "A flattened list of the nodes." + nodes: [Order] + } + + "A connection to a list of items." + type OrdersConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [OrdersEdge!] + "A flattened list of the nodes." + nodes: [Order] + "Identifies the total count of items in the connection." + totalCount: Int! + } + + "A connection to a list of items." + type AnimalsConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [AnimalsEdge!] + "A flattened list of the nodes." + nodes: [IAnimal] + "Identifies the total count of items in the connection." + totalCount: Int! + } + + "A connection to a list of items." + type OrdersOffsetPagingConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [OrdersOffsetPagingEdge!] + "A flattened list of the nodes." + nodes: [Order] + } + + input UuidOperationFilterInput { + eq: UUID + neq: UUID + in: [UUID] + nin: [UUID] + gt: UUID + ngt: UUID + gte: UUID + ngte: UUID + lt: UUID + nlt: UUID + lte: UUID + nlte: UUID + } + + input CustomerFilterInput { + and: [CustomerFilterInput!] + or: [CustomerFilterInput!] + customerId: UuidOperationFilterInput + customerName: StringOperationFilterInput + status: CustomerStatusOperationFilterInput + orders: ListFilterInputTypeOfOrderFilterInput + address: AddressFilterInput + created: DateTimeOperationFilterInput + } + + input AddressFilterInput { + and: [AddressFilterInput!] + or: [AddressFilterInput!] + name: StringOperationFilterInput + street: StringOperationFilterInput + postalCode: StringOperationFilterInput + createdDate: LocalDateOperationFilterInput + } + + input DateTimeOperationFilterInput { + eq: DateTime + neq: DateTime + in: [DateTime] + nin: [DateTime] + gt: DateTime + ngt: DateTime + gte: DateTime + ngte: DateTime + lt: DateTime + nlt: DateTime + lte: DateTime + nlte: DateTime + } + + input LocalDateOperationFilterInput { + eq: LocalDate + neq: LocalDate + in: [LocalDate] + nin: [LocalDate] + gt: LocalDate + ngt: LocalDate + gte: LocalDate + ngte: LocalDate + lt: LocalDate + nlt: LocalDate + lte: LocalDate + nlte: LocalDate + } + + input ListFilterInputTypeOfOrderLineFilterInput { + all: OrderLineFilterInput + none: OrderLineFilterInput + some: OrderLineFilterInput + any: Boolean + } + + input LocalTimeOperationFilterInput { + eq: LocalTime + neq: LocalTime + in: [LocalTime] + nin: [LocalTime] + gt: LocalTime + ngt: LocalTime + gte: LocalTime + ngte: LocalTime + lt: LocalTime + nlt: LocalTime + lte: LocalTime + nlte: LocalTime + } + + input StringOperationFilterInput { + and: [StringOperationFilterInput!] + or: [StringOperationFilterInput!] + eq: String + neq: String + contains: String + ncontains: String + in: [String] + nin: [String] + startsWith: String + nstartsWith: String + endsWith: String + nendsWith: String + } + + enum SortEnumType { + ASC + DESC + } + + input CustomerSortInput { + customerId: SortEnumType + customerName: SortEnumType + status: SortEnumType + address: AddressSortInput + created: SortEnumType + } + + input AddressSortInput { + name: SortEnumType + street: SortEnumType + postalCode: SortEnumType + createdDate: SortEnumType + } + + input IntOperationFilterInput { + eq: Int + neq: Int + in: [Int] + nin: [Int] + gt: Int + ngt: Int + gte: Int + ngte: Int + lt: Int + nlt: Int + lte: Int + nlte: Int + } + + "Information about pagination in a connection." + type PageInfo { + "Indicates whether more edges exist following the set defined by the clients arguments." + hasNextPage: Boolean! + "Indicates whether more edges exist prior the set defined by the clients arguments." + hasPreviousPage: Boolean! + "When paginating backwards, the cursor to continue." + startCursor: String + "When paginating forwards, the cursor to continue." + endCursor: String + } + + "An edge in a connection." + type OrdersNoBackwardPaginationEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Order + } + + "An edge in a connection." + type OrdersNoTotalCountEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Order + } + + "An edge in a connection." + type OrdersEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Order + } + + "An edge in a connection." + type AnimalsEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: IAnimal + } + + "An edge in a connection." + type OrdersOffsetPagingEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Order + } + + "The `UUID` scalar type represents a Universally Unique Identifier (UUID) as defined by RFC 9562." + scalar UUID + + input CustomerStatusOperationFilterInput { + eq: CustomerStatus + neq: CustomerStatus + in: [CustomerStatus!] + nin: [CustomerStatus!] + } + + input ListFilterInputTypeOfOrderFilterInput { + all: OrderFilterInput + none: OrderFilterInput + some: OrderFilterInput + any: Boolean + } + + "The `DateTime` scalar type represents a date and time with time zone offset information." + scalar DateTime + + "The `LocalDate` scalar type represents a date without time or time zone information." + scalar LocalDate + + input OrderLineFilterInput { + and: [OrderLineFilterInput!] + or: [OrderLineFilterInput!] + lineNumber: IntOperationFilterInput + order: OrderFilterInput + item: ItemFilterInput + price: DecimalOperationFilterInput + quantity: FloatOperationFilterInput + } + + "The `LocalTime` scalar type represents a time of day without date or time zone information." + scalar LocalTime + + input ItemFilterInput { + and: [ItemFilterInput!] + or: [ItemFilterInput!] + itemId: StringOperationFilterInput + itemName: StringOperationFilterInput + data: ListUnsignedByteOperationFilterInput + } + + input DecimalOperationFilterInput { + eq: Decimal + neq: Decimal + in: [Decimal] + nin: [Decimal] + gt: Decimal + ngt: Decimal + gte: Decimal + ngte: Decimal + lt: Decimal + nlt: Decimal + lte: Decimal + nlte: Decimal + } + + input FloatOperationFilterInput { + eq: Float + neq: Float + in: [Float] + nin: [Float] + gt: Float + ngt: Float + gte: Float + ngte: Float + lt: Float + nlt: Float + lte: Float + nlte: Float + } + + input ListUnsignedByteOperationFilterInput { + all: UnsignedByteOperationFilterInput + none: UnsignedByteOperationFilterInput + some: UnsignedByteOperationFilterInput + any: Boolean + } + + "The `Decimal` scalar type represents a decimal floating-point number with high precision." + scalar Decimal + + input UnsignedByteOperationFilterInput { + eq: UnsignedByte + neq: UnsignedByte + in: [UnsignedByte] + nin: [UnsignedByte] + gt: UnsignedByte + ngt: UnsignedByte + gte: UnsignedByte + ngte: UnsignedByte + lt: UnsignedByte + nlt: UnsignedByte + lte: UnsignedByte + nlte: UnsignedByte + } + + "The `UnsignedByte` scalar type represents an unsigned 8-bit integer." + scalar UnsignedByte + + enum CustomerStatus { + ACTIVE + DISABLED + IN_TRANSIT @deprecated(reason: "No longer in use, please use enum value Active instead") + } + + interface IAnimal { + name: String! + numberOfLegs: Int! + } + + type Order { + myGradeMethod(name: String!): String @deprecated(reason: "This method should not be used anymore") + orderHello(name: String!, first: Int! = 0): String! + orderAddress(addressType: AddressType!): Address! + orderId: UUID! + customer: Customer! + address: Address + orderDate: DateTime! + orderDateTime: DateTime! + orderDay: LocalDate! + lines: [OrderLine!]! + entryTime: LocalTime + grade: String @deprecated(reason: "This propery is obsolete and should not be used!") + } + + input CustomerInput { + customerId: UUID! + customerName: String! + status: CustomerStatus! + orders: [OrderInput!]! + address: AddressInput + created: DateTime + } + + type Customer { + customerId: UUID! + customerName: String! + status: CustomerStatus! + orders: [Order!]! + address: Address + created: DateTime + relatedCustomers(relationType: Int!): [Customer] + relatedCustomer(relationType: Int!): Customer + } + + input OrderInput { + orderId: UUID! + customer: CustomerInput! + address: AddressInput + orderDate: DateTime! + orderDateTime: DateTime! + orderDay: LocalDate! + lines: [OrderLineInput!]! + entryTime: LocalTime + grade: String @deprecated(reason: "This propery is obsolete and should not be used!") + } + + type OrderLine { + lineNumber: Int! + order: Order! + item: Item + price: Decimal! + quantity: Float! + } + + enum AddressType { + DELIVERY + INVOICE + } + + type Address { + name: String! + street: String! + postalCode: String! + createdDate: LocalDate! + } + + input AddressInput { + name: String! + street: String! + postalCode: String! + createdDate: LocalDate! + } + + type Item { + itemId: String! + itemName: String! + data: [UnsignedByte!] + } + + input OrderLineInput { + lineNumber: Int! + order: OrderInput! + item: ItemInput + price: Decimal! + quantity: Float! + } + + input ItemInput { + itemId: String! + itemName: String! + data: [UnsignedByte!] + } + """; +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SubscriptionMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SubscriptionMethods.cs new file mode 100644 index 00000000..7d82b560 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Client/SubscriptionMethods.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Implementation of SubscriptionMethods GraphQL operations +/// +public class SubscriptionMethods : ISubscriptionMethods +{ + private readonly GraphClient client; + + /// + /// Initializes a new instance of the SubscriptionMethods class + /// + /// The GraphQL client instance + public SubscriptionMethods(GraphClient client) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes customerAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription + public GraphSubscription CustomerAdded() + { + var arguments = new List + { + }; + + return new GraphSubscription(client, "customerAdded", OperationType.Subscription, arguments); + } + + /// + /// Executes customerNameAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription + public GraphSubscription CustomerNameAdded(string name = null) + { + var arguments = new List + { + new("name", "String", name), + }; + + return new GraphSubscription(client, "customerNameAdded", OperationType.Subscription, arguments); + } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/AddressType.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/AddressType.cs new file mode 100644 index 00000000..32050e03 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/AddressType.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum AddressType +{ + [EnumMember(Value = "DELIVERY")] + Delivery, + [EnumMember(Value = "INVOICE")] + Invoice, +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/CustomerStatus.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/CustomerStatus.cs new file mode 100644 index 00000000..a4191df8 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/CustomerStatus.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum CustomerStatus +{ + [EnumMember(Value = "ACTIVE")] + Active, + [EnumMember(Value = "DISABLED")] + Disabled, + [Obsolete("No longer in use, please use enum value Active instead")] + [EnumMember(Value = "IN_TRANSIT")] + InTransit, +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/SortEnumType.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/SortEnumType.cs new file mode 100644 index 00000000..14f2ab61 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Enums/SortEnumType.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(JsonStringEnumMemberConverter))] +public enum SortEnumType +{ + [EnumMember(Value = "ASC")] + Asc, + [EnumMember(Value = "DESC")] + Desc, +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressFilterInput.cs new file mode 100644 index 00000000..e3295203 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressFilterInput.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class AddressFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("name")] + [JsonPropertyName("name")] + public StringOperationFilterInput Name + { + get => GetValue("name"); + set => SetValue("name", value); + } + + [GraphQLMember("street")] + [JsonPropertyName("street")] + public StringOperationFilterInput Street + { + get => GetValue("street"); + set => SetValue("street", value); + } + + [GraphQLMember("postalCode")] + [JsonPropertyName("postalCode")] + public StringOperationFilterInput PostalCode + { + get => GetValue("postalCode"); + set => SetValue("postalCode", value); + } + + [GraphQLMember("createdDate")] + [JsonPropertyName("createdDate")] + public LocalDateOperationFilterInput CreatedDate + { + get => GetValue("createdDate"); + set => SetValue("createdDate", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressInput.cs new file mode 100644 index 00000000..80e63cb1 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class AddressInput : GraphInputBase +{ + [GraphQLMember("name")] + [JsonPropertyName("name")] + public string Name + { + get => GetValue("name"); + set => SetValue("name", value); + } + + [GraphQLMember("street")] + [JsonPropertyName("street")] + public string Street + { + get => GetValue("street"); + set => SetValue("street", value); + } + + [GraphQLMember("postalCode")] + [JsonPropertyName("postalCode")] + public string PostalCode + { + get => GetValue("postalCode"); + set => SetValue("postalCode", value); + } + + [GraphQLMember("createdDate")] + [JsonPropertyName("createdDate")] + public DateOnly CreatedDate + { + get => GetValue("createdDate"); + set => SetValue("createdDate", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressSortInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressSortInput.cs new file mode 100644 index 00000000..3fce4b68 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/AddressSortInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class AddressSortInput : GraphInputBase +{ + [GraphQLMember("name")] + [JsonPropertyName("name")] + public SortEnumType? Name + { + get => GetValue("name"); + set => SetValue("name", value); + } + + [GraphQLMember("street")] + [JsonPropertyName("street")] + public SortEnumType? Street + { + get => GetValue("street"); + set => SetValue("street", value); + } + + [GraphQLMember("postalCode")] + [JsonPropertyName("postalCode")] + public SortEnumType? PostalCode + { + get => GetValue("postalCode"); + set => SetValue("postalCode", value); + } + + [GraphQLMember("createdDate")] + [JsonPropertyName("createdDate")] + public SortEnumType? CreatedDate + { + get => GetValue("createdDate"); + set => SetValue("createdDate", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerFilterInput.cs new file mode 100644 index 00000000..d458f3a2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerFilterInput.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class CustomerFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("customerId")] + [JsonPropertyName("customerId")] + public UuidOperationFilterInput CustomerId + { + get => GetValue("customerId"); + set => SetValue("customerId", value); + } + + [GraphQLMember("customerName")] + [JsonPropertyName("customerName")] + public StringOperationFilterInput CustomerName + { + get => GetValue("customerName"); + set => SetValue("customerName", value); + } + + [GraphQLMember("status")] + [JsonPropertyName("status")] + public CustomerStatusOperationFilterInput Status + { + get => GetValue("status"); + set => SetValue("status", value); + } + + [GraphQLMember("orders")] + [JsonPropertyName("orders")] + public ListFilterInputTypeOfOrderFilterInput Orders + { + get => GetValue("orders"); + set => SetValue("orders", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressFilterInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("created")] + [JsonPropertyName("created")] + public DateTimeOperationFilterInput Created + { + get => GetValue("created"); + set => SetValue("created", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerInput.cs new file mode 100644 index 00000000..9944ee4d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerInput.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class CustomerInput : GraphInputBase +{ + [GraphQLMember("customerId")] + [JsonPropertyName("customerId")] + public Guid CustomerId + { + get => GetValue("customerId"); + set => SetValue("customerId", value); + } + + [GraphQLMember("customerName")] + [JsonPropertyName("customerName")] + public string CustomerName + { + get => GetValue("customerName"); + set => SetValue("customerName", value); + } + + [GraphQLMember("status")] + [JsonPropertyName("status")] + public CustomerStatus Status + { + get => GetValue("status"); + set => SetValue("status", value); + } + + [GraphQLMember("orders")] + [JsonPropertyName("orders")] + public List Orders + { + get => GetValue>("orders"); + set => SetValue("orders", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("created")] + [JsonPropertyName("created")] + public DateTimeOffset? Created + { + get => GetValue("created"); + set => SetValue("created", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerSortInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerSortInput.cs new file mode 100644 index 00000000..15b56655 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerSortInput.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class CustomerSortInput : GraphInputBase +{ + [GraphQLMember("customerId")] + [JsonPropertyName("customerId")] + public SortEnumType? CustomerId + { + get => GetValue("customerId"); + set => SetValue("customerId", value); + } + + [GraphQLMember("customerName")] + [JsonPropertyName("customerName")] + public SortEnumType? CustomerName + { + get => GetValue("customerName"); + set => SetValue("customerName", value); + } + + [GraphQLMember("status")] + [JsonPropertyName("status")] + public SortEnumType? Status + { + get => GetValue("status"); + set => SetValue("status", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressSortInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("created")] + [JsonPropertyName("created")] + public SortEnumType? Created + { + get => GetValue("created"); + set => SetValue("created", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerStatusOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerStatusOperationFilterInput.cs new file mode 100644 index 00000000..2026611d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/CustomerStatusOperationFilterInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class CustomerStatusOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public CustomerStatus? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public CustomerStatus? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DateTimeOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DateTimeOperationFilterInput.cs new file mode 100644 index 00000000..08f755d4 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DateTimeOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class DateTimeOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public DateTimeOffset? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public DateTimeOffset? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public DateTimeOffset? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public DateTimeOffset? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public DateTimeOffset? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public DateTimeOffset? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public DateTimeOffset? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public DateTimeOffset? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public DateTimeOffset? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public DateTimeOffset? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DecimalOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DecimalOperationFilterInput.cs new file mode 100644 index 00000000..2fae901e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/DecimalOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class DecimalOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public decimal? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public decimal? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public decimal? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public decimal? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public decimal? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public decimal? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public decimal? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public decimal? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public decimal? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public decimal? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/FloatOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/FloatOperationFilterInput.cs new file mode 100644 index 00000000..bce88ad2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/FloatOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class FloatOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public double? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public double? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public double? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public double? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public double? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public double? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public double? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public double? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public double? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public double? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalFilterInput.cs new file mode 100644 index 00000000..8227219d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalFilterInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class IAnimalFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("name")] + [JsonPropertyName("name")] + public StringOperationFilterInput Name + { + get => GetValue("name"); + set => SetValue("name", value); + } + + [GraphQLMember("numberOfLegs")] + [JsonPropertyName("numberOfLegs")] + public IntOperationFilterInput NumberOfLegs + { + get => GetValue("numberOfLegs"); + set => SetValue("numberOfLegs", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalSortInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalSortInput.cs new file mode 100644 index 00000000..712fbd08 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IAnimalSortInput.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class IAnimalSortInput : GraphInputBase +{ + [GraphQLMember("name")] + [JsonPropertyName("name")] + public SortEnumType? Name + { + get => GetValue("name"); + set => SetValue("name", value); + } + + [GraphQLMember("numberOfLegs")] + [JsonPropertyName("numberOfLegs")] + public SortEnumType? NumberOfLegs + { + get => GetValue("numberOfLegs"); + set => SetValue("numberOfLegs", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/InputFactory.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/InputFactory.cs new file mode 100644 index 00000000..aff3784b --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/InputFactory.cs @@ -0,0 +1,1847 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +public static class IF +{ + public static AddressFilterInput AddressFilter() + { + return new AddressFilterInput(); + } + public static AddressInput Address() + { + return new AddressInput(); + } + public static AddressSortInput AddressSort() + { + return new AddressSortInput(); + } + public static CustomerFilterInput CustomerFilter() + { + return new CustomerFilterInput(); + } + public static CustomerInput Customer() + { + return new CustomerInput(); + } + public static CustomerSortInput CustomerSort() + { + return new CustomerSortInput(); + } + public static CustomerStatusOperationFilterInput CustomerStatusOperationFilter() + { + return new CustomerStatusOperationFilterInput(); + } + public static DateTimeOperationFilterInput DateTimeOperationFilter() + { + return new DateTimeOperationFilterInput(); + } + public static DecimalOperationFilterInput DecimalOperationFilter() + { + return new DecimalOperationFilterInput(); + } + public static FloatOperationFilterInput FloatOperationFilter() + { + return new FloatOperationFilterInput(); + } + public static IAnimalFilterInput IAnimalFilter() + { + return new IAnimalFilterInput(); + } + public static IAnimalSortInput IAnimalSort() + { + return new IAnimalSortInput(); + } + public static IntOperationFilterInput IntOperationFilter() + { + return new IntOperationFilterInput(); + } + public static ItemFilterInput ItemFilter() + { + return new ItemFilterInput(); + } + public static ItemInput Item() + { + return new ItemInput(); + } + public static ListFilterInputTypeOfOrderFilterInput ListFilterInputTypeOfOrderFilter() + { + return new ListFilterInputTypeOfOrderFilterInput(); + } + public static ListFilterInputTypeOfOrderLineFilterInput ListFilterInputTypeOfOrderLineFilter() + { + return new ListFilterInputTypeOfOrderLineFilterInput(); + } + public static ListUnsignedByteOperationFilterInput ListUnsignedByteOperationFilter() + { + return new ListUnsignedByteOperationFilterInput(); + } + public static LocalDateOperationFilterInput LocalDateOperationFilter() + { + return new LocalDateOperationFilterInput(); + } + public static LocalTimeOperationFilterInput LocalTimeOperationFilter() + { + return new LocalTimeOperationFilterInput(); + } + public static OrderFilterInput OrderFilter() + { + return new OrderFilterInput(); + } + public static OrderInput Order() + { + return new OrderInput(); + } + public static OrderLineFilterInput OrderLineFilter() + { + return new OrderLineFilterInput(); + } + public static OrderLineInput OrderLine() + { + return new OrderLineInput(); + } + public static OrderSortInput OrderSort() + { + return new OrderSortInput(); + } + public static StringOperationFilterInput StringOperationFilter() + { + return new StringOperationFilterInput(); + } + public static UnsignedByteOperationFilterInput UnsignedByteOperationFilter() + { + return new UnsignedByteOperationFilterInput(); + } + public static UuidOperationFilterInput UuidOperationFilter() + { + return new UuidOperationFilterInput(); + } +} + + + +public static class AddressFilterInputExtensions +{ + public static AddressFilterInput And(this AddressFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static AddressFilterInput Or(this AddressFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static AddressFilterInput Name(this AddressFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Name = filter; + return input; + } + + public static AddressFilterInput Street(this AddressFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Street = filter; + return input; + } + + public static AddressFilterInput PostalCode(this AddressFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.PostalCode = filter; + return input; + } + + public static AddressFilterInput CreatedDate(this AddressFilterInput input, Action mod) + { + var filter = new LocalDateOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.CreatedDate = filter; + return input; + } + +} + +public static class AddressInputExtensions +{ + + public static AddressInput Name(this AddressInput input, string val) + { + input.Name = val; + return input; + } + + + public static AddressInput Street(this AddressInput input, string val) + { + input.Street = val; + return input; + } + + + public static AddressInput PostalCode(this AddressInput input, string val) + { + input.PostalCode = val; + return input; + } + + + public static AddressInput CreatedDate(this AddressInput input, DateOnly val) + { + input.CreatedDate = val; + return input; + } + +} + +public static class AddressSortInputExtensions +{ + + public static AddressSortInput Name(this AddressSortInput input, SortEnumType? val) + { + input.Name = val; + return input; + } + + + public static AddressSortInput Street(this AddressSortInput input, SortEnumType? val) + { + input.Street = val; + return input; + } + + + public static AddressSortInput PostalCode(this AddressSortInput input, SortEnumType? val) + { + input.PostalCode = val; + return input; + } + + + public static AddressSortInput CreatedDate(this AddressSortInput input, SortEnumType? val) + { + input.CreatedDate = val; + return input; + } + +} + +public static class CustomerFilterInputExtensions +{ + public static CustomerFilterInput And(this CustomerFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static CustomerFilterInput Or(this CustomerFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static CustomerFilterInput CustomerId(this CustomerFilterInput input, Action mod) + { + var filter = new UuidOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.CustomerId = filter; + return input; + } + + public static CustomerFilterInput CustomerName(this CustomerFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.CustomerName = filter; + return input; + } + + public static CustomerFilterInput Status(this CustomerFilterInput input, Action mod) + { + var filter = new CustomerStatusOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Status = filter; + return input; + } + + public static CustomerFilterInput Orders(this CustomerFilterInput input, Action mod) + { + var filter = new ListFilterInputTypeOfOrderFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Orders = filter; + return input; + } + + public static CustomerFilterInput Address(this CustomerFilterInput input, Action mod) + { + var filter = new AddressFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + public static CustomerFilterInput Created(this CustomerFilterInput input, Action mod) + { + var filter = new DateTimeOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Created = filter; + return input; + } + +} + +public static class CustomerInputExtensions +{ + + public static CustomerInput CustomerId(this CustomerInput input, Guid val) + { + input.CustomerId = val; + return input; + } + + + public static CustomerInput CustomerName(this CustomerInput input, string val) + { + input.CustomerName = val; + return input; + } + + + public static CustomerInput Status(this CustomerInput input, CustomerStatus val) + { + input.Status = val; + return input; + } + + public static CustomerInput Orders(this CustomerInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Orders = filter; + return input; + } + + public static CustomerInput Address(this CustomerInput input, Action mod) + { + var filter = new AddressInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + + public static CustomerInput Created(this CustomerInput input, DateTimeOffset? val) + { + input.Created = val; + return input; + } + +} + +public static class CustomerSortInputExtensions +{ + + public static CustomerSortInput CustomerId(this CustomerSortInput input, SortEnumType? val) + { + input.CustomerId = val; + return input; + } + + + public static CustomerSortInput CustomerName(this CustomerSortInput input, SortEnumType? val) + { + input.CustomerName = val; + return input; + } + + + public static CustomerSortInput Status(this CustomerSortInput input, SortEnumType? val) + { + input.Status = val; + return input; + } + + public static CustomerSortInput Address(this CustomerSortInput input, Action mod) + { + var filter = new AddressSortInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + + public static CustomerSortInput Created(this CustomerSortInput input, SortEnumType? val) + { + input.Created = val; + return input; + } + +} + +public static class CustomerStatusOperationFilterInputExtensions +{ + + public static CustomerStatusOperationFilterInput Eq(this CustomerStatusOperationFilterInput input, CustomerStatus? val) + { + input.Eq = val; + return input; + } + + + public static CustomerStatusOperationFilterInput Neq(this CustomerStatusOperationFilterInput input, CustomerStatus? val) + { + input.Neq = val; + return input; + } + + + public static CustomerStatusOperationFilterInput In(this CustomerStatusOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static CustomerStatusOperationFilterInput Nin(this CustomerStatusOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + +} + +public static class DateTimeOperationFilterInputExtensions +{ + + public static DateTimeOperationFilterInput Eq(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Eq = val; + return input; + } + + + public static DateTimeOperationFilterInput Neq(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Neq = val; + return input; + } + + + public static DateTimeOperationFilterInput In(this DateTimeOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static DateTimeOperationFilterInput Nin(this DateTimeOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static DateTimeOperationFilterInput Gt(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Gt = val; + return input; + } + + + public static DateTimeOperationFilterInput Ngt(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Ngt = val; + return input; + } + + + public static DateTimeOperationFilterInput Gte(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Gte = val; + return input; + } + + + public static DateTimeOperationFilterInput Ngte(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Ngte = val; + return input; + } + + + public static DateTimeOperationFilterInput Lt(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Lt = val; + return input; + } + + + public static DateTimeOperationFilterInput Nlt(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Nlt = val; + return input; + } + + + public static DateTimeOperationFilterInput Lte(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Lte = val; + return input; + } + + + public static DateTimeOperationFilterInput Nlte(this DateTimeOperationFilterInput input, DateTimeOffset? val) + { + input.Nlte = val; + return input; + } + +} + +public static class DecimalOperationFilterInputExtensions +{ + + public static DecimalOperationFilterInput Eq(this DecimalOperationFilterInput input, decimal? val) + { + input.Eq = val; + return input; + } + + + public static DecimalOperationFilterInput Neq(this DecimalOperationFilterInput input, decimal? val) + { + input.Neq = val; + return input; + } + + + public static DecimalOperationFilterInput In(this DecimalOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static DecimalOperationFilterInput Nin(this DecimalOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static DecimalOperationFilterInput Gt(this DecimalOperationFilterInput input, decimal? val) + { + input.Gt = val; + return input; + } + + + public static DecimalOperationFilterInput Ngt(this DecimalOperationFilterInput input, decimal? val) + { + input.Ngt = val; + return input; + } + + + public static DecimalOperationFilterInput Gte(this DecimalOperationFilterInput input, decimal? val) + { + input.Gte = val; + return input; + } + + + public static DecimalOperationFilterInput Ngte(this DecimalOperationFilterInput input, decimal? val) + { + input.Ngte = val; + return input; + } + + + public static DecimalOperationFilterInput Lt(this DecimalOperationFilterInput input, decimal? val) + { + input.Lt = val; + return input; + } + + + public static DecimalOperationFilterInput Nlt(this DecimalOperationFilterInput input, decimal? val) + { + input.Nlt = val; + return input; + } + + + public static DecimalOperationFilterInput Lte(this DecimalOperationFilterInput input, decimal? val) + { + input.Lte = val; + return input; + } + + + public static DecimalOperationFilterInput Nlte(this DecimalOperationFilterInput input, decimal? val) + { + input.Nlte = val; + return input; + } + +} + +public static class FloatOperationFilterInputExtensions +{ + + public static FloatOperationFilterInput Eq(this FloatOperationFilterInput input, double? val) + { + input.Eq = val; + return input; + } + + + public static FloatOperationFilterInput Neq(this FloatOperationFilterInput input, double? val) + { + input.Neq = val; + return input; + } + + + public static FloatOperationFilterInput In(this FloatOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static FloatOperationFilterInput Nin(this FloatOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static FloatOperationFilterInput Gt(this FloatOperationFilterInput input, double? val) + { + input.Gt = val; + return input; + } + + + public static FloatOperationFilterInput Ngt(this FloatOperationFilterInput input, double? val) + { + input.Ngt = val; + return input; + } + + + public static FloatOperationFilterInput Gte(this FloatOperationFilterInput input, double? val) + { + input.Gte = val; + return input; + } + + + public static FloatOperationFilterInput Ngte(this FloatOperationFilterInput input, double? val) + { + input.Ngte = val; + return input; + } + + + public static FloatOperationFilterInput Lt(this FloatOperationFilterInput input, double? val) + { + input.Lt = val; + return input; + } + + + public static FloatOperationFilterInput Nlt(this FloatOperationFilterInput input, double? val) + { + input.Nlt = val; + return input; + } + + + public static FloatOperationFilterInput Lte(this FloatOperationFilterInput input, double? val) + { + input.Lte = val; + return input; + } + + + public static FloatOperationFilterInput Nlte(this FloatOperationFilterInput input, double? val) + { + input.Nlte = val; + return input; + } + +} + +public static class IAnimalFilterInputExtensions +{ + public static IAnimalFilterInput And(this IAnimalFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static IAnimalFilterInput Or(this IAnimalFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static IAnimalFilterInput Name(this IAnimalFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Name = filter; + return input; + } + + public static IAnimalFilterInput NumberOfLegs(this IAnimalFilterInput input, Action mod) + { + var filter = new IntOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.NumberOfLegs = filter; + return input; + } + +} + +public static class IAnimalSortInputExtensions +{ + + public static IAnimalSortInput Name(this IAnimalSortInput input, SortEnumType? val) + { + input.Name = val; + return input; + } + + + public static IAnimalSortInput NumberOfLegs(this IAnimalSortInput input, SortEnumType? val) + { + input.NumberOfLegs = val; + return input; + } + +} + +public static class IntOperationFilterInputExtensions +{ + + public static IntOperationFilterInput Eq(this IntOperationFilterInput input, int? val) + { + input.Eq = val; + return input; + } + + + public static IntOperationFilterInput Neq(this IntOperationFilterInput input, int? val) + { + input.Neq = val; + return input; + } + + + public static IntOperationFilterInput In(this IntOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static IntOperationFilterInput Nin(this IntOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static IntOperationFilterInput Gt(this IntOperationFilterInput input, int? val) + { + input.Gt = val; + return input; + } + + + public static IntOperationFilterInput Ngt(this IntOperationFilterInput input, int? val) + { + input.Ngt = val; + return input; + } + + + public static IntOperationFilterInput Gte(this IntOperationFilterInput input, int? val) + { + input.Gte = val; + return input; + } + + + public static IntOperationFilterInput Ngte(this IntOperationFilterInput input, int? val) + { + input.Ngte = val; + return input; + } + + + public static IntOperationFilterInput Lt(this IntOperationFilterInput input, int? val) + { + input.Lt = val; + return input; + } + + + public static IntOperationFilterInput Nlt(this IntOperationFilterInput input, int? val) + { + input.Nlt = val; + return input; + } + + + public static IntOperationFilterInput Lte(this IntOperationFilterInput input, int? val) + { + input.Lte = val; + return input; + } + + + public static IntOperationFilterInput Nlte(this IntOperationFilterInput input, int? val) + { + input.Nlte = val; + return input; + } + +} + +public static class ItemFilterInputExtensions +{ + public static ItemFilterInput And(this ItemFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static ItemFilterInput Or(this ItemFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static ItemFilterInput ItemId(this ItemFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.ItemId = filter; + return input; + } + + public static ItemFilterInput ItemName(this ItemFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.ItemName = filter; + return input; + } + + public static ItemFilterInput Data(this ItemFilterInput input, Action mod) + { + var filter = new ListUnsignedByteOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Data = filter; + return input; + } + +} + +public static class ItemInputExtensions +{ + + public static ItemInput ItemId(this ItemInput input, string val) + { + input.ItemId = val; + return input; + } + + + public static ItemInput ItemName(this ItemInput input, string val) + { + input.ItemName = val; + return input; + } + + + public static ItemInput Data(this ItemInput input, List val) + { + input.Data = val; + return input; + } + +} + +public static class ListFilterInputTypeOfOrderFilterInputExtensions +{ + public static ListFilterInputTypeOfOrderFilterInput All(this ListFilterInputTypeOfOrderFilterInput input, Action mod) + { + var filter = new OrderFilterInput(); + mod ??= _ => { }; + mod(filter); + input.All = filter; + return input; + } + + public static ListFilterInputTypeOfOrderFilterInput None(this ListFilterInputTypeOfOrderFilterInput input, Action mod) + { + var filter = new OrderFilterInput(); + mod ??= _ => { }; + mod(filter); + input.None = filter; + return input; + } + + public static ListFilterInputTypeOfOrderFilterInput Some(this ListFilterInputTypeOfOrderFilterInput input, Action mod) + { + var filter = new OrderFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Some = filter; + return input; + } + + + public static ListFilterInputTypeOfOrderFilterInput Any(this ListFilterInputTypeOfOrderFilterInput input, bool? val) + { + input.Any = val; + return input; + } + +} + +public static class ListFilterInputTypeOfOrderLineFilterInputExtensions +{ + public static ListFilterInputTypeOfOrderLineFilterInput All(this ListFilterInputTypeOfOrderLineFilterInput input, Action mod) + { + var filter = new OrderLineFilterInput(); + mod ??= _ => { }; + mod(filter); + input.All = filter; + return input; + } + + public static ListFilterInputTypeOfOrderLineFilterInput None(this ListFilterInputTypeOfOrderLineFilterInput input, Action mod) + { + var filter = new OrderLineFilterInput(); + mod ??= _ => { }; + mod(filter); + input.None = filter; + return input; + } + + public static ListFilterInputTypeOfOrderLineFilterInput Some(this ListFilterInputTypeOfOrderLineFilterInput input, Action mod) + { + var filter = new OrderLineFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Some = filter; + return input; + } + + + public static ListFilterInputTypeOfOrderLineFilterInput Any(this ListFilterInputTypeOfOrderLineFilterInput input, bool? val) + { + input.Any = val; + return input; + } + +} + +public static class ListUnsignedByteOperationFilterInputExtensions +{ + public static ListUnsignedByteOperationFilterInput All(this ListUnsignedByteOperationFilterInput input, Action mod) + { + var filter = new UnsignedByteOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.All = filter; + return input; + } + + public static ListUnsignedByteOperationFilterInput None(this ListUnsignedByteOperationFilterInput input, Action mod) + { + var filter = new UnsignedByteOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.None = filter; + return input; + } + + public static ListUnsignedByteOperationFilterInput Some(this ListUnsignedByteOperationFilterInput input, Action mod) + { + var filter = new UnsignedByteOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Some = filter; + return input; + } + + + public static ListUnsignedByteOperationFilterInput Any(this ListUnsignedByteOperationFilterInput input, bool? val) + { + input.Any = val; + return input; + } + +} + +public static class LocalDateOperationFilterInputExtensions +{ + + public static LocalDateOperationFilterInput Eq(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Eq = val; + return input; + } + + + public static LocalDateOperationFilterInput Neq(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Neq = val; + return input; + } + + + public static LocalDateOperationFilterInput In(this LocalDateOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static LocalDateOperationFilterInput Nin(this LocalDateOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static LocalDateOperationFilterInput Gt(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Gt = val; + return input; + } + + + public static LocalDateOperationFilterInput Ngt(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Ngt = val; + return input; + } + + + public static LocalDateOperationFilterInput Gte(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Gte = val; + return input; + } + + + public static LocalDateOperationFilterInput Ngte(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Ngte = val; + return input; + } + + + public static LocalDateOperationFilterInput Lt(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Lt = val; + return input; + } + + + public static LocalDateOperationFilterInput Nlt(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Nlt = val; + return input; + } + + + public static LocalDateOperationFilterInput Lte(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Lte = val; + return input; + } + + + public static LocalDateOperationFilterInput Nlte(this LocalDateOperationFilterInput input, DateOnly? val) + { + input.Nlte = val; + return input; + } + +} + +public static class LocalTimeOperationFilterInputExtensions +{ + + public static LocalTimeOperationFilterInput Eq(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Eq = val; + return input; + } + + + public static LocalTimeOperationFilterInput Neq(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Neq = val; + return input; + } + + + public static LocalTimeOperationFilterInput In(this LocalTimeOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static LocalTimeOperationFilterInput Nin(this LocalTimeOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static LocalTimeOperationFilterInput Gt(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Gt = val; + return input; + } + + + public static LocalTimeOperationFilterInput Ngt(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Ngt = val; + return input; + } + + + public static LocalTimeOperationFilterInput Gte(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Gte = val; + return input; + } + + + public static LocalTimeOperationFilterInput Ngte(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Ngte = val; + return input; + } + + + public static LocalTimeOperationFilterInput Lt(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Lt = val; + return input; + } + + + public static LocalTimeOperationFilterInput Nlt(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Nlt = val; + return input; + } + + + public static LocalTimeOperationFilterInput Lte(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Lte = val; + return input; + } + + + public static LocalTimeOperationFilterInput Nlte(this LocalTimeOperationFilterInput input, TimeOnly? val) + { + input.Nlte = val; + return input; + } + +} + +public static class OrderFilterInputExtensions +{ + public static OrderFilterInput And(this OrderFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static OrderFilterInput Or(this OrderFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static OrderFilterInput OrderId(this OrderFilterInput input, Action mod) + { + var filter = new UuidOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.OrderId = filter; + return input; + } + + public static OrderFilterInput Customer(this OrderFilterInput input, Action mod) + { + var filter = new CustomerFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Customer = filter; + return input; + } + + public static OrderFilterInput Address(this OrderFilterInput input, Action mod) + { + var filter = new AddressFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + public static OrderFilterInput OrderDate(this OrderFilterInput input, Action mod) + { + var filter = new DateTimeOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.OrderDate = filter; + return input; + } + + public static OrderFilterInput OrderDateTime(this OrderFilterInput input, Action mod) + { + var filter = new DateTimeOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.OrderDateTime = filter; + return input; + } + + public static OrderFilterInput OrderDay(this OrderFilterInput input, Action mod) + { + var filter = new LocalDateOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.OrderDay = filter; + return input; + } + + public static OrderFilterInput Lines(this OrderFilterInput input, Action mod) + { + var filter = new ListFilterInputTypeOfOrderLineFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Lines = filter; + return input; + } + + public static OrderFilterInput EntryTime(this OrderFilterInput input, Action mod) + { + var filter = new LocalTimeOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.EntryTime = filter; + return input; + } + + [Obsolete("This propery is obsolete and should not be used!")] + public static OrderFilterInput Grade(this OrderFilterInput input, Action mod) + { + var filter = new StringOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Grade = filter; + return input; + } + +} + +public static class OrderInputExtensions +{ + + public static OrderInput OrderId(this OrderInput input, Guid val) + { + input.OrderId = val; + return input; + } + + public static OrderInput Customer(this OrderInput input, Action mod) + { + var filter = new CustomerInput(); + mod ??= _ => { }; + mod(filter); + input.Customer = filter; + return input; + } + + public static OrderInput Address(this OrderInput input, Action mod) + { + var filter = new AddressInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + + public static OrderInput OrderDate(this OrderInput input, DateTimeOffset val) + { + input.OrderDate = val; + return input; + } + + + public static OrderInput OrderDateTime(this OrderInput input, DateTimeOffset val) + { + input.OrderDateTime = val; + return input; + } + + + public static OrderInput OrderDay(this OrderInput input, DateOnly val) + { + input.OrderDay = val; + return input; + } + + public static OrderInput Lines(this OrderInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Lines = filter; + return input; + } + + + public static OrderInput EntryTime(this OrderInput input, TimeOnly? val) + { + input.EntryTime = val; + return input; + } + + + [Obsolete("This propery is obsolete and should not be used!")] + public static OrderInput Grade(this OrderInput input, string val) + { + input.Grade = val; + return input; + } + +} + +public static class OrderLineFilterInputExtensions +{ + public static OrderLineFilterInput And(this OrderLineFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static OrderLineFilterInput Or(this OrderLineFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + public static OrderLineFilterInput LineNumber(this OrderLineFilterInput input, Action mod) + { + var filter = new IntOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.LineNumber = filter; + return input; + } + + public static OrderLineFilterInput Order(this OrderLineFilterInput input, Action mod) + { + var filter = new OrderFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Order = filter; + return input; + } + + public static OrderLineFilterInput Item(this OrderLineFilterInput input, Action mod) + { + var filter = new ItemFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Item = filter; + return input; + } + + public static OrderLineFilterInput Price(this OrderLineFilterInput input, Action mod) + { + var filter = new DecimalOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Price = filter; + return input; + } + + public static OrderLineFilterInput Quantity(this OrderLineFilterInput input, Action mod) + { + var filter = new FloatOperationFilterInput(); + mod ??= _ => { }; + mod(filter); + input.Quantity = filter; + return input; + } + +} + +public static class OrderLineInputExtensions +{ + + public static OrderLineInput LineNumber(this OrderLineInput input, int val) + { + input.LineNumber = val; + return input; + } + + public static OrderLineInput Order(this OrderLineInput input, Action mod) + { + var filter = new OrderInput(); + mod ??= _ => { }; + mod(filter); + input.Order = filter; + return input; + } + + public static OrderLineInput Item(this OrderLineInput input, Action mod) + { + var filter = new ItemInput(); + mod ??= _ => { }; + mod(filter); + input.Item = filter; + return input; + } + + + public static OrderLineInput Price(this OrderLineInput input, decimal val) + { + input.Price = val; + return input; + } + + + public static OrderLineInput Quantity(this OrderLineInput input, double val) + { + input.Quantity = val; + return input; + } + +} + +public static class OrderSortInputExtensions +{ + + public static OrderSortInput OrderId(this OrderSortInput input, SortEnumType? val) + { + input.OrderId = val; + return input; + } + + public static OrderSortInput Customer(this OrderSortInput input, Action mod) + { + var filter = new CustomerSortInput(); + mod ??= _ => { }; + mod(filter); + input.Customer = filter; + return input; + } + + public static OrderSortInput Address(this OrderSortInput input, Action mod) + { + var filter = new AddressSortInput(); + mod ??= _ => { }; + mod(filter); + input.Address = filter; + return input; + } + + + public static OrderSortInput OrderDate(this OrderSortInput input, SortEnumType? val) + { + input.OrderDate = val; + return input; + } + + + public static OrderSortInput OrderDateTime(this OrderSortInput input, SortEnumType? val) + { + input.OrderDateTime = val; + return input; + } + + + public static OrderSortInput OrderDay(this OrderSortInput input, SortEnumType? val) + { + input.OrderDay = val; + return input; + } + + + public static OrderSortInput EntryTime(this OrderSortInput input, SortEnumType? val) + { + input.EntryTime = val; + return input; + } + + + [Obsolete("This propery is obsolete and should not be used!")] + public static OrderSortInput Grade(this OrderSortInput input, SortEnumType? val) + { + input.Grade = val; + return input; + } + +} + +public static class StringOperationFilterInputExtensions +{ + public static StringOperationFilterInput And(this StringOperationFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.And = filter; + return input; + } + + public static StringOperationFilterInput Or(this StringOperationFilterInput input, Action> mod) + { + var filter = new List(); + mod ??= _ => { }; + mod(filter); + input.Or = filter; + return input; + } + + + public static StringOperationFilterInput Eq(this StringOperationFilterInput input, string val) + { + input.Eq = val; + return input; + } + + + public static StringOperationFilterInput Neq(this StringOperationFilterInput input, string val) + { + input.Neq = val; + return input; + } + + + public static StringOperationFilterInput Contains(this StringOperationFilterInput input, string val) + { + input.Contains = val; + return input; + } + + + public static StringOperationFilterInput Ncontains(this StringOperationFilterInput input, string val) + { + input.Ncontains = val; + return input; + } + + + public static StringOperationFilterInput In(this StringOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static StringOperationFilterInput Nin(this StringOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static StringOperationFilterInput StartsWith(this StringOperationFilterInput input, string val) + { + input.StartsWith = val; + return input; + } + + + public static StringOperationFilterInput NstartsWith(this StringOperationFilterInput input, string val) + { + input.NstartsWith = val; + return input; + } + + + public static StringOperationFilterInput EndsWith(this StringOperationFilterInput input, string val) + { + input.EndsWith = val; + return input; + } + + + public static StringOperationFilterInput NendsWith(this StringOperationFilterInput input, string val) + { + input.NendsWith = val; + return input; + } + +} + +public static class UnsignedByteOperationFilterInputExtensions +{ + + public static UnsignedByteOperationFilterInput Eq(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Eq = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Neq(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Neq = val; + return input; + } + + + public static UnsignedByteOperationFilterInput In(this UnsignedByteOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Nin(this UnsignedByteOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Gt(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Gt = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Ngt(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Ngt = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Gte(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Gte = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Ngte(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Ngte = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Lt(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Lt = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Nlt(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Nlt = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Lte(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Lte = val; + return input; + } + + + public static UnsignedByteOperationFilterInput Nlte(this UnsignedByteOperationFilterInput input, byte? val) + { + input.Nlte = val; + return input; + } + +} + +public static class UuidOperationFilterInputExtensions +{ + + public static UuidOperationFilterInput Eq(this UuidOperationFilterInput input, Guid? val) + { + input.Eq = val; + return input; + } + + + public static UuidOperationFilterInput Neq(this UuidOperationFilterInput input, Guid? val) + { + input.Neq = val; + return input; + } + + + public static UuidOperationFilterInput In(this UuidOperationFilterInput input, List val) + { + input.In = val; + return input; + } + + + public static UuidOperationFilterInput Nin(this UuidOperationFilterInput input, List val) + { + input.Nin = val; + return input; + } + + + public static UuidOperationFilterInput Gt(this UuidOperationFilterInput input, Guid? val) + { + input.Gt = val; + return input; + } + + + public static UuidOperationFilterInput Ngt(this UuidOperationFilterInput input, Guid? val) + { + input.Ngt = val; + return input; + } + + + public static UuidOperationFilterInput Gte(this UuidOperationFilterInput input, Guid? val) + { + input.Gte = val; + return input; + } + + + public static UuidOperationFilterInput Ngte(this UuidOperationFilterInput input, Guid? val) + { + input.Ngte = val; + return input; + } + + + public static UuidOperationFilterInput Lt(this UuidOperationFilterInput input, Guid? val) + { + input.Lt = val; + return input; + } + + + public static UuidOperationFilterInput Nlt(this UuidOperationFilterInput input, Guid? val) + { + input.Nlt = val; + return input; + } + + + public static UuidOperationFilterInput Lte(this UuidOperationFilterInput input, Guid? val) + { + input.Lte = val; + return input; + } + + + public static UuidOperationFilterInput Nlte(this UuidOperationFilterInput input, Guid? val) + { + input.Nlte = val; + return input; + } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IntOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IntOperationFilterInput.cs new file mode 100644 index 00000000..55a1fd51 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/IntOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class IntOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public int? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public int? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public int? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public int? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public int? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public int? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public int? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public int? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public int? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public int? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemFilterInput.cs new file mode 100644 index 00000000..68511b1e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemFilterInput.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class ItemFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("itemId")] + [JsonPropertyName("itemId")] + public StringOperationFilterInput ItemId + { + get => GetValue("itemId"); + set => SetValue("itemId", value); + } + + [GraphQLMember("itemName")] + [JsonPropertyName("itemName")] + public StringOperationFilterInput ItemName + { + get => GetValue("itemName"); + set => SetValue("itemName", value); + } + + [GraphQLMember("data")] + [JsonPropertyName("data")] + public ListUnsignedByteOperationFilterInput Data + { + get => GetValue("data"); + set => SetValue("data", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemInput.cs new file mode 100644 index 00000000..b8aa83f8 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ItemInput.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class ItemInput : GraphInputBase +{ + [GraphQLMember("itemId")] + [JsonPropertyName("itemId")] + public string ItemId + { + get => GetValue("itemId"); + set => SetValue("itemId", value); + } + + [GraphQLMember("itemName")] + [JsonPropertyName("itemName")] + public string ItemName + { + get => GetValue("itemName"); + set => SetValue("itemName", value); + } + + [GraphQLMember("data")] + [JsonPropertyName("data")] + public List Data + { + get => GetValue>("data"); + set => SetValue("data", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderFilterInput.cs new file mode 100644 index 00000000..0ef9181c --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderFilterInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class ListFilterInputTypeOfOrderFilterInput : GraphInputBase +{ + [GraphQLMember("all")] + [JsonPropertyName("all")] + public OrderFilterInput All + { + get => GetValue("all"); + set => SetValue("all", value); + } + + [GraphQLMember("none")] + [JsonPropertyName("none")] + public OrderFilterInput None + { + get => GetValue("none"); + set => SetValue("none", value); + } + + [GraphQLMember("some")] + [JsonPropertyName("some")] + public OrderFilterInput Some + { + get => GetValue("some"); + set => SetValue("some", value); + } + + [GraphQLMember("any")] + [JsonPropertyName("any")] + public bool? Any + { + get => GetValue("any"); + set => SetValue("any", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderLineFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderLineFilterInput.cs new file mode 100644 index 00000000..8dd56f0a --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListFilterInputTypeOfOrderLineFilterInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class ListFilterInputTypeOfOrderLineFilterInput : GraphInputBase +{ + [GraphQLMember("all")] + [JsonPropertyName("all")] + public OrderLineFilterInput All + { + get => GetValue("all"); + set => SetValue("all", value); + } + + [GraphQLMember("none")] + [JsonPropertyName("none")] + public OrderLineFilterInput None + { + get => GetValue("none"); + set => SetValue("none", value); + } + + [GraphQLMember("some")] + [JsonPropertyName("some")] + public OrderLineFilterInput Some + { + get => GetValue("some"); + set => SetValue("some", value); + } + + [GraphQLMember("any")] + [JsonPropertyName("any")] + public bool? Any + { + get => GetValue("any"); + set => SetValue("any", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListUnsignedByteOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListUnsignedByteOperationFilterInput.cs new file mode 100644 index 00000000..ecae3b8b --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/ListUnsignedByteOperationFilterInput.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class ListUnsignedByteOperationFilterInput : GraphInputBase +{ + [GraphQLMember("all")] + [JsonPropertyName("all")] + public UnsignedByteOperationFilterInput All + { + get => GetValue("all"); + set => SetValue("all", value); + } + + [GraphQLMember("none")] + [JsonPropertyName("none")] + public UnsignedByteOperationFilterInput None + { + get => GetValue("none"); + set => SetValue("none", value); + } + + [GraphQLMember("some")] + [JsonPropertyName("some")] + public UnsignedByteOperationFilterInput Some + { + get => GetValue("some"); + set => SetValue("some", value); + } + + [GraphQLMember("any")] + [JsonPropertyName("any")] + public bool? Any + { + get => GetValue("any"); + set => SetValue("any", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalDateOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalDateOperationFilterInput.cs new file mode 100644 index 00000000..037699f8 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalDateOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class LocalDateOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public DateOnly? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public DateOnly? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public DateOnly? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public DateOnly? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public DateOnly? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public DateOnly? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public DateOnly? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public DateOnly? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public DateOnly? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public DateOnly? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalTimeOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalTimeOperationFilterInput.cs new file mode 100644 index 00000000..72f47b51 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/LocalTimeOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class LocalTimeOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public TimeOnly? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public TimeOnly? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public TimeOnly? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public TimeOnly? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public TimeOnly? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public TimeOnly? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public TimeOnly? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public TimeOnly? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public TimeOnly? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public TimeOnly? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderFilterInput.cs new file mode 100644 index 00000000..344fd8b3 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderFilterInput.cs @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class OrderFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("orderId")] + [JsonPropertyName("orderId")] + public UuidOperationFilterInput OrderId + { + get => GetValue("orderId"); + set => SetValue("orderId", value); + } + + [GraphQLMember("customer")] + [JsonPropertyName("customer")] + public CustomerFilterInput Customer + { + get => GetValue("customer"); + set => SetValue("customer", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressFilterInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("orderDate")] + [JsonPropertyName("orderDate")] + public DateTimeOperationFilterInput OrderDate + { + get => GetValue("orderDate"); + set => SetValue("orderDate", value); + } + + [GraphQLMember("orderDateTime")] + [JsonPropertyName("orderDateTime")] + public DateTimeOperationFilterInput OrderDateTime + { + get => GetValue("orderDateTime"); + set => SetValue("orderDateTime", value); + } + + [GraphQLMember("orderDay")] + [JsonPropertyName("orderDay")] + public LocalDateOperationFilterInput OrderDay + { + get => GetValue("orderDay"); + set => SetValue("orderDay", value); + } + + [GraphQLMember("lines")] + [JsonPropertyName("lines")] + public ListFilterInputTypeOfOrderLineFilterInput Lines + { + get => GetValue("lines"); + set => SetValue("lines", value); + } + + [GraphQLMember("entryTime")] + [JsonPropertyName("entryTime")] + public LocalTimeOperationFilterInput EntryTime + { + get => GetValue("entryTime"); + set => SetValue("entryTime", value); + } + + [Obsolete("This propery is obsolete and should not be used!")] + [GraphQLMember("grade")] + [JsonPropertyName("grade")] + public StringOperationFilterInput Grade + { + get => GetValue("grade"); + set => SetValue("grade", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderInput.cs new file mode 100644 index 00000000..367ca158 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderInput.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class OrderInput : GraphInputBase +{ + [GraphQLMember("orderId")] + [JsonPropertyName("orderId")] + public Guid OrderId + { + get => GetValue("orderId"); + set => SetValue("orderId", value); + } + + [GraphQLMember("customer")] + [JsonPropertyName("customer")] + public CustomerInput Customer + { + get => GetValue("customer"); + set => SetValue("customer", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("orderDate")] + [JsonPropertyName("orderDate")] + public DateTimeOffset OrderDate + { + get => GetValue("orderDate"); + set => SetValue("orderDate", value); + } + + [GraphQLMember("orderDateTime")] + [JsonPropertyName("orderDateTime")] + public DateTimeOffset OrderDateTime + { + get => GetValue("orderDateTime"); + set => SetValue("orderDateTime", value); + } + + [GraphQLMember("orderDay")] + [JsonPropertyName("orderDay")] + public DateOnly OrderDay + { + get => GetValue("orderDay"); + set => SetValue("orderDay", value); + } + + [GraphQLMember("lines")] + [JsonPropertyName("lines")] + public List Lines + { + get => GetValue>("lines"); + set => SetValue("lines", value); + } + + [GraphQLMember("entryTime")] + [JsonPropertyName("entryTime")] + public TimeOnly? EntryTime + { + get => GetValue("entryTime"); + set => SetValue("entryTime", value); + } + + [Obsolete("This propery is obsolete and should not be used!")] + [GraphQLMember("grade")] + [JsonPropertyName("grade")] + public string Grade + { + get => GetValue("grade"); + set => SetValue("grade", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineFilterInput.cs new file mode 100644 index 00000000..a4682277 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineFilterInput.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class OrderLineFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("lineNumber")] + [JsonPropertyName("lineNumber")] + public IntOperationFilterInput LineNumber + { + get => GetValue("lineNumber"); + set => SetValue("lineNumber", value); + } + + [GraphQLMember("order")] + [JsonPropertyName("order")] + public OrderFilterInput Order + { + get => GetValue("order"); + set => SetValue("order", value); + } + + [GraphQLMember("item")] + [JsonPropertyName("item")] + public ItemFilterInput Item + { + get => GetValue("item"); + set => SetValue("item", value); + } + + [GraphQLMember("price")] + [JsonPropertyName("price")] + public DecimalOperationFilterInput Price + { + get => GetValue("price"); + set => SetValue("price", value); + } + + [GraphQLMember("quantity")] + [JsonPropertyName("quantity")] + public FloatOperationFilterInput Quantity + { + get => GetValue("quantity"); + set => SetValue("quantity", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineInput.cs new file mode 100644 index 00000000..10f60393 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderLineInput.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class OrderLineInput : GraphInputBase +{ + [GraphQLMember("lineNumber")] + [JsonPropertyName("lineNumber")] + public int LineNumber + { + get => GetValue("lineNumber"); + set => SetValue("lineNumber", value); + } + + [GraphQLMember("order")] + [JsonPropertyName("order")] + public OrderInput Order + { + get => GetValue("order"); + set => SetValue("order", value); + } + + [GraphQLMember("item")] + [JsonPropertyName("item")] + public ItemInput Item + { + get => GetValue("item"); + set => SetValue("item", value); + } + + [GraphQLMember("price")] + [JsonPropertyName("price")] + public decimal Price + { + get => GetValue("price"); + set => SetValue("price", value); + } + + [GraphQLMember("quantity")] + [JsonPropertyName("quantity")] + public double Quantity + { + get => GetValue("quantity"); + set => SetValue("quantity", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderSortInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderSortInput.cs new file mode 100644 index 00000000..16a7024c --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/OrderSortInput.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class OrderSortInput : GraphInputBase +{ + [GraphQLMember("orderId")] + [JsonPropertyName("orderId")] + public SortEnumType? OrderId + { + get => GetValue("orderId"); + set => SetValue("orderId", value); + } + + [GraphQLMember("customer")] + [JsonPropertyName("customer")] + public CustomerSortInput Customer + { + get => GetValue("customer"); + set => SetValue("customer", value); + } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public AddressSortInput Address + { + get => GetValue("address"); + set => SetValue("address", value); + } + + [GraphQLMember("orderDate")] + [JsonPropertyName("orderDate")] + public SortEnumType? OrderDate + { + get => GetValue("orderDate"); + set => SetValue("orderDate", value); + } + + [GraphQLMember("orderDateTime")] + [JsonPropertyName("orderDateTime")] + public SortEnumType? OrderDateTime + { + get => GetValue("orderDateTime"); + set => SetValue("orderDateTime", value); + } + + [GraphQLMember("orderDay")] + [JsonPropertyName("orderDay")] + public SortEnumType? OrderDay + { + get => GetValue("orderDay"); + set => SetValue("orderDay", value); + } + + [GraphQLMember("entryTime")] + [JsonPropertyName("entryTime")] + public SortEnumType? EntryTime + { + get => GetValue("entryTime"); + set => SetValue("entryTime", value); + } + + [Obsolete("This propery is obsolete and should not be used!")] + [GraphQLMember("grade")] + [JsonPropertyName("grade")] + public SortEnumType? Grade + { + get => GetValue("grade"); + set => SetValue("grade", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/StringOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/StringOperationFilterInput.cs new file mode 100644 index 00000000..92fc4169 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/StringOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class StringOperationFilterInput : GraphInputBase +{ + [GraphQLMember("and")] + [JsonPropertyName("and")] + public List And + { + get => GetValue>("and"); + set => SetValue("and", value); + } + + [GraphQLMember("or")] + [JsonPropertyName("or")] + public List Or + { + get => GetValue>("or"); + set => SetValue("or", value); + } + + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public string Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public string Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("contains")] + [JsonPropertyName("contains")] + public string Contains + { + get => GetValue("contains"); + set => SetValue("contains", value); + } + + [GraphQLMember("ncontains")] + [JsonPropertyName("ncontains")] + public string Ncontains + { + get => GetValue("ncontains"); + set => SetValue("ncontains", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("startsWith")] + [JsonPropertyName("startsWith")] + public string StartsWith + { + get => GetValue("startsWith"); + set => SetValue("startsWith", value); + } + + [GraphQLMember("nstartsWith")] + [JsonPropertyName("nstartsWith")] + public string NstartsWith + { + get => GetValue("nstartsWith"); + set => SetValue("nstartsWith", value); + } + + [GraphQLMember("endsWith")] + [JsonPropertyName("endsWith")] + public string EndsWith + { + get => GetValue("endsWith"); + set => SetValue("endsWith", value); + } + + [GraphQLMember("nendsWith")] + [JsonPropertyName("nendsWith")] + public string NendsWith + { + get => GetValue("nendsWith"); + set => SetValue("nendsWith", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UnsignedByteOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UnsignedByteOperationFilterInput.cs new file mode 100644 index 00000000..82782521 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UnsignedByteOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class UnsignedByteOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public byte? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public byte? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public byte? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public byte? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public byte? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public byte? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public byte? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public byte? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public byte? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public byte? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UuidOperationFilterInput.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UuidOperationFilterInput.cs new file mode 100644 index 00000000..d3ac4551 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Inputs/UuidOperationFilterInput.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +[JsonConverter(typeof(GraphInputConverter))] +public partial class UuidOperationFilterInput : GraphInputBase +{ + [GraphQLMember("eq")] + [JsonPropertyName("eq")] + public Guid? Eq + { + get => GetValue("eq"); + set => SetValue("eq", value); + } + + [GraphQLMember("neq")] + [JsonPropertyName("neq")] + public Guid? Neq + { + get => GetValue("neq"); + set => SetValue("neq", value); + } + + [GraphQLMember("in")] + [JsonPropertyName("in")] + public List In + { + get => GetValue>("in"); + set => SetValue("in", value); + } + + [GraphQLMember("nin")] + [JsonPropertyName("nin")] + public List Nin + { + get => GetValue>("nin"); + set => SetValue("nin", value); + } + + [GraphQLMember("gt")] + [JsonPropertyName("gt")] + public Guid? Gt + { + get => GetValue("gt"); + set => SetValue("gt", value); + } + + [GraphQLMember("ngt")] + [JsonPropertyName("ngt")] + public Guid? Ngt + { + get => GetValue("ngt"); + set => SetValue("ngt", value); + } + + [GraphQLMember("gte")] + [JsonPropertyName("gte")] + public Guid? Gte + { + get => GetValue("gte"); + set => SetValue("gte", value); + } + + [GraphQLMember("ngte")] + [JsonPropertyName("ngte")] + public Guid? Ngte + { + get => GetValue("ngte"); + set => SetValue("ngte", value); + } + + [GraphQLMember("lt")] + [JsonPropertyName("lt")] + public Guid? Lt + { + get => GetValue("lt"); + set => SetValue("lt", value); + } + + [GraphQLMember("nlt")] + [JsonPropertyName("nlt")] + public Guid? Nlt + { + get => GetValue("nlt"); + set => SetValue("nlt", value); + } + + [GraphQLMember("lte")] + [JsonPropertyName("lte")] + public Guid? Lte + { + get => GetValue("lte"); + set => SetValue("lte", value); + } + + [GraphQLMember("nlte")] + [JsonPropertyName("nlte")] + public Guid? Nlte + { + get => GetValue("nlte"); + set => SetValue("nlte", value); + } + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IAnimal.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IAnimal.cs new file mode 100644 index 00000000..27f7b53b --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IAnimal.cs @@ -0,0 +1,120 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Converters; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Extension methods for IAnimal interface type casting +/// +public static class IAnimalExtensions +{ + /// + /// Casts IAnimal to Pig if the runtime type matches + /// + /// The interface value to cast + /// Pig instance or null if type doesn't match + [GraphQLMember("Pig", true)] + public static Pig Pig(this IAnimal value) + { + if (value.__TypeName == "Pig") + { + return (Pig)value; + } + return null; + } + + /// + /// Casts IAnimal to Spider if the runtime type matches + /// + /// The interface value to cast + /// Spider instance or null if type doesn't match + [GraphQLMember("Spider", true)] + public static Spider Spider(this IAnimal value) + { + if (value.__TypeName == "Spider") + { + return (Spider)value; + } + return null; + } + +} + +/// +/// JSON converter for IAnimal interface deserialization +/// +internal class IAnimalConverter : InterfaceJsonConverter +{ + /// + /// Deserializes JSON to the appropriate concrete type based on __typename + /// + /// The GraphQL type name from __typename field + /// The JSON object to deserialize + /// Deserialized instance of the appropriate concrete type + public override IAnimal Deserialize(string typeName, JsonObject json) => typeName switch + { + "Pig" => json.Deserialize(), + "Spider" => json.Deserialize(), + _ => json.Deserialize() + }; +} + +/// +/// GraphQL interface IAnimal with all common fields +/// +[JsonConverter(typeof(IAnimalConverter))] +public interface IAnimal +{ + /// + /// name field from GraphQL schema + /// + [GraphQLMember("name")] + string Name { get; set; } + + /// + /// numberOfLegs field from GraphQL schema + /// + [GraphQLMember("numberOfLegs")] + int NumberOfLegs { get; set; } + + /// + /// GraphQL __typename field for runtime type resolution + /// + [GraphQLMember("__typename")] + string __TypeName { get; set; } +} + +/// +/// Concrete implementation of IAnimal interface for fallback deserialization +/// +internal class IAnimal__Concrete : IAnimal +{ + /// + /// name field from GraphQL schema + /// + [GraphQLMember("name")] + public string Name { get; set; } + + /// + /// numberOfLegs field from GraphQL schema + /// + [GraphQLMember("numberOfLegs")] + public int NumberOfLegs { get; set; } + + /// + /// GraphQL __typename field for runtime type resolution + /// + [GraphQLMember("__typename")] + public string __TypeName { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IMutationMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IMutationMethods.cs new file mode 100644 index 00000000..c632acad --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IMutationMethods.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Interface for MutationMethods GraphQL operations +/// +public interface IMutationMethods +{ + /// + /// Executes setName GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery SetName(string name = null); + + /// + /// Executes addCustomer GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery AddCustomer(CustomerInput customer = null); + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IQueryMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IQueryMethods.cs new file mode 100644 index 00000000..8a385f14 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/IQueryMethods.cs @@ -0,0 +1,89 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Interface for QueryMethods GraphQL operations +/// +public interface IQueryMethods +{ + /// + /// Executes hello GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery Hello(string name = null); + + /// + /// Executes raiseError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery RaiseError(string message = null); + + /// + /// Executes raiseAuthError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery RaiseAuthError(); + + /// + /// Executes customerReturnNull GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery CustomerReturnNull(); + + /// + /// Executes customers GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery> + GraphQuery> Customers(); + + /// + /// Executes ordersNoBackwardPagination GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery OrdersNoBackwardPagination([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, OrderFilterInput where = null, List order = null); + + /// + /// Executes ordersNoTotalCount GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + GraphCursorQuery OrdersNoTotalCount([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null); + + /// + /// Executes orders GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + GraphCursorQuery Orders([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null); + + /// + /// Executes animals GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + GraphCursorQuery Animals([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, IAnimalFilterInput where = null, List order = null); + + /// + /// Executes ordersOffsetPaging GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery + GraphCursorQuery OrdersOffsetPaging([global::System.ComponentModel.Description("Returns the first _n_ elements from the list.")] int? first = null, [global::System.ComponentModel.Description("Returns the elements in the list that come after the specified cursor.")] string after = null, [global::System.ComponentModel.Description("Returns the last _n_ elements from the list.")] int? last = null, [global::System.ComponentModel.Description("Returns the elements in the list that come before the specified cursor.")] string before = null, OrderFilterInput where = null, List order = null); + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISampleClient.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISampleClient.cs new file mode 100644 index 00000000..9b47543d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISampleClient.cs @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone; + +public interface ISampleClient +{ + IQueryMethods Query { get; } + IMutationMethods Mutation { get; } + ISubscriptionMethods Subscription { get; } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISubscriptionMethods.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISubscriptionMethods.cs new file mode 100644 index 00000000..56f3c878 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Interfaces/ISubscriptionMethods.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +namespace Linq2GraphQL.TestClientStandalone; + +/// +/// Interface for SubscriptionMethods GraphQL operations +/// +public interface ISubscriptionMethods +{ + /// + /// Executes customerAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription + GraphSubscription CustomerAdded(); + + /// + /// Executes customerNameAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription + GraphSubscription CustomerNameAdded(string name = null); + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ArgumentValue.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ArgumentValue.cs new file mode 100644 index 00000000..8f0584a8 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ArgumentValue.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class ArgumentValue +{ + public ArgumentValue(string graphName, string graphType, object value) + { + GraphType = graphType; + Value = value; + GraphName = graphName; + VariableName = graphName; + } + + public string GraphName { get; set; } + public string GraphType { get; set; } + public object Value { get; set; } + public string VariableName { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphArgumentAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphArgumentAttribute.cs new file mode 100644 index 00000000..84e370be --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphArgumentAttribute.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +[AttributeUsage(AttributeTargets.Parameter)] +public class GraphArgumentAttribute : Attribute +{ + public GraphArgumentAttribute(string graphType) + { + GraphType = graphType; + } + + public string GraphType { get; private set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphInterfaceAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphInterfaceAttribute.cs new file mode 100644 index 00000000..00193535 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphInterfaceAttribute.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + + +/// +/// Will be removed in future versions +/// Only here for backward compatibility +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] +public class GraphInterfaceAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphMethodAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphMethodAttribute.cs new file mode 100644 index 00000000..a0d89f83 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphMethodAttribute.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +[AttributeUsage(AttributeTargets.Method)] +public class GraphMethodAttribute : Attribute +{ + public GraphMethodAttribute(string graphName) + { + GraphName = graphName; + } + + public string GraphName { get; private set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLArgumentAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLArgumentAttribute.cs new file mode 100644 index 00000000..e3c4f826 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLArgumentAttribute.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +[AttributeUsage(AttributeTargets.Parameter)] +public class GraphQLArgumentAttribute(string graphQLName, string graphQLType) : Attribute +{ + public string GraphQLType { get; private set; } = graphQLType; + public string GraphQLName { get; private set; } = graphQLName; +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLMemberAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLMemberAttribute.cs new file mode 100644 index 00000000..ad03f223 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphQLMemberAttribute.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] +public class GraphQLMemberAttribute : Attribute +{ + public GraphQLMemberAttribute(string graphQLName) + { + GraphQLName = graphQLName; + } + + public GraphQLMemberAttribute(string graphQLName, bool interfaceProperty) + { + GraphQLName = graphQLName; + InterfaceProperty = interfaceProperty; + } + + public string GraphQLName { get; private set; } + + public bool InterfaceProperty { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphShadowPropertyAttribute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphShadowPropertyAttribute.cs new file mode 100644 index 00000000..474594b9 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Attributes/GraphShadowPropertyAttribute.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +[AttributeUsage(AttributeTargets.Property)] +public class GraphShadowPropertyAttribute : Attribute +{ +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ClientBuilder.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ClientBuilder.cs new file mode 100644 index 00000000..b5dfcb3e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/ClientBuilder.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class ClientBuilder : IGraphClientBuilder where T : class +{ + public ClientBuilder( + string clientName, + IServiceCollection services) + { + ClientName = clientName; + Services = services; + } + + public string ClientName { get; } + public IServiceCollection Services { get; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ClientJson.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ClientJson.cs new file mode 100644 index 00000000..a52aa275 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ClientJson.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common; + +/// +/// Shared, frozen JsonSerializerOptions for all GraphClient instances. +/// System.Text.Json caches type metadata per options instance, so a single shared +/// instance avoids rebuilding converters and property metadata on every request. +/// Values are identical to the historical per-client configuration. +/// +internal static class ClientJson +{ + internal static readonly JsonSerializerOptions Default = CreateDefault(); + + private static JsonSerializerOptions CreateDefault() + { + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { }, + }; + options.MakeReadOnly(populateMissingResolver: true); + return options; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/CustomScalar.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/CustomScalar.cs new file mode 100644 index 00000000..b773d577 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/CustomScalar.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class CustomScalar +{ + internal object InternalValue { get; set; } + + public virtual object Value + { + get => InternalValue; + + set => InternalValue = value; + } + + public override string ToString() + { + return InternalValue?.ToString(); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphEnvelopeReader.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphEnvelopeReader.cs new file mode 100644 index 00000000..b52223f6 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphEnvelopeReader.cs @@ -0,0 +1,154 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Buffers; +using System.Text.Json; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common; + +/// +/// The single parser for the GraphQL response envelope (data / errors / +/// extensions). Reads directly from UTF-8 bytes: only the requested field under +/// "data" is deserialized; sibling root fields are skipped without materializing a typed +/// value. No JsonDocument and no UTF-16 round trip. Every response path (typed queries, +/// raw GraphClient.ExecuteAsync, the public ProcessResponse overloads and subscription +/// payloads) goes through here, so they cannot drift apart. +/// +internal static class GraphEnvelopeReader +{ + /// + /// Reads the envelope and deserializes only data.{name} as . + /// An empty body yields a result with no data and no errors. + /// + internal static GraphResult Read(ReadOnlySpan utf8, string name, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(name); + return ReadCore(utf8, name, options, out _); + } + + /// + /// Reads the envelope and returns the whole data value as a standalone + /// (for queries written as GraphQL text). A JSON null + /// data is returned as a element and a missing one as + /// default. reports whether the body carried a + /// data or errors member, i.e. whether it is a GraphQL response at all. + /// + internal static GraphResult ReadDocument(ReadOnlySpan utf8, JsonSerializerOptions options, + out bool isEnvelope) + { + return ReadCore(utf8, null, options, out isEnvelope); + } + + /// Root field to read, or null to read the whole data value. + private static GraphResult ReadCore(ReadOnlySpan utf8, string name, JsonSerializerOptions options, + out bool isEnvelope) + { + isEnvelope = false; + + if (utf8.Length == 0) + { + return new GraphResult { Data = default }; + } + + var reader = new Utf8JsonReader(utf8, isFinalBlock: true, state: default); + + if (reader.Read() && reader.TokenType == JsonTokenType.StartObject) + { + List errors = null; + Dictionary extensions = null; + T data = default; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + var propertyName = reader.GetString(); + reader.Read(); + + switch (propertyName) + { + case "data": + isEnvelope = true; + data = name == null + ? JsonSerializer.Deserialize(ref reader, options) + : ReadData(ref reader, name, options); + break; + case "errors": + isEnvelope = true; + errors = JsonSerializer.Deserialize>(ref reader, options); + break; + case "extensions": + extensions = + JsonSerializer.Deserialize>(ref reader, options); + break; + default: + reader.Skip(); + break; + } + } + + return new GraphResult { Data = data, Errors = errors, Extensions = extensions }; + } + + throw new JsonException("GraphQL response envelope must be a JSON object."); + } + + private static TData ReadData(ref Utf8JsonReader reader, string name, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("The 'data' property must be a JSON object or null."); + } + + TData result = default; + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + var fieldName = reader.GetString(); + reader.Read(); + + if (fieldName == name) + { + if (reader.TokenType == JsonTokenType.Null) + { + result = default; + } + else + { + result = JsonSerializer.Deserialize(ref reader, options); + } + } + else + { + // Multi-root query: never materialize fields we were not asked for. + reader.Skip(); + } + } + + return result; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphQLTypeBase.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphQLTypeBase.cs new file mode 100644 index 00000000..35b818c7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/GraphQLTypeBase.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System.IO; +using System.Net.Http; +using System.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common +{ + public abstract class GraphQLTypeBase + { + + public T GetMethodValue(string methodName, params object[] arguments) + { + var id = Utilities.GetArgumentsId(arguments); + + if (__AdditionalProperties?.TryGetValue(methodName + id, out var value) != true) + { + return default; + } + + return value.Deserialize(ClientJson.Default); + } + + public T GetFirstMethodValue(string methodName) + { + var keyValue = __AdditionalProperties?.FirstOrDefault(e => e.Key.StartsWith(methodName)); + if (keyValue == null || keyValue.Value.Key == null) + { + return default; + } + + return keyValue.Value.Value.Deserialize(ClientJson.Default); + } + + + + /// + /// Unknown properties captured during deserialization. Null until the serializer + /// actually meets an unknown property — no dictionary is allocated per object. + /// + [JsonExtensionData] + public Dictionary __AdditionalProperties { get; set; } + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ICursorPaging.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ICursorPaging.cs new file mode 100644 index 00000000..d7495469 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/ICursorPaging.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System.IO; +using System.Net.Http; +using System.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common +{ + public interface ICursorPaging + { + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public PageInfo PageInfo { get; set; } + + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/IPageInfo.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/IPageInfo.cs new file mode 100644 index 00000000..21789f4a --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/IPageInfo.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System.IO; +using System.Net.Http; +using System.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common +{ + public class PageInfo + { + [GraphQLMember("hasNextPage")] + [JsonPropertyName("hasNextPage")] + public bool HasNextPage { get; set; } + + [GraphQLMember("hasPreviousPage")] + [JsonPropertyName("hasPreviousPage")] + public bool HasPreviousPage { get; set; } + + [GraphQLMember("startCursor")] + [JsonPropertyName("startCursor")] + public string StartCursor { get; set; } + + [GraphQLMember("endCursor")] + [JsonPropertyName("endCursor")] + public string EndCursor { get; set; } + + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/LazyProperty.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/LazyProperty.cs new file mode 100644 index 00000000..fe1ec1f5 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Common/LazyProperty.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System.IO; +using System.Net.Http; +using System.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Common +{ + public class LazyProperty + { + bool _initialized = false; + T _result; + + public T Value(Func fn) + { + if (!_initialized) + { + _result = fn(); + _initialized = true; + } + return _result; + } + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/CustomScalarConverter.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/CustomScalarConverter.cs new file mode 100644 index 00000000..26460c87 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/CustomScalarConverter.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class CustomScalarConverter : JsonConverter + where TScalar : CustomScalar, new() +{ + public override TScalar Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + object value = null; + switch (reader.TokenType) + { + case JsonTokenType.Null: + case JsonTokenType.None: + return null; + case JsonTokenType.String: + value = reader.GetString(); + break; + case JsonTokenType.Number: + value = reader.GetDecimal(); + break; + case JsonTokenType.True: + value = true; + break; + case JsonTokenType.False: + value = false; + break; + default: + value = JsonDocument.ParseValue(ref reader); + break; + } + + return new() { Value = value }; + } + + public override void Write(Utf8JsonWriter writer, TScalar value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.InternalValue?.ToString()); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/EnumConverter.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/EnumConverter.cs new file mode 100644 index 00000000..f99f6cad --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/EnumConverter.cs @@ -0,0 +1,359 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + + +public class JsonStringEnumMemberConverter : JsonConverterFactory +{ + private readonly bool _allowIntegerValues; + private readonly JsonNamingPolicy _namingPolicy; + + public JsonStringEnumMemberConverter() + : this(null, true) { } + + public JsonStringEnumMemberConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) + { + _namingPolicy = namingPolicy; + _allowIntegerValues = allowIntegerValues; + } + + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsEnum; + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance( + typeof(Converter<>).MakeGenericType(typeToConvert), + BindingFlags.Instance | BindingFlags.Public, + null, + new object[] { _namingPolicy, _allowIntegerValues }, + null); + } + + private class Converter : JsonConverter + where T : struct, Enum + { + private static readonly Type s_enumType = typeof(T); + private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(s_enumType); + + private readonly bool _allowIntegerValues; + private readonly bool _isFlags; + private readonly Dictionary _rawToTransformed; + private readonly Dictionary _transformedToRaw; + + public Converter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) + { + _allowIntegerValues = allowIntegerValues; + + _isFlags = s_enumType.IsDefined(typeof(FlagsAttribute), true); + + var builtInNames = s_enumType.GetEnumNames(); + var builtInValues = s_enumType.GetEnumValues(); + + _rawToTransformed = new Dictionary(); + _transformedToRaw = new Dictionary(); + + for (var i = 0; i < builtInNames.Length; i++) + { + var enumValue = (T)builtInValues.GetValue(i); + var rawValue = GetEnumValue(enumValue); + + var name = builtInNames[i]; + + string transformedName; + if (namingPolicy == null) + { + var field = s_enumType.GetField(name, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)!; + var enumMemberAttribute = field.GetCustomAttribute(true); + transformedName = enumMemberAttribute?.Value ?? name; + } + else + { + transformedName = namingPolicy.ConvertName(name) ?? name; + } + + _rawToTransformed[rawValue] = new EnumInfo + { + Name = transformedName, + EnumValue = enumValue, + RawValue = rawValue + }; + _transformedToRaw[transformedName] = new EnumInfo + { + Name = name, + EnumValue = enumValue, + RawValue = rawValue + }; + } + } + + private static ulong GetEnumValue(object value) + { + switch (s_enumTypeCode) + { + case TypeCode.Int32: + return (ulong)(int)value; + case TypeCode.UInt32: + return (uint)value; + case TypeCode.UInt64: + return (ulong)value; + case TypeCode.Int64: + return (ulong)(long)value; + + case TypeCode.SByte: + return (ulong)(sbyte)value; + case TypeCode.Byte: + return (byte)value; + case TypeCode.Int16: + return (ulong)(short)value; + case TypeCode.UInt16: + return (ushort)value; + } + + throw new NotSupportedException(); + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var token = reader.TokenType; + + if (token == JsonTokenType.String) + { + var enumString = reader.GetString(); + + // Case sensitive search attempted first. + if (_transformedToRaw.TryGetValue(enumString, out var enumInfo)) + { + return (T)Enum.ToObject(s_enumType, enumInfo.RawValue); + } + + if (_isFlags) + { + ulong calculatedValue = 0; + + var flagValues = enumString.Split(", "); + foreach (var flagValue in flagValues) + { + // Case sensitive search attempted first. + if (_transformedToRaw.TryGetValue(flagValue, out enumInfo)) + { + calculatedValue |= enumInfo.RawValue; + } + else + { + // Case insensitive search attempted second. + + var matched = false; + foreach (var enumItem in _transformedToRaw) + { + if (string.Equals(enumItem.Key, flagValue, StringComparison.OrdinalIgnoreCase)) + { + calculatedValue |= enumItem.Value.RawValue; + matched = true; + break; + } + } + + if (!matched) + { + throw new NotSupportedException(); + } + } + } + + return (T)Enum.ToObject(s_enumType, calculatedValue); + } + + // Case insensitive search attempted second. + foreach (var enumItem in _transformedToRaw) + { + if (string.Equals(enumItem.Key, enumString, StringComparison.OrdinalIgnoreCase)) + { + return (T)Enum.ToObject(s_enumType, enumItem.Value.RawValue); + } + } + + if (_transformedToRaw.TryGetValue("", out var value)) + { + return value.EnumValue; + } + + throw new NotSupportedException(); + } + + if (token != JsonTokenType.Number || !_allowIntegerValues) + { + throw new NotSupportedException(); + } + + switch (s_enumTypeCode) + { + // Switch cases ordered by expected frequency + + case TypeCode.Int32: + if (reader.TryGetInt32(out var int32)) + { + return (T)Enum.ToObject(s_enumType, int32); + } + + break; + case TypeCode.UInt32: + if (reader.TryGetUInt32(out var uint32)) + { + return (T)Enum.ToObject(s_enumType, uint32); + } + + break; + case TypeCode.UInt64: + if (reader.TryGetUInt64(out var uint64)) + { + return (T)Enum.ToObject(s_enumType, uint64); + } + + break; + case TypeCode.Int64: + if (reader.TryGetInt64(out var int64)) + { + return (T)Enum.ToObject(s_enumType, int64); + } + + break; + + case TypeCode.SByte: + if (reader.TryGetSByte(out var byte8)) + { + return (T)Enum.ToObject(s_enumType, byte8); + } + + break; + case TypeCode.Byte: + if (reader.TryGetByte(out var ubyte8)) + { + return (T)Enum.ToObject(s_enumType, ubyte8); + } + + break; + case TypeCode.Int16: + if (reader.TryGetInt16(out var int16)) + { + return (T)Enum.ToObject(s_enumType, int16); + } + + break; + case TypeCode.UInt16: + if (reader.TryGetUInt16(out var uint16)) + { + return (T)Enum.ToObject(s_enumType, uint16); + } + + break; + } + + throw new NotSupportedException(); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + var rawValue = GetEnumValue(value); + + if (_rawToTransformed.TryGetValue(rawValue, out var enumInfo)) + { + writer.WriteStringValue(enumInfo.Name); + return; + } + + if (_isFlags) + { + ulong calculatedValue = 0; + + var Builder = new StringBuilder(); + foreach (var enumItem in _rawToTransformed) + { + enumInfo = enumItem.Value; + if (!value.HasFlag(enumInfo.EnumValue) + || enumInfo.RawValue == 0) // Definitions with 'None' should hit the cache case. + { + continue; + } + + // Track the value to make sure all bits are represented. + calculatedValue |= enumInfo.RawValue; + + if (Builder.Length > 0) + Builder.Append(", "); + Builder.Append(enumInfo.Name); + } + + if (calculatedValue == rawValue) + { + writer.WriteStringValue(Builder.ToString()); + return; + } + } + + if (!_allowIntegerValues) + { + throw new NotSupportedException(); + } + + switch (s_enumTypeCode) + { + case TypeCode.Int32: + writer.WriteNumberValue((int)rawValue); + break; + case TypeCode.UInt32: + writer.WriteNumberValue((uint)rawValue); + break; + case TypeCode.UInt64: + writer.WriteNumberValue(rawValue); + break; + case TypeCode.Int64: + writer.WriteNumberValue((long)rawValue); + break; + case TypeCode.Int16: + writer.WriteNumberValue((short)rawValue); + break; + case TypeCode.UInt16: + writer.WriteNumberValue((ushort)rawValue); + break; + case TypeCode.Byte: + writer.WriteNumberValue((byte)rawValue); + break; + case TypeCode.SByte: + writer.WriteNumberValue((sbyte)rawValue); + break; + default: + throw new NotSupportedException(); + } + } + + private class EnumInfo + { + public T EnumValue; + public string Name; + public ulong RawValue; + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/GraphInputConverter.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/GraphInputConverter.cs new file mode 100644 index 00000000..d6facca6 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/GraphInputConverter.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphInputConverter : JsonConverter + where ToGraphInputBase : GraphInputBase +{ + public override ToGraphInputBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, ToGraphInputBase value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.GetValues(), options); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/InterfaceConverter.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/InterfaceConverter.cs new file mode 100644 index 00000000..6a569161 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Converters/InterfaceConverter.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Converters; + +public class InterfaceConverter : JsonConverter + where TImplementation : class, TInterface +{ + public override TInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options) + { + } +} + +public abstract class InterfaceJsonConverter : JsonConverter +{ + public override T Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var json = JsonSerializer.Deserialize(ref reader); + if (json is null) + { + return default; + } + + if (!json.ContainsKey("__typename")) + { + return default; + } + + var type = json["__typename"]!.ToString(); + var value = Deserialize(type, json); + return value; + } + + public abstract T Deserialize(string typeName, JsonObject json); + + public override void Write( + Utf8JsonWriter writer, + T value, + JsonSerializerOptions options) + { + switch (value) + { + case null: + JsonSerializer.Serialize(writer, default!, options); + break; + default: + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + break; + } + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphErrorCode.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphErrorCode.cs new file mode 100644 index 00000000..ad72992a --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphErrorCode.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public enum GraphErrorCode +{ + Unknown, + Authentication, + Authorization, + Forbidden, + Validation, + BadRequest, + NotFound, + RateLimited, + InternalServerError, + Timeout, + Conflict, + PersistedQueryNotFound, + PersistedQueryMismatch +} + +internal static class GraphErrorCodeClassifier +{ + private static readonly Dictionary CodeMap = new(StringComparer.OrdinalIgnoreCase) + { + // Apollo / GraphQL-over-HTTP codes + { "UNAUTHENTICATED", GraphErrorCode.Authentication }, + { "FORBIDDEN", GraphErrorCode.Forbidden }, + { "BAD_USER_INPUT", GraphErrorCode.BadRequest }, + { "VALIDATION_FAILED", GraphErrorCode.Validation }, + { "GRAPHQL_VALIDATION_FAILED", GraphErrorCode.Validation }, + { "INTERNAL_SERVER_ERROR", GraphErrorCode.InternalServerError }, + { "PERSISTED_QUERY_NOT_FOUND", GraphErrorCode.PersistedQueryNotFound }, + { "PERSISTED_QUERY_NOT_SUPPORTED", GraphErrorCode.PersistedQueryMismatch }, + // Hot Chocolate codes + { "AUTH_NOT_AUTHENTICATED", GraphErrorCode.Authentication }, + { "AUTH_NOT_AUTHORIZED", GraphErrorCode.Authorization }, + { "HC0018", GraphErrorCode.Validation }, + { "HC0008", GraphErrorCode.BadRequest }, + { "HC0012", GraphErrorCode.NotFound }, + { "HC0001", GraphErrorCode.InternalServerError }, + // Common patterns + { "RATE_LIMITED", GraphErrorCode.RateLimited }, + { "RATE_LIMIT_EXCEEDED", GraphErrorCode.RateLimited }, + { "THROTTLED", GraphErrorCode.RateLimited }, + { "TIMEOUT", GraphErrorCode.Timeout }, + { "REQUEST_TIMEOUT", GraphErrorCode.Timeout }, + { "CONFLICT", GraphErrorCode.Conflict }, + { "NOT_FOUND", GraphErrorCode.NotFound }, + }; + + internal static GraphErrorCode Classify(string code) + { + if (string.IsNullOrWhiteSpace(code)) return GraphErrorCode.Unknown; + return CodeMap.TryGetValue(code, out var errorCode) ? errorCode : GraphErrorCode.Unknown; + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryExecutionException.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryExecutionException.cs new file mode 100644 index 00000000..b8052a3f --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryExecutionException.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphQueryExecutionException : Exception +{ + public GraphQueryExecutionException(string query, Dictionary variables) : base( + "Unexpected error response received from server.") + { + GraphQLQuery = query; + GraphQLVariables = variables; + } + + public GraphQueryExecutionException(IEnumerable errors, string query, + Dictionary variables) + : base($"{errors.FirstOrDefault()?.Message} - Check {nameof(Errors)} property for details") + { + Errors = errors; + GraphQLQuery = query; + GraphQLVariables = variables; + } + + public string GraphQLQuery { get; private set; } + public Dictionary GraphQLVariables { get; private set; } + + public IEnumerable Errors { get; private set; } +} + +public class GraphQueryError +{ + [JsonPropertyName("message")] public string Message { get; set; } + + [JsonPropertyName("locations")] public ErrorLocation[] Locations { get; set; } + + [JsonPropertyName("path")] public List Path { get; set; } + + [JsonPropertyName("extensions")] public Dictionary Extensions { get; set; } + + public GraphErrorCode ErrorCode => + Extensions?.TryGetValue("code", out var codeObj) == true + ? GraphErrorCodeClassifier.Classify(codeObj?.ToString()) + : GraphErrorCode.Unknown; +} + +public class ErrorLocation +{ + [JsonPropertyName("line")] public int Line { get; set; } + + [JsonPropertyName("column")] public int Column { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryRequestException.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryRequestException.cs new file mode 100644 index 00000000..eb680963 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryRequestException.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphQueryRequestException : Exception +{ + public GraphQueryRequestException(string message, string query, Dictionary variables) : + base(message) + { + GraphQLQuery = query; + GraphQLVariables = variables; + } + + public string GraphQLQuery { get; private set; } + public Dictionary GraphQLVariables { get; private set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryTranslationException.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryTranslationException.cs new file mode 100644 index 00000000..27a1409d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Exceptions/GraphQueryTranslationException.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// Thrown when an expression passed to Include or Select cannot be turned into a GraphQL +/// selection, either because it uses a construct that has no GraphQL equivalent or because it reads +/// something that is not part of the query. +/// +/// +/// The query text does not exist yet when translation fails, so unlike +/// and this +/// exception carries the offending instead. +/// It derives from , which is what the parser threw before this type +/// existed, so callers that catch that keep working. +/// +public class GraphQueryTranslationException : NotSupportedException +{ + public GraphQueryTranslationException(string message, Expression expression, string memberName = null) + : base(message) + { + Expression = expression; + MemberName = memberName; + } + + /// + /// The part of the expression that could not be translated. + /// + public Expression Expression { get; } + + /// + /// The LINQ operator or GraphQL member the failure is about, when it is about a single one. + /// + public string MemberName { get; } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Extensions.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Extensions.cs new file mode 100644 index 00000000..3291cc34 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Extensions.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +internal static class Extensions +{ + internal static bool IsValueTypeOrString(this Type type) + { + if (type == null) + { + return false; + } + + return type.IsValueType || type == typeof(string) || type.IsSubclassOf(typeof(CustomScalar)); + ; + } + + internal static bool IsListOfPrimitiveTypeOrString(this Type type) + { + return GetTypeOrListType(type).IsValueTypeOrString(); + } + + private static bool IsList(this Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>); + } + + internal static Type GetTypeOrListType(this Type type) + { + if (type.IsList()) + { + var genericArguments = type.GetGenericArguments(); + + return genericArguments[0].GetTypeOrListType(); + } + + return type; + } + + internal static string ToCamelCase(this string input) + { + if (char.IsLower(input[0])) + { + return input; + } + + return string.Concat(input[..1].ToLower(), input.AsSpan(1)); + } + + internal static Type GetUnderlyingType(this MemberInfo member) + { + return member.MemberType switch + { + MemberTypes.TypeInfo => (Type)member, + MemberTypes.Event => ((EventInfo)member).EventHandlerType, + MemberTypes.Field => ((FieldInfo)member).FieldType, + MemberTypes.Method => ((MethodInfo)member).ReturnType, + MemberTypes.Property => ((PropertyInfo)member).PropertyType, + _ => throw new ArgumentException + ( + "Input MemberInfo must be if type TypeInfo, EventInfo, FieldInfo, MethodInfo, or PropertyInfo" + ) + }; + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBase.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBase.cs new file mode 100644 index 00000000..4b8b6c77 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBase.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public abstract class GraphBase +{ + protected readonly GraphClient client; + protected readonly OperationType operationType; + + public GraphBase(GraphClient client, string name, OperationType operationType, List arguments) + { + this.client = client; + this.operationType = operationType; + QueryNode = new(typeof(T), name, arguments, null, true); + } + + public QueryNode QueryNode { get; } + + + /// + /// Include top node + /// + /// + public TGraph Include() + { + QueryNode.IncludePrimitive = true; + return (TGraph)(object)this; + } + + public TGraph Include(Expression> path) + { + Utilities.ParseExpression(path, QueryNode); + return (TGraph)(object)this; + } + + public void ParseExpression(Expression body) + { + Utilities.ParseExpression(body, QueryNode); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBaseExecute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBaseExecute.cs new file mode 100644 index 00000000..3ec6e302 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphBaseExecute.cs @@ -0,0 +1,212 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Linq2GraphQL.TestClientStandalone.Runtime.Schema; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Text.Json; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public abstract class GraphBaseExecute +{ + protected readonly GraphClient client; + protected readonly OperationType operationType; + protected readonly QueryExecutor queryExecutor; + public readonly QueryNode QueryNode; + protected readonly Func mapper; + protected readonly Expression> selector; + + private readonly SemaphoreSlim _lock = new(1, 1); + private volatile bool intialized; + private GraphQLSchema schema; + + public GraphBaseExecute(GraphClient client, OperationType operationType, QueryNode queryNode, + Expression> selector) + { + this.client = client; + this.operationType = operationType; + this.QueryNode = queryNode; + // Identity selector (r => r) is the common case — skip the expression compilation; + // ConvertResult already handles a null mapper as identity. + this.mapper = selector == null || selector.Body == selector.Parameters[0] ? null : selector.Compile(); + this.selector = selector; + queryExecutor = new QueryExecutor(client); + } + + private async Task InitQueryAsync(CancellationToken cancellationToken) + { + // Fast path: initialization is one-shot; skip the semaphore once done. + if (intialized) + { + return; + } + + // Outside the try: a cancelled wait never acquired the lock, so it must not release it. + await _lock.WaitAsync(cancellationToken); + try + { + if (!intialized) + { + // A cancelled schema fetch throws before intialized is set, so the next caller retries. + schema = await client.GetSchemaForSafeModeAsync(cancellationToken); + QueryNode.SetAllUniqueVariableNames(); + QueryNode.AddPrimitiveChildren(true, schema); + intialized = true; + } + + } + finally + { + _lock.Release(); + } + } + + public GraphQLSchema Schema => schema; + + private string GetQueryVariablesString(IReadOnlyList args) + { + if (args.Count == 0) + { + return ""; + } + + var text = new StringBuilder(args.Count * 32); + foreach (var argument in args) + { + text.Append('$').Append(argument.VariableName).Append(": ").Append(argument.GraphType).Append(' '); + } + + return $"({text})"; + } + + public Task GetRequestAsJsonAsync() + { + return GetRequestAsJsonAsync(CancellationToken.None); + } + + /// + /// Like , but the one-time safe-mode setup (waiting for + /// initialization and the schema query) stops when is cancelled. + /// + public async Task GetRequestAsJsonAsync(CancellationToken cancellationToken) + { + var request = await GetRequestAsync(cancellationToken); + + + var result = request.Query; + + result += Environment.NewLine; + result += Environment.NewLine; + result += JsonSerializer.Serialize(request.Variables, client.SerializerOptions); + + return result; + } + + + /// + /// The request this query would send, built without contacting the server: safe mode's schema check is + /// skipped, so automatically included fields are not validated against the live schema. Use it to show or + /// log a query; builds the request the query actually sends. + /// + public GraphQLRequest GetRequest() + { + _lock.Wait(); + try + { + if (!intialized) + { + QueryNode.SetAllUniqueVariableNames(); + QueryNode.AddPrimitiveChildren(true, null); + intialized = true; + } + } + finally + { + _lock.Release(); + } + + var args = QueryNode.GetAllActiveArguments(); + + return new GraphQLRequest + { + Query = GetGraphQLQuery(args), + Variables = args.ToDictionary(x => x.VariableName, e => e.Value) + }; + } + + public Task GetRequestAsync() + { + return GetRequestAsync(CancellationToken.None); + } + + /// + /// Builds the request this query sends. The first call does one-time setup: with + /// on, that includes the schema query (unless the schema is + /// already cached). Cancelling stops waiting for that setup and cancels + /// this caller's schema query; nothing is cached, so a later call starts the setup again. + /// + public async Task GetRequestAsync(CancellationToken cancellationToken) + { + await InitQueryAsync(cancellationToken); + + // Compute active arguments once for both the query string and the variables. + var args = QueryNode.GetAllActiveArguments(); + + return new GraphQLRequest + { + Query = GetGraphQLQuery(args), + Variables = args.ToDictionary(x => x.VariableName, e => e.Value) + }; + } + + private string GetGraphQLQuery(IReadOnlyList args) + { + return GetOperationType() + GetQueryVariablesString(args) + "{ " + Environment.NewLine + + QueryNode.GetQueryString() + Environment.NewLine + "}"; + } + + private string GetOperationType() + { + return operationType.ToString().ToLower(); + } + + public TResult ConvertResult(T result) + { + if (Equals(result, default(T))) + { + return default; + } + + if (mapper == null) + { + return (TResult)(object)result; + } + else + { + return mapper.Invoke(result); + } + } + + public GraphResult ConvertResultFull(GraphResult result) + { + return new GraphResult + { + Data = ConvertResult(result.Data), + Errors = result.Errors, + Extensions = result.Extensions + }; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClient.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClient.cs new file mode 100644 index 00000000..7c3c75f2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClient.cs @@ -0,0 +1,226 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; +using Linq2GraphQL.TestClientStandalone.Runtime.Converters; +using Linq2GraphQL.TestClientStandalone.Runtime.Schema; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphClient +{ + private readonly IMemoryCache cache; + private readonly IOptions options; + private readonly bool includeDeprecated; + + /// + /// Constructor for backward compatibility with code compiled against 0.13.83 and earlier. + /// This constructor calls the 4-parameter constructor with includeDeprecated = false. + /// + public GraphClient(HttpClient httpClient, IOptions options, IServiceProvider provider) + : this(httpClient, options, provider, false) + { + } + + /// + /// Constructor with optional includeDeprecated parameter. + /// + public GraphClient(HttpClient httpClient, IOptions options, IServiceProvider provider, + bool includeDeprecated = false) + { + this.options = options; + this.includeDeprecated = includeDeprecated; + if (options.Value.UseSafeMode) + { + cache = provider.GetRequiredService(); + } + + HttpClient = httpClient; + + // Shared frozen options: one metadata cache for all clients instead of one per request. + SerializerOptions = Common.ClientJson.Default; + + SubscriptionUrl = GetSubscriptionUrl(); + } + + private GraphClient(GraphClient source, Func configureRequest) + { + options = source.options; + includeDeprecated = source.includeDeprecated; + cache = source.cache; + HttpClient = source.HttpClient; + SerializerOptions = source.SerializerOptions; + SubscriptionUrl = source.SubscriptionUrl; + requestConfigurations = [..source.requestConfigurations, configureRequest]; + } + + /// + /// Hooks added with , run in order after + /// . + /// + private readonly Func[] requestConfigurations = []; + + /// + /// A client that shares this client's HttpClient, options, schema cache and serializer settings, and also + /// runs on every request it sends, after the hooks already in place. + /// Use it for per-user or per-conversation headers such as an access token. Creating one is cheap, and + /// this client is not changed. + /// + public GraphClient WithRequestConfiguration(Func configureRequest) + { + ArgumentNullException.ThrowIfNull(configureRequest); + return new GraphClient(this, configureRequest); + } + + /// + /// Executes a query written as GraphQL text and returns the whole data object as JSON, together + /// with any errors and extensions. GraphQL errors, including a server rejecting the document, are + /// returned in the result rather than thrown; only transport failures throw + /// . The request goes through the same hooks as typed queries. + /// + public Task> ExecuteAsync(string query, IDictionary variables = null, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(query); + + var request = new GraphQLRequest + { + Query = query, + Variables = variables == null ? null : new Dictionary(variables) + }; + return new QueryExecutor(this).ExecuteDocumentAsync(request, cancellationToken); + } + + public string SubscriptionUrl { get; } + public SubscriptionProtocol SubscriptionProtocol => options.Value.SubscriptionProtocol; + public HttpClient HttpClient { get; } + public JsonSerializerOptions SerializerOptions { get; } + + internal GraphClientOptions ClientOptions => options.Value; + + + /// + /// The connection_init payload delegate from , + /// exposed for callers that build transports by hand. The obsolete + /// is the fallback when this is null. + /// + public Func> ConnectionInitPayload => + options.Value.ConnectionInitPayload; + + [Obsolete("Use ConnectionInitPayload instead: it can return any JSON object (an authorization payload, " + + "for example), not just a GraphQLRequest.")] + public Func> WSConnectionInitPayload => options.Value.WSConnectionInitPayload; + + internal async ValueTask GetConnectionInitPayloadAsync(CancellationToken cancellationToken) + { + if (options.Value.ConnectionInitPayload is { } payload) + { + return await payload(this, cancellationToken).ConfigureAwait(false); + } + +#pragma warning disable CS0618 // WSConnectionInitPayload is the legacy shape, kept for source compatibility. + if (options.Value.WSConnectionInitPayload is { } legacy) + { + return await legacy(this).ConfigureAwait(false); + } +#pragma warning restore CS0618 + + return null; + } + + internal async ValueTask ConfigureRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (options.Value.ConfigureRequest is { } configureRequest) + { + await configureRequest(request, cancellationToken); + } + + foreach (var requestConfiguration in requestConfigurations) + { + await requestConfiguration(request, cancellationToken); + } + } + + private string GetSubscriptionUrl() + { + var baseUrl = HttpClient?.BaseAddress.ToString(); + if (string.IsNullOrWhiteSpace(baseUrl)) + { + throw new ArgumentNullException("HttpClient BaseAddress must be set"); + } + + if (SubscriptionProtocol == SubscriptionProtocol.ServerSentEvents) + { + return baseUrl; + } + + return HttpClient.BaseAddress.Scheme switch + { + "http" => new UriBuilder(HttpClient.BaseAddress) { Scheme = "ws" }.Uri.ToString(), + "https" => new UriBuilder(HttpClient.BaseAddress) { Scheme = "wss" }.Uri.ToString(), + _ => throw new Exception($"BaseAddress Scheme {HttpClient.BaseAddress.Scheme} is unknown") + }; + } + + public Task GetSchemaForSafeModeAsync() + { + return GetSchemaForSafeModeAsync(CancellationToken.None); + } + + /// + /// The schema safe mode validates against, fetched once and cached per base address, deprecated-fields + /// flag and schema cache key. Returns null when is off. + /// + /// + /// cancels only this caller's schema query. That is safe with + /// IMemoryCache.GetOrCreateAsync: it does not share one in-flight fetch between callers (each + /// cache miss runs its own factory), and a factory that throws, including on cancellation, stores + /// nothing, so a cancelled fetch is never cached. If the fetch is ever made shared (single-flight), + /// it must not run on one caller's token; each caller would then stop waiting with + /// Task.WaitAsync(cancellationToken) instead. + /// + public async Task GetSchemaForSafeModeAsync(CancellationToken cancellationToken) + { + if (!options.Value.UseSafeMode) + { + return null; + } + + // The schema type is part of the key: clients that carry their own copy of the runtime + // share the app's IMemoryCache but cannot read each other's GraphQLSchema. + var cacheKey = $"Linq2GraphQL_Schema:{typeof(GraphQLSchema).AssemblyQualifiedName}:{HttpClient.BaseAddress}:{includeDeprecated}:{options.Value.SchemaCacheKey}"; + + return await cache.GetOrCreateAsync(cacheKey, async entry => + { + var executor = new QueryExecutor(this); + + string query; + if (includeDeprecated) + { + query = Helpers.SchemaQueryIncludeDeprecated; + } + else + { + query = Helpers.SchemaQuery; + } + + var graphRequest = new GraphQLRequest { Query = query }; + return await executor.ExecuteRequestAsync("__schema", graphRequest, cancellationToken); + }); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClientOptions.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClientOptions.cs new file mode 100644 index 00000000..41246f1c --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphClientOptions.cs @@ -0,0 +1,115 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// What happens when the subscription event buffer (SubscriptionMaxBufferedEvents) +/// overflows because the observer cannot keep up. +/// +public enum SubscriptionOverflowPolicy +{ + /// Fault the subscription with an InvalidOperationException (default - never drops events silently). + Fault = 0, + + /// Drop the oldest queued events to make room; the newest events are always delivered. + DropOldest, + + /// Keep only the most recent event; observers that only need the latest value. + KeepLatest, +} + +public class GraphClientOptions +{ + public bool UseSafeMode { get; set; } = false; + public SubscriptionProtocol SubscriptionProtocol { get; set; } = default; + + /// + /// Sets the connection_init payload of the WebSocket subscription transports. Return any + /// JSON object the server expects as connection parameters, for example + /// new Dictionary<string, object> { ["Authorization"] = $"Bearer {token}" } (the + /// graphql-ws and Apollo convention) or { { "token", token } }. Called before every + /// (re)connect, before the socket opens, so a refreshed token is picked up and a slow token + /// fetch cannot run into the server's connection_init wait; it counts within the connect + /// timeout (30 s) together with the request hooks. Overrides the obsolete + /// when set. When + /// is enabled the payload must serialize to a + /// JSON object (a dictionary, an anonymous object, a POCO, a JsonObject...) so the + /// Authorization parameter can be merged. + /// + public Func> ConnectionInitPayload { get; set; } + + [Obsolete("Use ConnectionInitPayload instead: it can return any JSON object (an authorization payload, " + + "for example), not just a GraphQLRequest. Superseded when ConnectionInitPayload is set.")] + public Func> WSConnectionInitPayload { get; set; } = + _ => Task.FromResult(null!); + + /// + /// Copies the Authorization header produced by and + /// derived clients (WithAccessToken on a generated client) into the connection_init + /// payload. This is how a per-user token reaches the server on Blazor WebAssembly, where the + /// browser does not allow headers on the WebSocket upgrade request. An Authorization in + /// is used when no hook sets one. The header is + /// merged into the JSON object from and wins on key conflicts. Like + /// any connection_init payload, the token is only as protected as the transport: prefer + /// wss:// so it is not sent in clear text. + /// + public bool SendAuthorizationInConnectionInit { get; set; } + + /// + /// Called for every request the client sends (queries, mutations, the safe-mode schema query, + /// server-sent-event subscriptions and the WebSocket subscription upgrade) just before it is + /// sent. On the WebSocket upgrade the hook's headers, together with the + /// (a hook header wins on a name conflict), are + /// copied to the upgrade request. Use it + /// to set per-request headers, such as an access token, without a + /// . On Blazor WebAssembly the browser ignores headers on + /// WebSocket requests; set to send the token + /// in the connection_init payload instead. + /// + public Func ConfigureRequest { get; set; } + + /// + /// Separates the safe-mode schema cache entry of this client from other clients that use the same base + /// address. Set it when such clients see different schemas, for example because they use different + /// credentials. + /// + public string SchemaCacheKey { get; set; } + + /// + /// Maximum number of subscription events buffered between the transport and the + /// observer. Overflow behavior is controlled by SubscriptionOverflowPolicy. + /// + public int SubscriptionMaxBufferedEvents { get; set; } = 128; + + /// Behavior when the subscription event buffer overflows. + public SubscriptionOverflowPolicy SubscriptionOverflowPolicy { get; set; } = SubscriptionOverflowPolicy.Fault; + + /// + /// Optional idle timeout for SSE subscriptions. If no bytes (including keep-alive + /// comments) arrive within this window, the subscription fails. The window is measured + /// per read while waiting for data, so time spent handling an event never counts as idle. + /// Null disables it. + /// + public TimeSpan? SubscriptionIdleTimeout { get; set; } + + /// + /// Maximum number of WebSocket reconnect attempts after a dropped connection before + /// the subscription fails with an error. Attempts use exponential backoff with jitter + /// (graphql-ws default: 2^n seconds plus 300-3000 ms). A successful connection resets + /// the count. Fatal close codes never retry. Default 5; 0 disables reconnection. + /// + public int SubscriptionRetryAttempts { get; set; } = 5; +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorPager.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorPager.cs new file mode 100644 index 00000000..300848ca --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorPager.cs @@ -0,0 +1,179 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphCursorPager where T : ICursorPaging +{ + private GraphCursorQueryExecute query; + + // The page size the query was built with. Moving forward pages with `first`, moving + // back with `last`; a server only counts back from `before` when `last` is set. + private readonly object pageSize; + + public GraphCursorPager(GraphCursorQueryExecute graphCursorQueryExecute) + { + this.query = graphCursorQueryExecute; + pageSize = GetArgumentValue("first") ?? GetArgumentValue("last"); + IncludeT(e => e.PageInfo); + } + + public T PagerResult => query.BaseResult; + + private GraphCursorPager IncludeT(Expression> path) + { + Utilities.ParseExpression(path, query.QueryNode); + return this; + } + + private async Task ExecutePagerAsync(CancellationToken cancellationToken = default) + { + var baseType = await query.ExecuteBaseAsync(cancellationToken); + return query.ConvertResult(baseType); + } + + private async Task> ExecutePagerWithResultAsync(CancellationToken cancellationToken = default) + { + var result = await query.ExecuteBaseWithResultAsync(cancellationToken); + return query.ConvertResultFull(result); + } + + private object GetArgumentValue(string graphName) + { + return query.QueryNode.Arguments.FirstOrDefault(e => e.GraphName == graphName)?.Value; + } + + private void SetNextPageArguments() + { + query.QueryNode.SetArgumentValue("first", pageSize); + query.QueryNode.SetArgumentValue("after", query.BaseResult?.PageInfo?.EndCursor); + query.QueryNode.SetArgumentValue("last", null); + query.QueryNode.SetArgumentValue("before", null); + } + + private void SetPreviousPageArguments() + { + query.QueryNode.SetArgumentValue("first", null); + query.QueryNode.SetArgumentValue("after", null); + query.QueryNode.SetArgumentValue("last", pageSize); + query.QueryNode.SetArgumentValue("before", query.BaseResult?.PageInfo?.StartCursor); + } + + public async Task NextPageAsync(CancellationToken cancellationToken = default) + { + SetNextPageArguments(); + return await ExecutePagerAsync(cancellationToken); + } + + public async Task> NextPageWithResultAsync(CancellationToken cancellationToken = default) + { + SetNextPageArguments(); + return await ExecutePagerWithResultAsync(cancellationToken); + } + + public async Task PreviousPageAsync(CancellationToken cancellationToken = default) + { + SetPreviousPageArguments(); + return await ExecutePagerAsync(cancellationToken); + } + + public async Task> PreviousPageWithResultAsync(CancellationToken cancellationToken = default) + { + SetPreviousPageArguments(); + return await ExecutePagerWithResultAsync(cancellationToken); + } + + /// + /// Pages forward from the first page until the server reports no next page, yielding one result per page. + /// Each page is requested only when the caller asks for it, so breaking out of the loop stops paging; + /// the pager is then left on the last page that was read. + /// + /// Stop after this many pages even if the server reports more. + /// is less than 1. + public async IAsyncEnumerable GetAllPagesAsync(int? maxPages = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + ValidateMaxPages(maxPages); + SetFirstPageArguments(); + + for (var pageCount = 1; ; pageCount++) + { + var previousEndCursor = query.BaseResult?.PageInfo?.EndCursor; + yield return await ExecutePagerAsync(cancellationToken); + + if (!HasMorePages(previousEndCursor, pageCount, maxPages)) + { + yield break; + } + + SetNextPageArguments(); + } + } + + /// + /// Like , but yields each page's so errors and + /// extensions are kept. Paging stops after the first page that has errors or no data. + /// + /// Stop after this many pages even if the server reports more. + /// is less than 1. + public async IAsyncEnumerable> GetAllPagesWithResultAsync(int? maxPages = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + ValidateMaxPages(maxPages); + SetFirstPageArguments(); + + for (var pageCount = 1; ; pageCount++) + { + var previousEndCursor = query.BaseResult?.PageInfo?.EndCursor; + var result = await ExecutePagerWithResultAsync(cancellationToken); + yield return result; + + if (result.HasErrors || !result.HasData || !HasMorePages(previousEndCursor, pageCount, maxPages)) + { + yield break; + } + + SetNextPageArguments(); + } + } + + private void SetFirstPageArguments() + { + query.BaseResult = default; + SetNextPageArguments(); + } + + // Also stops when the end cursor does not move, so a server that keeps reporting a next page + // without advancing cannot keep the loop going forever. + private bool HasMorePages(string previousEndCursor, int pageCount, int? maxPages) + { + var pageInfo = query.BaseResult?.PageInfo; + return pageInfo is { HasNextPage: true, EndCursor: not null } + && pageInfo.EndCursor != previousEndCursor + && (maxPages == null || pageCount < maxPages); + } + + private static void ValidateMaxPages(int? maxPages) + { + if (maxPages < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxPages), maxPages, "Must be at least 1."); + } + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorQuery.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorQuery.cs new file mode 100644 index 00000000..7a65efff --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphCursorQuery.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphCursorQuery : GraphBase> where T : ICursorPaging +{ + public GraphCursorQuery(GraphClient client, string name, OperationType operationType, List arguments) + : base(client, name, operationType, arguments) + { + } + + public GraphCursorQueryExecute Select() + { + QueryNode.IncludePrimitiveIfNoChildPrimitive(); + return new(client, operationType, QueryNode, null); + } + + public GraphCursorQueryExecute Select(Expression> selector) + { + ParseExpression(selector); + return new(client, operationType, QueryNode, selector); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphInputBase.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphInputBase.cs new file mode 100644 index 00000000..8a4ecc01 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphInputBase.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Concurrent; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public abstract class GraphInputBase +{ + private ConcurrentDictionary Values { get; } = new(); + + internal Dictionary GetValues() + { + return Values.ToDictionary(e => e.Key, e => e.Value); + } + + protected void ClearValues() + { + Values.Clear(); + } + + protected T GetValue(string name) + { + if (Values.TryGetValue(name, out var value)) + { + return (T)value; + } + + return default; + } + + + protected void SetValue(string name, object value) + { + Values.TryRemove(name, out _); + Values.TryAdd(name, value); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQLRequest.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQLRequest.cs new file mode 100644 index 00000000..808d1b03 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQLRequest.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphQLRequest +{ + [JsonPropertyName("query")] public string Query { get; set; } + + [JsonPropertyName("variables")] public Dictionary Variables { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQuery.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQuery.cs new file mode 100644 index 00000000..486c6765 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQuery.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphQuery : GraphBase> +{ + public GraphQuery(GraphClient client, string name, OperationType operationType, List arguments) : base(client, name, operationType, arguments) + { + } + + public GraphQueryExecute Select() + { + QueryNode.IncludePrimitiveIfNoChildPrimitive(); + return new GraphQueryExecute(client, operationType, QueryNode, null); + } + + public GraphQueryExecute Select(Expression> selector) + { + ParseExpression(selector); + return new GraphQueryExecute(client, operationType, QueryNode, selector); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQueryExecute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQueryExecute.cs new file mode 100644 index 00000000..03a58ea8 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphQueryExecute.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; +using System.Linq.Expressions; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphCursorQueryExecute : GraphQueryExecute where T : ICursorPaging +{ + public GraphCursorQueryExecute(GraphClient client, OperationType operationType, QueryNode queryNode, + Expression> selector) : base(client, operationType, queryNode, selector) { } + + public GraphCursorPager AsPager() + { + return new GraphCursorPager(this); + } +} + +public class GraphQueryExecute : GraphBaseExecute +{ + public GraphQueryExecute(GraphClient client, OperationType operationType, QueryNode queryNode, + Expression> selector) : base(client, operationType, queryNode, selector) { } + + public T BaseResult { get; set; } + + public async Task ExecuteBaseAsync(CancellationToken cancellationToken = default) + { + BaseResult = + await queryExecutor.ExecuteRequestAsync(QueryNode.Name, await GetRequestAsync(cancellationToken), + cancellationToken); + return BaseResult; + } + + public async Task> ExecuteBaseWithResultAsync(CancellationToken cancellationToken = default) + { + var result = await queryExecutor.ExecuteRawAsync(QueryNode.Name, await GetRequestAsync(cancellationToken), + cancellationToken); + BaseResult = result.Data; + return result; + } + + public async Task ExecuteAsync(CancellationToken cancellationToken = default) + { + return ConvertResult(await ExecuteBaseAsync(cancellationToken)); + } + + public async Task> ExecuteWithResultAsync(CancellationToken cancellationToken = default) + { + return ConvertResultFull(await ExecuteBaseWithResultAsync(cancellationToken)); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphResult.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphResult.cs new file mode 100644 index 00000000..13e29bdc --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/GraphResult.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class GraphResult +{ + public T Data { get; init; } + public List Errors { get; init; } + public Dictionary Extensions { get; init; } + + public bool HasErrors => Errors is { Count: > 0 }; + public bool HasData => Data is not null; + + public void EnsureNoErrors() + { + if (HasErrors) + { + throw new GraphQueryExecutionException(Errors, string.Empty, null); + } + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/HttpClientBuilderExtensions.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/HttpClientBuilderExtensions.cs new file mode 100644 index 00000000..5bed7b82 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/HttpClientBuilderExtensions.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http.Headers; +using Microsoft.Extensions.DependencyInjection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public static class HttpClientBuilderExtensions +{ + private const string userAgentName = "Linq2GraphQL"; + private static readonly string userAgentVersion = ThisAssembly.AssemblyFileVersion; + + public static IHttpClientBuilder WithHttpClient( + this IGraphClientBuilder clientBuilder, + Action configureClient, + Action configureClientBuilder = null) where T : class + { + var builder = clientBuilder.Services + .AddHttpClient( + clientBuilder.ClientName, + client => + { + client.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue( + new ProductHeaderValue( + userAgentName, + userAgentVersion))); + configureClient(client); + }); + + configureClientBuilder?.Invoke(builder); + + return builder; + } + + public static IHttpClientBuilder WithHttpClient( + this IGraphClientBuilder clientBuilder, + string httpClientName, + Action configureClient, + Action configureClientBuilder = null) where T : class + { + var builder = clientBuilder.Services + .AddHttpClient( + httpClientName, + client => + { + client.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue( + new ProductHeaderValue( + userAgentName, + userAgentVersion))); + configureClient(client); + }); + + configureClientBuilder?.Invoke(builder); + + return builder; + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientBuilder.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientBuilder.cs new file mode 100644 index 00000000..71fcede2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientBuilder.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public interface IGraphClientBuilder where T : class +{ + public string ClientName { get; } + public IServiceCollection Services { get; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientSource.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientSource.cs new file mode 100644 index 00000000..67ee58bd --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphClientSource.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// Implemented by every generated client: access to its , and a way to derive a +/// client of the same type with an extra request hook. Lets other packages work with any generated client +/// generically, for example to execute queries with a per-user token. +/// +public interface IGraphClientSource where TSelf : IGraphClientSource +{ + GraphClient GraphClient { get; } + + TSelf WithRequestConfiguration(Func configureRequest); +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphSchemaSource.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphSchemaSource.cs new file mode 100644 index 00000000..82804c58 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/IGraphSchemaSource.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// Implemented by clients generated with --agent: the schema the client was generated from, as SDL. +/// Lets other packages require such a client at compile time. +/// +public interface IGraphSchemaSource +{ + static abstract string Sdl { get; } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/OperationType.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/OperationType.cs new file mode 100644 index 00000000..79cb1bb2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/OperationType.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public enum OperationType +{ + Query, + Mutation, + Subscription +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryExecutor.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryExecutor.cs new file mode 100644 index 00000000..a36c525a --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryExecutor.cs @@ -0,0 +1,226 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http.Json; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// Every response path parses the GraphQL envelope with ; +/// do not add a second envelope parser here. +/// +public class QueryExecutor +{ + private readonly GraphClient client; + + internal QueryExecutor(GraphClient client) + { + this.client = client; + } + + internal async Task ExecuteRequestAsync(string name, GraphQLRequest graphRequest, + CancellationToken cancellationToken = default) + { + var result = await ExecuteRawAsync(name, graphRequest, cancellationToken); + + if (result.HasErrors) + { + throw new GraphQueryExecutionException(result.Errors, graphRequest.Query, graphRequest.Variables); + } + + return result.Data; + } + + internal async Task> ExecuteRawAsync(string name, GraphQLRequest graphRequest, + CancellationToken cancellationToken = default) + { + using var request = new HttpRequestMessage(HttpMethod.Post, ""); + request.Content = JsonContent.Create(graphRequest, options: client.SerializerOptions); + await client.ConfigureRequestAsync(request, cancellationToken); + + // With ResponseHeadersRead, HttpClient.Timeout only covers the time until the + // headers arrive. Extend it over reading the body with a linked token source. + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + if (client.HttpClient.Timeout != Timeout.InfiniteTimeSpan) + { + timeoutCts.CancelAfter(client.HttpClient.Timeout); + } + + try + { + using var response = await client.HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, + timeoutCts.Token); + + if (!response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(timeoutCts.Token); + content = Truncate(content, MaxErrorMessageLength); + throw new GraphQueryRequestException($"Http error! Status code {response.StatusCode} Error: {content}", + graphRequest.Query, graphRequest.Variables); + } + + await using var stream = await response.Content.ReadAsStreamAsync(timeoutCts.Token); + using var memory = new MemoryStream(); + await stream.CopyToAsync(memory, timeoutCts.Token); + + // Parse in place — GetBuffer avoids copying the body a second time. + return Common.GraphEnvelopeReader.Read( + memory.GetBuffer().AsSpan(0, (int)memory.Length), name, client.SerializerOptions); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + // Only the timeout fired — surface it the way HttpClient does. + throw new TaskCanceledException("The request timed out.", new TimeoutException()); + } + } + + private const int MaxErrorMessageLength = 4096; + + private static string Truncate(string value, int maxLength) + { + if (value == null || value.Length <= maxLength) + { + return value; + } + + return value.Substring(0, maxLength) + "...(truncated)"; + } + + /// + /// Sends a request and returns the whole data object as JSON, for queries written as text rather + /// than built from expressions. + /// + internal async Task> ExecuteDocumentAsync(GraphQLRequest graphRequest, + CancellationToken cancellationToken = default) + { + using var request = new HttpRequestMessage(HttpMethod.Post, "") + { + Content = JsonContent.Create(graphRequest, options: client.SerializerOptions) + }; + await client.ConfigureRequestAsync(request, cancellationToken); + + using var response = await client.HttpClient.SendAsync(request, cancellationToken); + var content = await response.Content.ReadAsByteArrayAsync(cancellationToken); + + if (response.IsSuccessStatusCode) + { + return Common.GraphEnvelopeReader.ReadDocument(content, client.SerializerOptions, out _); + } + + // GraphQL servers answer a request they reject (for example a validation error) with a + // non-success status but a regular GraphQL body; that body is a result, not a transport failure. + GraphResult result = null; + var isEnvelope = false; + try + { + result = Common.GraphEnvelopeReader.ReadDocument(content, client.SerializerOptions, out isEnvelope); + } + catch (JsonException) + { + // Not JSON (HTML error page, proxy text, ...): a transport failure, reported below. + } + + if (!isEnvelope) + { + throw new GraphQueryRequestException( + $"Http error! Status code {response.StatusCode} Error: {Encoding.UTF8.GetString(content)}", + graphRequest.Query, graphRequest.Variables); + } + + return result; + } + + public T ProcessResponse(string con, string name, GraphQLRequest request) + { + var result = ProcessResponseFull(con, name); + + if (result.HasErrors) + { + throw new GraphQueryExecutionException(result.Errors, request.Query, request.Variables); + } + + return result.Data; + } + + /// + /// Parses a response from an already-materialized root + /// (the full envelope) and throws on errors. + /// + public T ProcessResponse(JsonElement root, string name, GraphQLRequest request) + { + var result = ProcessResponseFull(root, name); + + if (result.HasErrors) + { + throw new GraphQueryExecutionException(result.Errors, request.Query, request.Variables); + } + + return result.Data; + } + + /// + /// Parses a response envelope from raw UTF-8 bytes (used by the SSE byte path): + /// only the requested root field is deserialized — no JsonDocument and no UTF-16 + /// round trip, matching the query path. + /// + public T ProcessResponse(byte[] utf8Json, string name, GraphQLRequest request) + { + var result = Common.GraphEnvelopeReader.Read(utf8Json, name, client.SerializerOptions); + + if (result.HasErrors) + { + throw new GraphQueryExecutionException(result.Errors, request.Query, request.Variables); + } + + return result.Data; + } + + /// + /// Full-envelope parse from raw UTF-8 bytes (SSE/WS byte path). Returns data and + /// errors together and never throws on GraphQL errors. + /// + public GraphResult ProcessResponseFull(byte[] utf8Json, string name) + { + return Common.GraphEnvelopeReader.Read(utf8Json, name, client.SerializerOptions); + } + + /// + /// Full-envelope parse from a JSON string. Transcodes to UTF-8 and goes through the same + /// parser as every other response path. An empty string yields a result with no data and + /// no errors. + /// + public GraphResult ProcessResponseFull(string con, string name) + { + ArgumentNullException.ThrowIfNull(con); + return Common.GraphEnvelopeReader.Read(Encoding.UTF8.GetBytes(con), name, client.SerializerOptions); + } + + /// + /// Full-envelope parse from an already-materialized root. The + /// element's raw UTF-8 text is handed to the same parser as every other response path. + /// + public GraphResult ProcessResponseFull(JsonElement root, string name) + { + if (root.ValueKind != JsonValueKind.Object) + { + throw new JsonException("GraphQL response envelope must be a JSON object."); + } + + return Common.GraphEnvelopeReader.Read(JsonMarshal.GetRawUtf8Value(root), name, client.SerializerOptions); + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryNode.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryNode.cs new file mode 100644 index 00000000..2c0193a0 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/QueryNode.cs @@ -0,0 +1,260 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; +using Linq2GraphQL.TestClientStandalone.Runtime.Schema; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public class QueryNode +{ + private readonly bool mustHaveChildren; + private readonly Type underlyingMemberType; + private string argumentHashCodeId; + + public QueryNode(MemberInfo member, string name = null, List arguments = null, + bool? interfaceProperty = null, bool topLevel = false) + { + var memberAttribute = member.GetCustomAttribute(); + + Name = name ?? memberAttribute?.GraphQLName ?? member.Name.ToCamelCase(); + Member = member; + Arguments = arguments ?? new List(); + underlyingMemberType = member.GetUnderlyingType(); + mustHaveChildren = MustHaveChildren(underlyingMemberType); + InterfaceProperty = memberAttribute?.InterfaceProperty == true; + + if (!topLevel) + { + SetArgumentHashCodeId(); + } + } + + public bool InterfaceProperty { get; internal set; } + public string Name { get; internal set; } + public string Alias { get; internal set; } + + public MemberInfo Member { get; internal set; } + public List ChildNodes { get; internal set; } = new(); + public List Arguments { get; internal set; } = new(); + public bool IncludePrimitive { get; internal set; } + + public QueryNode Parent { get; private set; } + + public int Level => Parent?.Level + 1 ?? 1; + public int Leaf { get; internal set; } = 1; + + + internal void IncludePrimitiveIfNoChildPrimitive() + { + if (!ChildNodes.Any(e => e.underlyingMemberType.IsValueTypeOrString())) + { + IncludePrimitive = true; + } + } + + private void SetArgumentHashCodeId() + { + argumentHashCodeId = Utilities.GetArgumentsId(Arguments?.Select(e => e.Value)); + if (argumentHashCodeId != null) + { + Alias = Name + argumentHashCodeId; + } + } + + + private static bool MustHaveChildren(Type type) + { + return !type.IsValueTypeOrString() && + !type.IsListOfPrimitiveTypeOrString(); + } + + + public void AddChildNode(MemberInfo member, string name = null) + { + AddChildNode(new(member, name)); + } + + public QueryNode AddChildNode(QueryNode childNode) + { + var currentNode = ChildNodes.FirstOrDefault(e => + e.Name == childNode.Name && e.argumentHashCodeId == childNode.argumentHashCodeId); + if (currentNode == null) + { + childNode.Parent = this; + childNode.Leaf = ChildNodes.Count + 1; + ChildNodes.Add(childNode); + return childNode; + } + + if (childNode.IncludePrimitive) + { + currentNode.IncludePrimitive = true; + } + + foreach (var child in childNode.ChildNodes) + { + currentNode.AddChildNode(child); + } + + return currentNode; + } + + public void SetArgumentValue(string graphName, object value) + { + var argument = Arguments.FirstOrDefault(e => e.GraphName == graphName); + if (argument != null) + { + argument.Value = value; + } + } + + public void AddPrimitiveChildren(bool recursive, GraphQLSchema schema) + { + if (recursive) + { + foreach (var child in ChildNodes.Where(e => e.mustHaveChildren)) + { + child.AddPrimitiveChildren(recursive, schema); + } + } + + if (mustHaveChildren && (!ChildNodes.Any() || IncludePrimitive)) + { + var typeOrListType = underlyingMemberType.GetTypeOrListType(); + foreach (var propertyInfo in typeOrListType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!propertyInfo.PropertyType.IsValueTypeOrString()) + { + continue; + } + + + var memberAttribute = propertyInfo.GetCustomAttribute(); + if (memberAttribute == null) + { + continue; + } + + + if (schema != null) + { + if (schema.TypePropertyExists(typeOrListType.Name, memberAttribute.GraphQLName)) + { + AddChildNode(propertyInfo, memberAttribute.GraphQLName); + } + else + { + Console.WriteLine( + $"Property: {propertyInfo.Name} Type: {Member.Name} was not present in active schema"); + } + } + else + { + AddChildNode(propertyInfo); + } + } + } + } + + internal void SetAllUniqueVariableNames() + { + foreach (var argument in Arguments) + { + argument.VariableName += $"_{Level}_{Leaf}"; + } + + foreach (var child in ChildNodes) + { + child.SetAllUniqueVariableNames(); + } + } + + public List GetActiveArguments() + { + return Arguments.Where(e => e.Value != null).ToList(); + } + + public List GetAllActiveArguments() + { + var allArguments = GetActiveArguments(); + foreach (var childNode in ChildNodes) + { + allArguments.AddRange(childNode.GetAllActiveArguments()); + } + + return allArguments; + } + + private string GetArgumentString() + { + var args = GetActiveArguments(); + ; + if (!args.Any()) + { + return ""; + } + + var argString = "("; + foreach (var arg in args) + { + argString += arg.GraphName + ":$" + arg.VariableName + " "; + } + + argString.TrimEnd(); + argString += ")"; + + return argString; + } + + + internal string GetQueryString(string indent = "") + { + string query; + var memberType = Member.GetUnderlyingType(); + + if (InterfaceProperty) + { + query = "... on " + Name + GetArgumentString() + Environment.NewLine; + } + else if (!string.IsNullOrWhiteSpace(Alias)) + { + query = Alias + ":" + Name + GetArgumentString() + Environment.NewLine; + } + else + { + query = Name + GetArgumentString() + Environment.NewLine; + } + + if (memberType.IsListOfPrimitiveTypeOrString()) + { + return indent + query; + } + + query += indent + "{" + Environment.NewLine; + + if (ChildNodes.Any()) + { + var newIndent = " " + indent; + foreach (var childNode in ChildNodes) + { + query += childNode.GetQueryString(newIndent); + } + } + + query += indent + "}" + Environment.NewLine; + return query; + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/RequestConfigurations.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/RequestConfigurations.cs new file mode 100644 index 00000000..75d85e83 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/RequestConfigurations.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http.Headers; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// Ready-made request hooks for and +/// . +/// +public static class RequestConfigurations +{ + /// + /// Sets Authorization: Bearer <token> on every request, asking + /// for the token each time. Caching and refreshing the token is the delegate's job, as token acquisition + /// libraries already do. + /// + public static Func BearerToken( + Func> getAccessToken) + { + ArgumentNullException.ThrowIfNull(getAccessToken); + + return async (request, cancellationToken) => + { + var token = await getAccessToken(cancellationToken); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + }; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/GraphQLSchema.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/GraphQLSchema.cs new file mode 100644 index 00000000..cce3f821 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/GraphQLSchema.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Schema; + +public class GraphQLSchema +{ + [JsonPropertyName("types")] public List Types { get; set; } + + + public bool TypePropertyExists(string typeName, string fieldName) + { + if (fieldName.ToLower() == "__typename" ) { return true; } + + var type = Types.FirstOrDefault(x => x.Name == typeName); + if (type?.Fields.FirstOrDefault(x => x.Name == fieldName) == null) + { + return false; + } + + return true; + } +} + +[DebuggerDisplay("{Name}")] +public class GraphQLType +{ + [JsonPropertyName("name")] public string Name { get; set; } + + [JsonPropertyName("fields")] public List Fields { get; set; } +} + +public class GraphQLField +{ + [JsonPropertyName("name")] public string Name { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/Helpers.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/Helpers.cs new file mode 100644 index 00000000..7cbefc62 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Schema/Helpers.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime.Schema; + +internal static class Helpers +{ + internal const string SchemaQuery = @"{ + __schema { + types { + name + fields { + name + } + } + } + }"; + + internal const string SchemaQueryIncludeDeprecated = @"{ + __schema { + types { + name + fields(includeDeprecated: true) { + name + } + } + } + }"; +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/SubscriptionSubprotocol.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/SubscriptionSubprotocol.cs new file mode 100644 index 00000000..4c725261 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/SubscriptionSubprotocol.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public enum SubscriptionProtocol +{ + /// + /// Default protocol + /// actually named graphql-ws just to make things even more confusing + /// sends: graphql-transport-ws as subprotocol + /// + GraphQLWebSocket, + + /// + /// Old apollo protocol + /// sends: graphql-ws as subprotocol + /// + ApolloWebSocket, + + /// + /// SSE + /// + ServerSentEvents +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Utilities.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Utilities.cs new file mode 100644 index 00000000..6db5848e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Utilities.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Collections; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Linq2GraphQL.TestClientStandalone.Runtime.Visitors; + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +public static class Utilities +{ + private const int MaxArgumentDepth = 8; + + /// Separates one argument from the next so that adjacent values cannot run together. + private const char Separator = (char)31; + + /// + /// Identifies a set of field arguments so that the same field requested with different arguments becomes + /// several aliased selections. The result is derived from the argument values only, because the alias has + /// to be recomputed from the same values when the response is read back. + /// + /// An alias suffix, or null when there are no arguments to distinguish. + public static string GetArgumentsId(IEnumerable objects) + { + if (objects == null) + { + return null; + } + + var values = objects.Where(o => o != null).ToList(); + if (values.Count == 0) + { + return null; + } + + var builder = new StringBuilder(); + foreach (var value in values) + { + AppendValue(builder, value, 0); + builder.Append(Separator); + } + + return "_" + Hash(builder).ToString("x16"); + } + + public static void ParseExpression(Expression body, QueryNode parent) + { + QueryExpressionVisitor.Parse(body, parent); + } + + /// + /// Writes a value in a form that is identical for equal values and stable between processes, which + /// is not for reference types. + /// + private static void AppendValue(StringBuilder builder, object value, int depth) + { + switch (value) + { + case null: + builder.Append("null"); + return; + + case string text: + builder.Append('"').Append(text).Append('"'); + return; + + case bool flag: + builder.Append(flag ? "true" : "false"); + return; + + case Enum enumValue: + builder.Append(enumValue.GetType().FullName).Append('.').Append(enumValue.ToString("D")); + return; + + case DateTime dateTime: + builder.Append(dateTime.ToString("O", CultureInfo.InvariantCulture)); + return; + + case DateTimeOffset dateTimeOffset: + builder.Append(dateTimeOffset.ToString("O", CultureInfo.InvariantCulture)); + return; + + case TimeSpan timeSpan: + builder.Append(timeSpan.ToString("c", CultureInfo.InvariantCulture)); + return; + + case CustomScalar scalar: + AppendValue(builder, scalar.Value, depth + 1); + return; + + case IFormattable formattable: + // Numbers, Guid, DateOnly, TimeOnly. + builder.Append(formattable.ToString(null, CultureInfo.InvariantCulture)); + return; + + case IEnumerable items: + AppendItems(builder, items, depth); + return; + + default: + AppendProperties(builder, value, depth); + return; + } + } + + private static void AppendItems(StringBuilder builder, IEnumerable items, int depth) + { + if (depth >= MaxArgumentDepth) + { + builder.Append("[...]"); + return; + } + + builder.Append('['); + foreach (var item in items) + { + AppendValue(builder, item, depth + 1); + builder.Append(','); + } + + builder.Append(']'); + } + + /// Input objects are compared by their contents, in a fixed property order. + private static void AppendProperties(StringBuilder builder, object value, int depth) + { + var type = value.GetType(); + builder.Append(type.FullName); + + if (depth >= MaxArgumentDepth) + { + builder.Append("{...}"); + return; + } + + builder.Append('{'); + + var properties = type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(e => e.CanRead && e.GetIndexParameters().Length == 0) + .OrderBy(e => e.Name, StringComparer.Ordinal); + + foreach (var property in properties) + { + builder.Append(property.Name).Append('='); + + object propertyValue; + try + { + propertyValue = property.GetValue(value); + } + catch (Exception) + { + // A property that throws cannot contribute to the identity of the arguments. + builder.Append(";"); + continue; + } + + AppendValue(builder, propertyValue, depth + 1); + builder.Append(';'); + } + + builder.Append('}'); + } + + /// FNV-1a, for a 64 bit value that is the same in every process. + private static ulong Hash(StringBuilder builder) + { + const ulong offsetBasis = 14695981039346656037; + const ulong prime = 1099511628211; + + var hash = offsetBasis; + + foreach (var chunk in builder.GetChunks()) + { + foreach (var character in chunk.Span) + { + hash = (hash ^ (byte)character) * prime; + hash = (hash ^ (byte)(character >> 8)) * prime; + } + } + + return hash; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/ExpressionEvaluator.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/ExpressionEvaluator.cs new file mode 100644 index 00000000..de4214bb --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/ExpressionEvaluator.cs @@ -0,0 +1,117 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; +using System.Reflection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Visitors; + +/// +/// Turns the argument expressions of a GraphQL field into the values that are sent as query variables. +/// Constants and captured locals are read directly; everything else falls back to compiling the +/// expression, which is comparatively expensive. +/// +internal static class ExpressionEvaluator +{ + internal static object Evaluate(Expression expression) + { + return TryEvaluateFast(expression, out var value) ? value : Compile(expression); + } + + private static bool TryEvaluateFast(Expression expression, out object value) + { + switch (expression) + { + case null: + value = null; + return true; + + case ConstantExpression constant: + value = constant.Value; + return true; + + case MemberExpression member: + return TryEvaluateMember(member, out value); + + case UnaryExpression unary when IsTransparentConversion(unary): + return TryEvaluateFast(unary.Operand, out value); + + default: + value = null; + return false; + } + } + + private static bool TryEvaluateMember(MemberExpression member, out object value) + { + // A static member, or a captured local, which the compiler turns into a field on a closure object + // that is held by a ConstantExpression. + object target = null; + + if (member.Expression != null && !TryEvaluateFast(member.Expression, out target)) + { + value = null; + return false; + } + + switch (member.Member) + { + case FieldInfo field: + value = field.GetValue(target); + return true; + + case PropertyInfo property when property.CanRead && property.GetIndexParameters().Length == 0: + value = property.GetValue(target); + return true; + + default: + value = null; + return false; + } + } + + /// + /// True for a conversion that cannot change the value, so the operand can be read instead: boxing, + /// an upcast, or a lift into . + /// + private static bool IsTransparentConversion(UnaryExpression unary) + { + if (unary.NodeType != ExpressionType.Convert && + unary.NodeType != ExpressionType.ConvertChecked && + unary.NodeType != ExpressionType.TypeAs) + { + return false; + } + + if (unary.Method != null) + { + return false; + } + + var from = unary.Operand.Type; + var to = Nullable.GetUnderlyingType(unary.Type) ?? unary.Type; + + return to.IsAssignableFrom(from); + } + + private static object Compile(Expression expression) + { + var body = expression.Type == typeof(object) + ? expression + : Expression.Convert(expression, typeof(object)); + + return Expression.Lambda>(body).Compile()(); + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/LinqOperator.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/LinqOperator.cs new file mode 100644 index 00000000..bd24b87e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/LinqOperator.cs @@ -0,0 +1,140 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Visitors; + +internal enum LinqOperatorKind +{ + /// Not a LINQ operator at all. + None, + + /// + /// The operator replaces the element with something else (Select, SelectMany). + /// The selection continues at whatever the projection lambda returns. + /// + Projection, + + /// + /// The operator keeps the source element type, or reduces the sequence to one of its elements, to a + /// scalar computed from them, or to another container holding them (Where, OrderBy, + /// First, Count, ToDictionary, GroupBy, ...). + /// The selection stays on the source node; any lambda is a client side predicate or key selector whose + /// members still have to be fetched for it to work. + /// + PassThrough, + + /// + /// A known LINQ operator that cannot be translated, because it combines several sequences + /// (Concat, Join, Zip, ...) or folds the elements into an accumulator that the + /// selection cannot follow (Aggregate). + /// + Unsupported +} + +internal static class LinqOperator +{ + private static readonly HashSet ProjectionOperators = + [ + "Select", + "SelectMany" + ]; + + private static readonly HashSet PassThroughOperators = + [ + "All", + "Any", + "Append", + "AsEnumerable", + "AsQueryable", + "Average", + "Cast", + "Chunk", + "Contains", + "Count", + "CountBy", + "DefaultIfEmpty", + "Distinct", + "DistinctBy", + "ElementAt", + "ElementAtOrDefault", + "First", + "FirstOrDefault", + "GroupBy", + "Index", + "Last", + "LastOrDefault", + "LongCount", + "Max", + "MaxBy", + "Min", + "MinBy", + "OfType", + "Order", + "OrderBy", + "OrderByDescending", + "OrderDescending", + "Prepend", + "Reverse", + "Shuffle", + "Single", + "SingleOrDefault", + "Skip", + "SkipLast", + "SkipWhile", + "Sum", + "Take", + "TakeLast", + "TakeWhile", + "ThenBy", + "ThenByDescending", + "ToArray", + "ToDictionary", + "ToHashSet", + "ToList", + "ToLookup", + "TryGetNonEnumeratedCount", + "Where" + ]; + + internal static LinqOperatorKind Classify(MethodInfo method) + { + if (method.DeclaringType != typeof(Queryable) && method.DeclaringType != typeof(Enumerable)) + { + return LinqOperatorKind.None; + } + + if (Attribute.GetCustomAttribute(method, typeof(ExtensionAttribute)) == null) + { + return LinqOperatorKind.None; + } + + if (ProjectionOperators.Contains(method.Name)) + { + return LinqOperatorKind.Projection; + } + + return PassThroughOperators.Contains(method.Name) + ? LinqOperatorKind.PassThrough + : LinqOperatorKind.Unsupported; + } + + internal static bool IsOperator(MethodInfo method) + { + return Classify(method) != LinqOperatorKind.None; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/QueryExpressionVisitor.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/QueryExpressionVisitor.cs new file mode 100644 index 00000000..977f1b9e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Client/Visitors/QueryExpressionVisitor.cs @@ -0,0 +1,384 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; +using System.Reflection; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Visitors; + +/// +/// Translates the LINQ expressions passed to Include and Select into the +/// tree that the GraphQL query text is generated from. +/// +/// +/// +/// The visitor works on two levels. handles expressions that name a +/// field — a chain of members, a GraphQL method call, or a LINQ operator over one of those — and returns +/// the node that expression selects, creating it if needed. Everything else (object initialisers, +/// comparisons, string calls, ...) is walked by the inherited so that +/// every field mentioned anywhere inside it still ends up in the query. +/// +/// +/// Lambda parameters are bound to the node they iterate in , keyed by reference, so +/// nested lambdas that reuse a parameter name stay distinct and a parameter that was never bound is +/// reported instead of silently attaching its fields to the root. +/// +/// +internal class QueryExpressionVisitor : ExpressionVisitor +{ + private readonly QueryNode root; + private readonly Dictionary scopes = new(); + + private QueryExpressionVisitor(QueryNode root) + { + this.root = root; + } + + internal static void Parse(Expression expression, QueryNode root) + { + ArgumentNullException.ThrowIfNull(expression); + ArgumentNullException.ThrowIfNull(root); + + new QueryExpressionVisitor(root).ParseRoot(expression); + } + + private void ParseRoot(Expression expression) + { + var body = Unwrap(expression); + + if (body is LambdaExpression lambda) + { + if (lambda.Parameters.Count > 0) + { + Bind(lambda.Parameters[0], root); + } + + body = Unwrap(lambda.Body); + } + + Select(body); + } + + /// + /// Adds everything selects to the query. When the expression names a + /// single field, that field is the end of the path the caller wrote and therefore also gets its + /// primitive properties. + /// + private void Select(Expression expression) + { + var node = ResolvePath(expression); + if (node != null) + { + node.IncludePrimitive = true; + } + } + + /// + /// Resolves an expression that names a field to its node, or returns null when the expression is not a + /// single path — in which case any fields inside it have been added as a side effect. + /// + private QueryNode ResolvePath(Expression expression) + { + switch (Unwrap(expression)) + { + case null: + return null; + + case ParameterExpression parameter: + return Scope(parameter); + + case MemberExpression member: + return ResolveMember(member); + + case MethodCallExpression call: + return ResolveCall(call); + + case var other: + // Not a path: an initialiser, a comparison, a literal. Walk it so the fields it mentions are + // still fetched, but there is no single node to hand back. + Visit(other); + return null; + } + } + + private QueryNode ResolveMember(MemberExpression member) + { + if (member.Member.GetCustomAttribute() == null) + { + // Not a GraphQL field: Nullable.Value, a plain CLR property, a captured local. The selection is + // whatever it was read from. + return ResolvePath(member.Expression); + } + + var parent = ResolvePath(member.Expression); + + // A GraphQL field read off something that is not part of the query - a captured entity, for instance - + // selects nothing. + return parent?.AddChildNode(new QueryNode(member.Member)); + } + + private QueryNode ResolveCall(MethodCallExpression call) + { + if (call.Method.GetCustomAttribute() != null) + { + return ResolveGraphMethod(call); + } + + var kind = LinqOperator.Classify(call.Method); + + switch (kind) + { + case LinqOperatorKind.Projection: + case LinqOperatorKind.PassThrough: + return ResolveLinqOperator(call, kind); + + case LinqOperatorKind.Unsupported: + throw Unsupported(call, + $"the LINQ operator '{call.Method.Name}' has no GraphQL equivalent and the selection cannot " + + "follow what it returns", + $"Select the fields the query needs and call '{call.Method.Name}' on the result of " + + "ExecuteAsync instead.", + call.Method.Name); + + default: + // An ordinary method such as string.ToUpper(). It selects nothing itself, but its target and + // arguments may mention fields that have to be fetched for it to run on the result. + Visit(call.Object); + foreach (var argument in call.Arguments) + { + Visit(argument); + } + + return null; + } + } + + private QueryNode ResolveGraphMethod(MethodCallExpression call) + { + // Extension methods carry their target as the first argument; instance methods have it as Object. + var target = call.Object ?? call.Arguments.FirstOrDefault(); + var parent = ResolvePath(target); + + if (parent == null) + { + throw Unsupported(call, + $"the target of '{call.Method.Name}' is not part of the query", + memberName: call.Method.Name); + } + + return parent.AddChildNode(new QueryNode(call.Method, arguments: GetArguments(call))); + } + + private QueryNode ResolveLinqOperator(MethodCallExpression call, LinqOperatorKind kind) + { + var source = ResolvePath(call.Arguments[0]); + var lambdas = GetLambdas(call); + + if (source == null) + { + // The sequence is not part of the query, so there is nothing to bind the lambdas to. + foreach (var argument in call.Arguments) + { + Visit(argument); + } + + return null; + } + + if (kind == LinqOperatorKind.PassThrough) + { + // Predicates and key selectors run on the client, so the members they touch must be fetched, but + // the selection itself stays on the sequence. + var elementType = GetElementType(call.Arguments[0].Type); + + foreach (var lambda in lambdas) + { + Bind(lambda.Parameters[0], source); + + // GroupBy's result selector takes (key, elements): the elements are the sequence itself. + for (var i = 1; i < lambda.Parameters.Count; i++) + { + if (elementType != null && GetElementType(lambda.Parameters[i].Type) == elementType) + { + Bind(lambda.Parameters[i], source); + } + } + + Select(lambda.Body); + } + + return source; + } + + // Select / SelectMany: the selection moves to whatever the projection returns. + QueryNode collection = null; + QueryNode projected = null; + + for (var i = 0; i < lambdas.Count; i++) + { + var lambda = lambdas[i]; + + Bind(lambda.Parameters[0], source); + + // SelectMany's result selector takes (element, collectionElement). + if (i > 0 && lambda.Parameters.Count > 1 && collection != null) + { + Bind(lambda.Parameters[1], collection); + } + + projected = ResolvePath(lambda.Body); + + if (i == 0) + { + collection = projected; + } + } + + return projected; + } + + /// + /// The element type of a sequence type, or null when the type is not a sequence. + /// + private static Type GetElementType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return type.GetGenericArguments()[0]; + } + + return type.GetInterfaces() + .FirstOrDefault(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + ?.GetGenericArguments()[0]; + } + + private static List GetLambdas(MethodCallExpression call) + { + var lambdas = new List(); + + for (var i = 1; i < call.Arguments.Count; i++) + { + if (Unwrap(call.Arguments[i]) is LambdaExpression { Parameters.Count: > 0 } lambda) + { + lambdas.Add(lambda); + } + } + + return lambdas; + } + + private static List GetArguments(MethodCallExpression call) + { + var parameters = call.Method.GetParameters(); + var arguments = new List(); + + for (var i = 0; i < parameters.Length && i < call.Arguments.Count; i++) + { + var attribute = parameters[i].GetCustomAttribute(); + if (attribute == null) + { + continue; + } + + arguments.Add(new ArgumentValue(attribute.GraphQLName, attribute.GraphQLType, + ExpressionEvaluator.Evaluate(call.Arguments[i]))); + } + + return arguments; + } + + protected override Expression VisitMember(MemberExpression node) + { + if (node.Member.GetCustomAttribute() != null) + { + Select(node); + return node; + } + + return base.VisitMember(node); + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Method.GetCustomAttribute() != null || + LinqOperator.IsOperator(node.Method)) + { + Select(node); + return node; + } + + return base.VisitMethodCall(node); + } + + protected override Expression VisitParameter(ParameterExpression node) + { + // The whole element is projected, so take all of its primitive fields. + if (scopes.TryGetValue(node, out var scopeNode)) + { + scopeNode.IncludePrimitive = true; + } + + return node; + } + + private void Bind(ParameterExpression parameter, QueryNode node) + { + scopes[parameter] = node; + } + + private QueryNode Scope(ParameterExpression parameter) + { + if (scopes.TryGetValue(parameter, out var node)) + { + return node; + } + + throw new GraphQueryTranslationException( + $"Cannot translate '{parameter.Name}' of type '{parameter.Type.Name}': it is not bound to a field of " + + "the query. Only the parameters of the lambdas passed to Include and Select can be used to select fields.", + parameter); + } + + private static Expression Unwrap(Expression expression) + { + while (expression != null) + { + switch (expression.NodeType) + { + case ExpressionType.Quote: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.TypeAs: + case ExpressionType.Unbox: + expression = ((UnaryExpression)expression).Operand; + continue; + + default: + return expression; + } + } + + return null; + } + + private static GraphQueryTranslationException Unsupported(Expression expression, string reason, + string hint = null, string memberName = null) + { + var message = $"Cannot translate '{expression}' into a GraphQL selection: {reason}."; + + return new GraphQueryTranslationException(hint == null ? message : message + " " + hint, expression, + memberName); + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/RuntimeInfo.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/RuntimeInfo.cs new file mode 100644 index 00000000..8f9f843f --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/RuntimeInfo.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Written by the Linq2GraphQL generator in standalone mode. +// Please don't edit this file - regenerate instead. +//--------------------------------------------------------------------- + +namespace Linq2GraphQL.TestClientStandalone.Runtime; + +/// +/// The Linq2GraphQL runtime this copy was taken from. It needs these packages: +/// +/// Microsoft.Extensions.Caching.Memory 10.0.11 +/// Microsoft.Extensions.Http 10.0.11 +/// Websocket.Client 5.3.0 +/// +/// +public static class RuntimeInfo +{ + public const string Version = "1.0.81-prerelease"; +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscription.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscription.cs new file mode 100644 index 00000000..0f22ccf4 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscription.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class GraphSubscription : GraphBase> +{ + public GraphSubscription(GraphClient client, string name, OperationType operationType, List arguments) : base(client, name, operationType, arguments) + { + } + + public GraphSubscriptionExecute Select() + { + return new GraphSubscriptionExecute(client, operationType, QueryNode, null); + } + + public GraphSubscriptionExecute Select(Expression> selector) + { + ParseExpression(selector); + return new GraphSubscriptionExecute(client, operationType, QueryNode, selector); + } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscriptionExecute.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscriptionExecute.cs new file mode 100644 index 00000000..44f60729 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/GraphSubscriptionExecute.cs @@ -0,0 +1,312 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq.Expressions; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Text.Json; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class GraphSubscriptionExecute : GraphBaseExecute +{ + private TaskCompletionSource connectedTcs; + + public GraphSubscriptionExecute(GraphClient client, OperationType operationType, QueryNode queryNode, + Expression> selector) : base(client, operationType, queryNode, selector) { } + + /// + /// Completes when the underlying transport is connected (SSE response accepted, + /// WebSocket init/ack received) — lets callers trigger server events deterministically. + /// + internal Task WhenConnected => connectedTcs?.Task ?? Task.CompletedTask; + + public async Task> StartAsync() + { + var results = await StartCoreAsync(continueOnFieldErrors: false); + return results.Select(result => result.Data); + } + + /// + /// Like , but each event is delivered as a + /// carrying its data AND its GraphQL errors. + /// A next event with field errors does NOT end the subscription: the + /// partial data and all later events keep flowing. Only transport and protocol + /// failures terminate the stream. + /// + public async Task>> StartWithErrorsAsync() + { + return await StartCoreAsync(continueOnFieldErrors: true); + } + + private async Task>> StartCoreAsync(bool continueOnFieldErrors) + { + var request = await GetRequestAsync(); + + if (string.IsNullOrWhiteSpace(client.SubscriptionUrl)) + { + throw new Exception("Subscription url is not set"); + } + + var options = client.ClientOptions; + + return Observable.Create>(observer => + { + // Per-subscriber state: each Subscribe() gets its own buffer, transport and + // connection signal. Sharing these across subscribers let one overflow poison + // the others. + var buffer = new SubscriptionEventBuffer( + options.SubscriptionMaxBufferedEvents, options.SubscriptionOverflowPolicy); + var connectedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + connectedTcs = connectedSource; + var cancellation = new CancellationTokenSource(); + var queueLock = new object(); + var signal = new SemaphoreSlim(0); + var completed = false; + var processorExited = false; + Exception failure = null; + IDisposable transportSubscription = Disposable.Empty; + IDisposable transportFailureSubscription = Disposable.Empty; + SSEClient sseClient = null; + WSClient wsClient = null; + + void OnNext(PendingPayload payload) + { + lock (queueLock) + { + if (completed || processorExited || cancellation.IsCancellationRequested) + { + return; + } + + if (!buffer.TryEnqueue(payload, out var overflowFault)) + { + if (overflowFault is not null) + { + failure = overflowFault; + completed = true; + signal.Release(); + } + + return; + } + + signal.Release(); + } + } + + void OnFailure(Exception exception) + { + lock (queueLock) + { + // Terminal transitions are idempotent: a late transport failure must + // not override normal completion, and no callback may signal the + // semaphore once the processor has exited and disposed it. + if (completed || processorExited) + { + return; + } + + failure = exception; + completed = true; + signal.Release(); + } + } + + void OnCompleted() + { + lock (queueLock) + { + if (completed || processorExited) + { + return; + } + + completed = true; + signal.Release(); + } + } + + if (client.SubscriptionProtocol == SubscriptionProtocol.ServerSentEvents) + { + sseClient = new SSEClient(client, request); + transportSubscription = sseClient.Payloads.Subscribe( + bytes => OnNext(new PendingPayload(bytes, default)), OnFailure, OnCompleted); + transportFailureSubscription = sseClient.Failures.Subscribe(OnFailure); + _ = ForwardConnectedAsync(sseClient.Connected, connectedSource); + _ = sseClient.Start(cancellation.Token); + } + else + { + wsClient = new WSClient(client, request); + transportSubscription = wsClient.Payloads.Subscribe( + element => OnNext(new PendingPayload(null, element)), OnFailure, OnCompleted); + transportFailureSubscription = wsClient.Failures.Subscribe(OnFailure); + _ = StartWebSocketAsync(wsClient, OnFailure, connectedSource); + } + + _ = ProcessEventsAsync(); + + return Disposable.Create(() => + { + try + { + cancellation.Cancel(); + } + catch (ObjectDisposedException) + { + // Processor may already have exited and disposed the source. + } + + transportSubscription.Dispose(); + transportFailureSubscription.Dispose(); + sseClient?.Dispose(); + if (wsClient is not null) + { + _ = wsClient.DisposeAsync(); + } + + // The processor owns signal/cancellation cleanup; never block the disposing observer. + }); + + async Task ProcessEventsAsync() + { + try + { + while (true) + { + await signal.WaitAsync(cancellation.Token).ConfigureAwait(false); + + // Drain everything currently queued before waiting again. + while (true) + { + byte[] jsonBytes = null; + JsonElement element = default; + var hasPayload = false; + var shouldComplete = false; + Exception terminalFailure = null; + + lock (queueLock) + { + if (buffer.TryDequeue(out var payload)) + { + jsonBytes = payload.JsonBytes; + element = payload.Element; + hasPayload = true; + } + else if (completed) + { + shouldComplete = true; + terminalFailure = failure; + } + } + + if (!hasPayload) + { + if (shouldComplete) + { + if (terminalFailure is not null) + { + observer.OnError(terminalFailure); + } + else + { + observer.OnCompleted(); + } + + return; + } + + break; + } + + try + { + var full = jsonBytes is not null + ? queryExecutor.ProcessResponseFull(jsonBytes, QueryNode.Name) + : queryExecutor.ProcessResponseFull(element, QueryNode.Name); + + if (full.HasErrors && !continueOnFieldErrors) + { + throw new GraphQueryExecutionException(full.Errors, request.Query, request.Variables); + } + + observer.OnNext(new GraphResult + { + Data = ConvertResult(full.Data), + Errors = full.Errors, + Extensions = full.Extensions + }); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + } + } + } + catch (OperationCanceledException) when (cancellation.IsCancellationRequested) + { + // Unsubscription ends the processor without notifying the observer. + } + finally + { + lock (queueLock) + { + processorExited = true; + } + + signal.Dispose(); + cancellation.Dispose(); + } + } + }); + } + + private readonly record struct PendingPayload(byte[] JsonBytes, JsonElement Element); + + private static async Task StartWebSocketAsync(WSClient wsClient, Action onFailure, + TaskCompletionSource connectedTcs) + { + try + { + await wsClient.Start().ConfigureAwait(false); + connectedTcs.TrySetResult(null); + } + catch (Exception exception) + { + connectedTcs.TrySetException(exception); + onFailure(exception); + } + } + + private static async Task ForwardConnectedAsync(Task connected, TaskCompletionSource connectedTcs) + { + try + { + await connected.ConfigureAwait(false); + connectedTcs.TrySetResult(null); + } + catch (OperationCanceledException) + { + connectedTcs.TrySetCanceled(); + } + catch (Exception exception) + { + connectedTcs.TrySetException(exception); + } + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/IdleTimeoutStream.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/IdleTimeoutStream.cs new file mode 100644 index 00000000..3e3cb048 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/IdleTimeoutStream.cs @@ -0,0 +1,127 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +/// +/// Stream decorator that fails a read with an IOException once no bytes have +/// arrived within the idle timeout. Each read gets its own timer that runs only while +/// that read waits for data, so keep-alives (including WHATWG : comments, which +/// the SSE parser consumes invisibly) keep the stream alive, and time the consumer spends +/// between reads (processing an event, waiting for a thread) never counts as idle. +/// +internal sealed class IdleTimeoutStream : Stream +{ + private readonly Stream inner; + private readonly TimeSpan idleTimeout; + private readonly TimeProvider timeProvider; + private bool disposed; + + /// Clock driving the idle timer; when null. + /// Tests pass a fake provider so the timer only advances when they say so. + internal IdleTimeoutStream(Stream inner, TimeSpan idleTimeout, TimeProvider timeProvider = null) + { + if (idleTimeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(idleTimeout), "Idle timeout must be positive."); + } + + this.inner = inner ?? throw new ArgumentNullException(nameof(inner)); + this.idleTimeout = idleTimeout; + this.timeProvider = timeProvider ?? TimeProvider.System; + } + + public override bool CanRead => inner.CanRead; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(disposed, this); + + // A fresh timer per read, disposed when the read completes: it runs only while waiting + // for data, and a timer that fired during a read which still returned bytes cannot leak + // into the next read (a CancellationTokenSource cannot be reset once cancelled). + // The timer source is bound to the TimeProvider (a linked source cannot be), then linked + // with the caller's token for the read itself. + using var timerCts = new CancellationTokenSource(idleTimeout, timeProvider); + using var readCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timerCts.Token); + + try + { + return await inner.ReadAsync(buffer, readCts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) when ( + timerCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + // The idle timer fired and the caller did not cancel, so no bytes arrived within the + // window: the connection is dead. An OperationCanceledException the inner stream + // raised on its own propagates unchanged. + throw new IOException($"SSE idle timeout: no data received within {FormatTimeout(idleTimeout)}."); + } + } + + /// + /// Whole seconds as "N s", anything else as "N ms" - never rounds up (750 ms is not "1s"); + /// a positive timeout under 1 ms is "<1 ms", not "0 s". Expects a positive timeout, as + /// the constructor enforces. + /// + internal static string FormatTimeout(TimeSpan timeout) + { + var milliseconds = (long)timeout.TotalMilliseconds; + if (milliseconds == 0) + { + return "<1 ms"; + } + + return milliseconds % 1000 == 0 + ? FormattableString.Invariant($"{milliseconds / 1000} s") + : FormattableString.Invariant($"{milliseconds} ms"); + } + + public override void Flush() => throw new NotSupportedException(); + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + if (Interlocked.Exchange(ref disposed, true)) + { + return; + } + + if (disposing) + { + inner.Dispose(); + } + + base.Dispose(disposing); + } + + public override async ValueTask DisposeAsync() + { + if (Interlocked.Exchange(ref disposed, true)) + { + return; + } + + await inner.DisposeAsync().ConfigureAwait(false); + GC.SuppressFinalize(this); + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SSEClient.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SSEClient.cs new file mode 100644 index 00000000..3b15ad93 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SSEClient.cs @@ -0,0 +1,325 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http.Headers; +using System.Net.ServerSentEvents; +using System.Net.Mime; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; +using System.Text.Json; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class SSEClient : IDisposable +{ + private readonly GraphClient graphClient; + private readonly GraphQLRequest payload; + private readonly ISubject payloadSubject = Subject.Synchronize(new Subject()); + private readonly ISubject errorSubject = + Subject.Synchronize(new Subject()); + private readonly ISubject failureSubject = Subject.Synchronize(new Subject()); + private readonly CancellationTokenSource cancellationSource = new(); + private readonly object resourceLock = new(); + private readonly TaskCompletionSource connectedTcs = + new(TaskCreationOptions.RunContinuationsAsynchronously); + private HttpResponseMessage response; + private Stream eventStream; + private int disposed; + private int failureReported; + private int completedSent; + + public SSEClient(GraphClient graphClient, GraphQLRequest payload) + { + this.graphClient = graphClient; + this.payload = payload; + } + + /// Clock for the idle timeout; tests substitute a fake provider. + internal TimeProvider TimeProvider { get; init; } = TimeProvider.System; + + /// + /// UTF-8 payloads of "next" events decoded to strings — deferred per subscriber: + /// the decode only happens for listeners actually attached here. + /// + public IObservable Subscription => payloadSubject + .Select(static data => Encoding.UTF8.GetString(data)); + + /// + /// Completes when the server has accepted the SSE POST (status + content type + /// verified); faults/cancels if the connection fails or is disposed pre-connect. + /// + internal Task Connected => connectedTcs.Task; + + public IObservable Errors => errorSubject.AsObservable(); + internal IObservable Failures => failureSubject.AsObservable(); + + /// + /// Raw UTF-8 payloads of "next" events — the byte-level feed used by + /// GraphSubscriptionExecute to avoid a string round trip per message. + /// + internal IObservable Payloads => payloadSubject.AsObservable(); + + public void Dispose() + { + if (Interlocked.Exchange(ref disposed, 1) != 0) + { + return; + } + + try + { + cancellationSource.Cancel(); + } + catch (ObjectDisposedException) + { + // The reader has already completed and disposed the token source. + } + + lock (resourceLock) + { + eventStream?.Dispose(); + response?.Dispose(); + } + payloadSubject.OnCompleted(); + errorSubject.OnCompleted(); + failureSubject.OnCompleted(); + cancellationSource.Dispose(); + } + + public Task Start() + { + if (Volatile.Read(ref disposed) != 0) + { + connectedTcs.TrySetCanceled(); + return Task.CompletedTask; + } + + return Start(cancellationSource.Token); + } + + internal async Task Start(CancellationToken cancellationToken) + { + if (Volatile.Read(ref disposed) != 0) + { + // Disposed before start: a no-op, not an ObjectDisposedException. + connectedTcs.TrySetCanceled(); + return; + } + + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationSource.Token, cancellationToken); + var token = linkedSource.Token; + + using var request = new HttpRequestMessage(HttpMethod.Post, ""); + + try + { + var json = JsonSerializer.Serialize(payload, graphClient.SerializerOptions); + request.Content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); + await graphClient.ConfigureRequestAsync(request, token).ConfigureAwait(false); + var sentResponse = await graphClient.HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token) + .ConfigureAwait(false); + lock (resourceLock) + { + response = sentResponse; + if (Volatile.Read(ref disposed) != 0) + { + response.Dispose(); + response = null; + connectedTcs.TrySetCanceled(); + return; + } + } + + if (!sentResponse.IsSuccessStatusCode) + { + var content = await sentResponse.Content.ReadAsStringAsync(token).ConfigureAwait(false); + throw new GraphQueryRequestException( + $"SSE connection failed with status {sentResponse.StatusCode}: {content}", + payload.Query, payload.Variables); + } + + if (!string.Equals(sentResponse.Content.Headers.ContentType?.MediaType, "text/event-stream", + StringComparison.OrdinalIgnoreCase)) + { + throw new GraphQueryRequestException( + $"SSE connection returned unsupported content type '{sentResponse.Content.Headers.ContentType}'.", + payload.Query, payload.Variables); + } + + var rawStream = await sentResponse.Content.ReadAsStreamAsync(token).ConfigureAwait(false); + Stream source = rawStream; + + // The server has accepted the subscription POST — the transport is live. + connectedTcs.TrySetResult(null); + + // Optional idle timeout: any bytes (including ':' keep-alive comments, which + // the SSE parser consumes invisibly) reset the timer. + var idleTimeout = graphClient.ClientOptions?.SubscriptionIdleTimeout; + if (idleTimeout is { } timeout) + { + source = new IdleTimeoutStream(rawStream, timeout, TimeProvider); + } + + lock (resourceLock) + { + eventStream = source; + if (Volatile.Read(ref disposed) != 0) + { + eventStream.Dispose(); + eventStream = null; + connectedTcs.TrySetCanceled(); + return; + } + } + + // SseParser (System.Net.ServerSentEvents) implements the WHATWG framing: + // multi-line data joined with '\n', comments ignored, dispatch on blank line, + // incomplete trailing event discarded at EOF. Data is kept as UTF-8 bytes. + var parser = SseParser.Create(source, static (_, data) => data.ToArray()); + await foreach (var item in parser.EnumerateAsync(token).ConfigureAwait(false)) + { + // Older GraphQL-over-SSE servers omit `event: next`. SseParser reports the + // SSE default event type "message" when no event field was sent — map both + // to "next"; Dispatch ignores unknown types (keep-alives etc.) otherwise. + var eventType = item.EventType; + if (string.IsNullOrEmpty(eventType) || eventType == "message") + { + eventType = "next"; + } + + var sseEvent = new SseEvent(eventType, item.Data); + + if (Dispatch(sseEvent)) + { + return; + } + } + + token.ThrowIfCancellationRequested(); + throw new IOException("SSE stream ended before a complete event was received."); + } + catch (OperationCanceledException) when (token.IsCancellationRequested) + { + // Disposal/unsubscription is the normal way to stop an open stream. + connectedTcs.TrySetCanceled(); + } + catch (Exception exception) + { + ReportFailure(exception is HttpRequestException or IOException or OperationCanceledException + ? new GraphQueryRequestException($"SSE connection failed: {exception.Message}", + payload.Query, payload.Variables) + : exception); + } + finally + { + lock (resourceLock) + { + response?.Dispose(); + response = null; + eventStream?.Dispose(); + eventStream = null; + } + + // Complete only if neither a failure nor an explicit server complete + // already terminated the stream (prevents a double OnCompleted). + if (Volatile.Read(ref failureReported) == 0 + && Volatile.Read(ref completedSent) == 0 + && Volatile.Read(ref disposed) == 0) + { + payloadSubject.OnCompleted(); + } + } + } + + private bool Dispatch(SseEvent message) + { + switch (message.EventType) + { + case "next": + payloadSubject.OnNext(message.Data); + return false; + case "error": + ReportFailure(ParseSseError(message.Data)); + return true; + case "complete": + // Guard against a duplicate complete. + if (Interlocked.Exchange(ref completedSent, 1) == 0) + { + payloadSubject.OnCompleted(); + } + + return true; + default: + // WHATWG SSE spec: unknown event names dispatch to no listener. Servers + // and proxies legitimately add event types (e.g. HotChocolate "ka" + // keep-alives); ignoring them keeps the stream alive. + return false; + } + } + + private Exception ParseSseError(byte[] json) + { + try + { + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + var errors = root.ValueKind == JsonValueKind.Array + ? root.Deserialize>(graphClient.SerializerOptions) + : root.TryGetProperty("errors", out var errorsElement) + ? errorsElement.Deserialize>(graphClient.SerializerOptions) + : new List { new() { Message = Encoding.UTF8.GetString(json) } }; + return new GraphQueryExecutionException(errors, payload.Query, payload.Variables); + } + catch (JsonException) + { + return new GraphQueryExecutionException( + new List { new() { Message = Encoding.UTF8.GetString(json) } }, + payload.Query, payload.Variables); + } + catch (InvalidOperationException) + { + return new GraphQueryExecutionException( + new List { new() { Message = Encoding.UTF8.GetString(json) } }, + payload.Query, payload.Variables); + } + } + + private void ReportFailure(Exception exception) + { + // Idempotent: only the first failure emits. This uses a dedicated flag so a + // later Dispose() still runs (it owns resource cleanup via the disposed flag). + if (Interlocked.Exchange(ref failureReported, 1) != 0) + { + return; + } + + if (exception is GraphQueryExecutionException executionException) + { + errorSubject.OnNext(executionException); + } + + // WhenConnected waiters learn about connect failures; a no-op once connected. + connectedTcs.TrySetException(exception); + + failureSubject.OnNext(exception); + failureSubject.OnCompleted(); + payloadSubject.OnCompleted(); + } +} + +internal readonly record struct SseEvent(string EventType, byte[] Data); diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscribeCommands.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscribeCommands.cs new file mode 100644 index 00000000..ed0aa5ae --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscribeCommands.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions +{ + internal class SubscribeCommands + { + internal const string SUBSCRIBE = "subscribe"; + internal const string START = "start"; + internal const string STOP = "stop"; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionEventBuffer.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionEventBuffer.cs new file mode 100644 index 00000000..192b211e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionEventBuffer.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Linq2GraphQL.TestClientStandalone.Runtime; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +/// +/// Bounded event buffer between a subscription transport and its observer. The +/// overflow behavior is controlled by . +/// Callers own synchronization. +/// +internal sealed class SubscriptionEventBuffer +{ + private readonly int maximumBufferedEvents; + private readonly SubscriptionOverflowPolicy policy; + private readonly Queue queue; + + public SubscriptionEventBuffer(int maximumBufferedEvents, SubscriptionOverflowPolicy policy) + { + if (maximumBufferedEvents <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumBufferedEvents), + "SubscriptionMaxBufferedEvents must be greater than zero."); + } + + this.maximumBufferedEvents = maximumBufferedEvents; + this.policy = policy; + queue = new Queue(Math.Min(maximumBufferedEvents, 16)); + } + + public int Count => queue.Count; + + /// + /// Adds an event, applying the overflow policy when full. Returns false when + /// the buffer is in the faulted state (Fault policy with a full buffer); the + /// fault is returned via and the buffer then + /// refuses all further enqueues. + /// + public bool TryEnqueue(T payload, out Exception overflowFault) + { + overflowFault = null; + + if (faulted) + { + return false; + } + + if (queue.Count >= maximumBufferedEvents) + { + switch (policy) + { + case SubscriptionOverflowPolicy.DropOldest: + queue.Dequeue(); + break; + case SubscriptionOverflowPolicy.KeepLatest: + queue.Clear(); + break; + default: + overflowFault = new InvalidOperationException( + $"Subscription event buffer exceeded {maximumBufferedEvents} messages."); + faulted = true; + return false; + } + } + + queue.Enqueue(payload); + return true; + } + + public bool TryDequeue(out T payload) + { + if (queue.Count == 0) + { + payload = default; + return false; + } + + payload = queue.Dequeue(); + return true; + } + + private bool faulted; +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionProtocols.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionProtocols.cs new file mode 100644 index 00000000..fc0cfb66 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/SubscriptionProtocols.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions +{ + internal static class SubscriptionProtocols + { + internal const string GRAPGQL_TRANSPORT_WS = "graphql-transport-ws"; + internal const string GRAPHQL_WS = "graphql-ws"; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WSClient.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WSClient.cs new file mode 100644 index 00000000..026a2614 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WSClient.cs @@ -0,0 +1,843 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.WebSockets; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Runtime.ExceptionServices; +using System.Text.Json; +using System.Text.Json.Nodes; +using Websocket.Client; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class WSClient : IAsyncDisposable +{ + private readonly GraphClient graphClient; + private readonly GraphQLRequest payload; + // Raw string feed kept for backward compatibility; emissions are serialized by the + // transport's single receive loop, so the unsynchronized Subject is safe. + private readonly Subject subscriptionSubject = new(); + private readonly ISubject payloadSubject = Subject.Synchronize(new Subject()); + private readonly ISubject errorSubject = + Subject.Synchronize(new Subject()); + private readonly ISubject failureSubject = Subject.Synchronize(new Subject()); + private readonly WebsocketClient client; + // Shared frozen options (same instance as GraphClient.SerializerOptions) — outgoing + // protocol messages only, but reusing it keeps one metadata cache process-wide. + private readonly JsonSerializerOptions jsonOptions = Linq2GraphQL.TestClientStandalone.Runtime.Common.ClientJson.Default; + private readonly string subscriptionId = Guid.NewGuid().ToString(); + private readonly object stateLock = new(); + private TaskCompletionSource ackSource; + private readonly CancellationTokenSource cancellationSource = new(); + private readonly IDisposable messageSubscription; + private readonly IDisposable disconnectionSubscription; + private Task initializationTask; + private bool acknowledged; + private int retryAttemptsUsed; + private readonly int maxRetryAttempts; + private bool retryLoopRunning; + private bool subscriptionSent; + private bool initSent; + private bool disposed; + private bool intentionalDisconnection; + private int terminalFailure; + private string subprotocol; + // Own no-message watchdog: Websocket.Client's ReconnectTimeout only acts when its own + // reconnection is enabled, which this class disables (verified: no NoMessageReceived event). + private Timer watchdog; + private long lastMessageTicks; + // connection_init payload of the CURRENT connection. Resolved in the connection factory, + // before the socket connects, so the payload delegate runs inside ConnectTimeout and cannot + // hold an open socket past the server's connection_init wait (graphql-ws closes with 4408). + // Read by SendConnectionInit, which runs after the factory for the same generation. + private volatile object connectionInitPayload; + // The last failure of the pre-connect preparation (request hooks, payload delegate). + // Websocket.Client wraps any factory exception in its own WebsocketException; this lets + // Start() and the retry loop surface the caller's original exception instead. + private volatile ExceptionDispatchInfo preparationFailure; + + // Headers the WebSocket handshake owns; a hook setting them must not break the upgrade. + private static readonly HashSet HandshakeOwnedHeaders = new(StringComparer.OrdinalIgnoreCase) + { + "Host", + "Connection", + "Upgrade", + "Sec-WebSocket-Key", + "Sec-WebSocket-Version", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Extensions", + }; + + public WSClient(GraphClient graphClient, GraphQLRequest payload) + { + this.graphClient = graphClient; + this.payload = payload; + maxRetryAttempts = Math.Max(0, graphClient.ClientOptions.SubscriptionRetryAttempts); + + // The async connection factory runs for EVERY connect, including each reconnect attempt + // inside the retry loop — so request hooks (WithAccessToken etc.) run per connection and + // a refreshed token is picked up on reconnect. It must return an already-connected socket. + var factory = new Func>(async (uri, cancellationToken) => + { + var socket = new ClientWebSocket(); + try + { + try + { + socket.Options.AddSubProtocol(subprotocol ?? GetSubprotocolString()); + var authorization = await ApplyRequestHooksAsync(socket.Options, cancellationToken) + .ConfigureAwait(false); + connectionInitPayload = await BuildConnectionInitPayloadAsync(authorization, cancellationToken) + .ConfigureAwait(false); + preparationFailure = null; + } + catch (Exception exception) when (exception is not OperationCanceledException) + { + // Cancellation (ConnectTimeout elapsed, disposal) stays wrapped: a bare + // OperationCanceledException from Start() would read as the caller's own cancel. + preparationFailure = ExceptionDispatchInfo.Capture(exception); + throw; + } + + await socket.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); + return socket; + } + catch + { + socket.Dispose(); + throw; + } + }); + + client = new WebsocketClient(new Uri(graphClient.SubscriptionUrl), null, factory) + { + // This class owns reconnection (bounded, with backoff) and silence detection. The + // library's own loop retries forever and goes silent after an Error. + ReconnectTimeout = null, + IsReconnectionEnabled = false, + // The default 5 s budget covers only the socket connect; the request hooks (token + // fetch, e.g. an IdP round-trip) run inside the same window, so give them room. + ConnectTimeout = TimeSpan.FromSeconds(30), + }; + + messageSubscription = client.MessageReceived.Subscribe(HandleMessage, ReportFailure); + disconnectionSubscription = client.DisconnectionHappened.Subscribe(HandleDisconnection, ReportFailure); + } + + /// + /// Runs the request hooks for one upgrade request and copies the resulting headers onto the + /// socket. Returns the effective Authorization value (for + /// ), or null. + /// + private async Task ApplyRequestHooksAsync(ClientWebSocketOptions socketOptions, + CancellationToken cancellationToken) + { + // The hooks are HttpRequestMessage-based (same shape as queries and SSE); run them on a + // dummy request for the subscription URL, then copy the resulting headers onto the socket. + using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(graphClient.SubscriptionUrl)); + await graphClient.ConfigureRequestAsync(request, cancellationToken).ConfigureAwait(false); + + // Queries get HttpClient.DefaultRequestHeaders from HttpClient.SendAsync; the upgrade does + // not go through HttpClient, so merge them here. Same precedence as SendAsync: a header + // already on the request (set by a hook) wins over the default of the same name. + var defaults = graphClient.HttpClient?.DefaultRequestHeaders; + var headers = new List<(string Name, string Value)>(); + if (defaults is not null) + { + foreach (var (key, values) in defaults) + { + if (!request.Headers.Contains(key)) + { + headers.Add((key, string.Join(", ", values))); + } + } + } + + foreach (var (key, values) in request.Headers) + { + headers.Add((key, string.Join(", ", values))); + } + + var authorization = request.Headers.Authorization?.ToString() ?? defaults?.Authorization?.ToString(); + + // Browsers do not allow custom headers on WebSocket requests: the Authorization value + // returned here travels in the connection_init payload instead (Blazor WebAssembly). + if (OperatingSystem.IsBrowser()) + { + return authorization; + } + + foreach (var (name, value) in headers) + { + if (!HandshakeOwnedHeaders.Contains(name) && !string.IsNullOrEmpty(value)) + { + socketOptions.SetRequestHeader(name, value); + } + } + + return authorization; + } + + public IObservable Subscription => subscriptionSubject.AsObservable(); + internal IObservable Payloads => payloadSubject.AsObservable(); + public IObservable Errors => errorSubject.AsObservable(); + internal IObservable Failures => failureSubject.AsObservable(); + + /// + /// A connection that delivers no frame (data, ping or keep-alive) for this long is treated + /// as lost and goes through the reconnect loop. Mirrors the previous 30 s ReconnectTimeout. + /// + internal TimeSpan NoMessageTimeout { get; set; } = TimeSpan.FromSeconds(30); + + /// + /// Delay before reconnect attempt n (0-based). Tests shorten it so "no background retry" + /// assertions do not depend on outwaiting the real backoff. + /// + internal Func RetryDelay { get; set; } = BackoffDelay; + + public async ValueTask DisposeAsync() + { + lock (stateLock) + { + if (disposed) + { + return; + } + + disposed = true; + intentionalDisconnection = true; + } + + cancellationSource.Cancel(); + watchdog?.Dispose(); + messageSubscription.Dispose(); + disconnectionSubscription.Dispose(); + + try + { + if (client.IsRunning) + { + await client.SendInstant(JsonSerializer.Serialize( + new WebsocketRequest(GetCompleteCommand()) { Id = subscriptionId }, jsonOptions)).ConfigureAwait(false); + } + + await client.Stop(WebSocketCloseStatus.NormalClosure, string.Empty).ConfigureAwait(false); + } + catch (Exception) + { + // Disposal must still release client resources if the transport already failed. + } + + client.Dispose(); + payloadSubject.OnCompleted(); + subscriptionSubject.OnCompleted(); + errorSubject.OnCompleted(); + failureSubject.OnCompleted(); + // cancellationSource is intentionally not disposed: a retry loop still unwinding after + // Cancel() may read its token, and an undisposed CTS without timers holds no resources. + } + + public Task Start() + { + lock (stateLock) + { + if (disposed) + { + throw new ObjectDisposedException(nameof(WSClient)); + } + + initializationTask ??= InitializeAsync(); + return initializationTask; + } + } + + private async Task InitializeAsync() + { + // Created before StartOrFail so an eager ack arriving during the handshake + // completes this generation's source instead of racing a null field (L2). + ackSource = CreateAckSource(); + try + { + await StartTransportAsync().ConfigureAwait(false); + + // Initial connect: a missing subprotocol is a protocol violation and stays terminal. + var negotiated = client.NativeClient?.SubProtocol; + if (string.IsNullOrEmpty(negotiated)) + { + throw new InvalidDataException("WebSocket server did not negotiate a GraphQL subprotocol."); + } + + subprotocol = ValidateNegotiatedSubprotocol(negotiated); + SendConnectionInit(); + await WaitForAckAsync().ConfigureAwait(false); + } + catch + { + // Any failure of the initial connect/handshake propagates to the caller from Start(). + // The transport may still surface the same failure later (e.g. the server closing on + // its own ack timeout); without marking it terminal the retry loop would run the + // full bounded budget in the background and report a second, delayed error for a + // failure the caller already handled. + Interlocked.Exchange(ref terminalFailure, 1); + throw; + } + + StartWatchdog(); + } + + /// + /// , but a failure of our own pre-connect step + /// (a request hook or the connection_init payload delegate) is rethrown as the original + /// exception, with its stack, instead of Websocket.Client's WebsocketException + /// wrapper - so callers can catch their own types (e.g. a token service's exception). + /// + private async Task StartTransportAsync() + { + preparationFailure = null; + try + { + await client.StartOrFail().ConfigureAwait(false); + } + catch (Exception exception) when (preparationFailure is { } failure + && ReferenceEquals(exception.InnerException, failure.SourceException)) + { + failure.Throw(); + } + } + + private void StartWatchdog() + { + Volatile.Write(ref lastMessageTicks, Environment.TickCount64); + var period = TimeSpan.FromMilliseconds(Math.Max(100, NoMessageTimeout.TotalMilliseconds / 4)); + lock (stateLock) + { + if (disposed || watchdog is not null) + { + return; + } + + watchdog = new Timer(_ => CheckSilence(), null, period, period); + } + } + + private void CheckSilence() + { + var silentMs = Environment.TickCount64 - Volatile.Read(ref lastMessageTicks); + if (silentMs < NoMessageTimeout.TotalMilliseconds) + { + return; + } + + lock (stateLock) + { + if (disposed || retryLoopRunning || Volatile.Read(ref terminalFailure) != 0) + { + return; + } + } + + StartRetryLoop(new IOException( + $"No WebSocket message received for {NoMessageTimeout.TotalSeconds:0.#} s; connection treated as lost.")); + } + + private async Task WaitForAckAsync() + { + var source = ackSource; + using var registration = cancellationSource.Token.Register( + () => source.TrySetCanceled(cancellationSource.Token)); + try + { + await source.Task.WaitAsync(TimeSpan.FromSeconds(10), cancellationSource.Token).ConfigureAwait(false); + } + catch (TimeoutException) + { + throw new GraphQueryRequestException( + "WebSocket connection_ack was not received within 10 seconds.", + payload.Query, payload.Variables); + } + } + + private static TaskCompletionSource CreateAckSource() + { + return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + private void SendConnectionInit() + { + // The payload was resolved by the connection factory for this connection (before the + // socket opened), so nothing slow runs between the open socket and connection_init. + var initRequest = new WebsocketRequest(WebsocketRequestTypes.CONNECTION_INIT) + { + Payload = connectionInitPayload + }; + + SendRequest(initRequest); + lock (stateLock) + { + initSent = true; + } + + // An eager server may ack before init was queued; if so, subscribe now. + // SendSubscription is idempotent (subscriptionSent) and no-ops otherwise. + SendSubscription(); + } + + private async ValueTask BuildConnectionInitPayloadAsync(string authorization, + CancellationToken cancellationToken) + { + // New option wins; the obsolete WSConnectionInitPayload is the fallback (resolved inside + // GraphClient so the deprecation pragma lives in exactly one place). + var payload = await graphClient.GetConnectionInitPayloadAsync(cancellationToken).ConfigureAwait(false); + + if (!graphClient.ClientOptions.SendAuthorizationInConnectionInit || string.IsNullOrEmpty(authorization)) + { + return payload; + } + + // Blazor WebAssembly path: the browser drops headers on WebSocket requests, so the token + // produced by the request hooks travels inside connection_init (graphql-ws/Apollo style + // "Authorization" connection parameter). The hook value wins on key conflicts. + if (payload is null) + { + return new JsonObject { ["Authorization"] = authorization }; + } + + // Merge on the JSON the payload serializes to - with the same options it would be sent + // with - so any shape that is a JSON object merges: dictionaries of any value type, + // anonymous objects, POCOs ([JsonPropertyName] honored), JsonObject and JsonElement. + if (JsonSerializer.SerializeToNode(payload, payload.GetType(), jsonOptions) is JsonObject merged) + { + merged["Authorization"] = authorization; + return merged; + } + + throw new InvalidOperationException( + "SendAuthorizationInConnectionInit requires the ConnectionInitPayload to serialize to a JSON " + + "object (or be null) so the Authorization connection parameter can be merged; " + + $"it was {payload.GetType()}."); + } + + private string ValidateNegotiatedSubprotocol(string negotiatedProtocol) + { + // Null on reconnect can mean the handshake has not completed yet. Skipping + // (instead of failing) lets the library's reconnect cycle retry; a wrong + // non-null protocol is a real mismatch and stays terminal. + if (string.IsNullOrEmpty(negotiatedProtocol)) + { + return null; + } + + var expected = GetSubprotocolString(); + if (!string.Equals(negotiatedProtocol, expected, StringComparison.Ordinal)) + { + throw new InvalidDataException($"WebSocket server negotiated '{negotiatedProtocol}', expected '{expected}'."); + } + + return negotiatedProtocol; + } + + private void HandleMessage(ResponseMessage message) + { + Volatile.Write(ref lastMessageTicks, Environment.TickCount64); + if (Volatile.Read(ref terminalFailure) != 0 || disposed) + { + return; + } + + if (string.IsNullOrWhiteSpace(message.Text)) + { + ReportFailure(new InvalidDataException("Received an empty WebSocket frame.")); + return; + } + + try + { + using var document = JsonDocument.Parse(message.Text); + var root = document.RootElement; + var type = root.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : null; + var id = root.TryGetProperty("id", out var idElement) ? idElement.GetString() : null; + + switch (type) + { + case null: + return; + case WebsocketRequestTypes.CONNECTION_ACK: + lock (stateLock) + { + if (disposed) + { + return; + } + + acknowledged = true; + retryAttemptsUsed = 0; + } + + ackSource?.TrySetResult(true); + SendSubscription(); + return; + case WebsocketRequestTypes.CONNECTION_ERROR: + ReportFailure(CreateServerException(root.TryGetProperty("payload", out var connectionError) + ? connectionError + : root)); + return; + case WebsocketRequestTypes.PING: + SendPong(root.TryGetProperty("payload", out var pingPayload) ? pingPayload : null); + return; + case WebsocketRequestTypes.PONG: + return; + case "ka": + return; + case WebsocketRequestTypes.ERROR: + if (MatchesActiveSubscription(id)) + { + ReportFailure(CreateServerException(root.TryGetProperty("payload", out var errorPayload) + ? errorPayload + : default)); + } + + return; + case WebsocketRequestTypes.COMPLETE: + if (MatchesActiveSubscription(id)) + { + // Typed consumers (GraphSubscriptionExecute) listen to payloadSubject; + // completing only the string feed left their observable open forever. + payloadSubject.OnCompleted(); + subscriptionSubject.OnCompleted(); + } + + return; + case WebsocketRequestTypes.NEXT: + case WebsocketRequestTypes.DATA: + if (MatchesActiveSubscription(id) && root.TryGetProperty("payload", out var nextPayload)) + { + // Cloned so the element outlives the disposed JsonDocument. + payloadSubject.OnNext(nextPayload.Clone()); + if (subscriptionSubject.HasObservers) + { + subscriptionSubject.OnNext(nextPayload.GetRawText()); + } + } + + return; + default: + // Unknown message types (proxy injections, forward-compatible server + // additions like graphql-ws "ping"/"pong") are ignored, matching the + // SSE transport's tolerance for unknown event types — a single + // unknown type must not kill the subscription. + return; + } + } + catch (Exception exception) + { + ReportFailure(exception); + } + } + + // graphql-ws reference client (client.ts shouldRetryConnectOrThrow): these close codes are + // fatal and never retried. 4403 Forbidden, 4408 init timeout, 4499 and 4504 are retryable. + private static readonly int[] FatalCloseCodes = + { + 4004, 4005, 4400, 4401, 4406, 4409, 4429, 4500, + }; + + // Internal WebSocket close codes in [1000, 1999] are fatal except the normal/transient ones. + private static readonly int[] NonFatalInternalCloseCodes = { 1000, 1001, 1005, 1006, 1012, 1013, 1014 }; + + internal static bool IsFatalCloseCodeForTests(int code) => IsFatalCloseCode(code); + + private static bool IsFatalCloseCode(int code) + { + if (Array.IndexOf(FatalCloseCodes, code) >= 0) + { + return true; + } + + return code is >= 1000 and <= 1999 && Array.IndexOf(NonFatalInternalCloseCodes, code) < 0; + } + + private void HandleDisconnection(DisconnectionInfo info) + { + if (info.Type == DisconnectionType.Exit || info.Type == DisconnectionType.ByUser) + { + return; + } + + lock (stateLock) + { + if (disposed || intentionalDisconnection || Volatile.Read(ref terminalFailure) != 0) + { + return; + } + } + + if (info.Type == DisconnectionType.Error) + { + // A failed connect/reconnect attempt: the retry loop below decides whether to continue. + StartRetryLoop(info.Exception); + return; + } + + // A protocol-level close code means the server rejected this client or operation; + // reconnecting would retry into the same rejection (spec: fatal, never retried). + if (info.CloseStatus is { } closeStatus && IsFatalCloseCode((int)closeStatus)) + { + // Stop the transport from reconnecting in the background after a terminal close. + info.CancelReconnection = true; + ReportFailure(new IOException( + $"WebSocket closed with fatal protocol code {(int)closeStatus}: {info.CloseStatusDescription}")); + return; + } + + // Recoverable disconnect (Lost / NoMessageReceived / ByServer): reconnect through the + // bounded retry loop so a transient outage does not terminate the subscription. + StartRetryLoop(info.Exception); + } + + private void StartRetryLoop(Exception cause) + { + lock (stateLock) + { + if (retryLoopRunning) + { + return; + } + + retryLoopRunning = true; + acknowledged = false; + subscriptionSent = false; + initSent = false; + } + + _ = RunRetryLoopAsync(cause); + } + + // graphql-ws reference client behaviour: bounded retries, exponential backoff with jitter + // (2^n seconds + 300-3000 ms), a fatal close or an exhausted budget reports ONE terminal error. + private async Task RunRetryLoopAsync(Exception lastException) + { + try + { + while (true) + { + int attempt; + bool exhausted; + lock (stateLock) + { + if (disposed || Volatile.Read(ref terminalFailure) != 0) + { + return; + } + + attempt = retryAttemptsUsed; + exhausted = attempt >= maxRetryAttempts; + if (!exhausted) + { + retryAttemptsUsed++; + } + } + + if (exhausted) + { + // Reported outside stateLock: subscribers may dispose synchronously from OnError, + // and DisposeAsync takes stateLock. Keeps the last cause as the inner exception. + ReportFailure(new IOException( + $"WebSocket reconnect failed after {attempt} attempts; giving up.", lastException)); + return; + } + + var delay = RetryDelay(attempt); + await Task.Delay(delay, cancellationSource.Token).ConfigureAwait(false); + + // Re-check after the backoff: a terminal failure (e.g. the initial connect's + // Start() already threw) can be flagged while this loop is sleeping, and the + // attempt below must not run hooks for a client that already reported failure. + if (Volatile.Read(ref terminalFailure) != 0) + { + return; + } + + try + { + // Websocket.Client keeps a failed/aborted socket as its current one: a bare StartOrFail() + // after a failed attempt returns at once with State=Aborted and no connection (verified). + // Stop() resets it so every attempt opens a real socket. + await client.Stop(WebSocketCloseStatus.NormalClosure, string.Empty).ConfigureAwait(false); + await StartTransportAsync().ConfigureAwait(false); + // A fresh ack source per handshake: the previous one is already completed and would + // make WaitForAckAsync return without waiting for this connection's connection_ack. + subprotocol = ValidateNegotiatedSubprotocol(client.NativeClient?.SubProtocol); + ackSource = CreateAckSource(); + SendConnectionInit(); + await WaitForAckAsync().ConfigureAwait(false); + Volatile.Write(ref lastMessageTicks, Environment.TickCount64); + return; + } + catch (OperationCanceledException) when (cancellationSource.IsCancellationRequested) + { + return; + } + catch (Exception exception) + { + lastException = exception; + } + } + } + catch (OperationCanceledException) when (cancellationSource.IsCancellationRequested) + { + // Disposal ends the loop without reporting. + } + finally + { + lock (stateLock) + { + retryLoopRunning = false; + } + } + } + + private static TimeSpan BackoffDelay(int attempt) + { + // attempt 0 -> 1 s, 1 -> 2 s, 2 -> 4 s ... capped at 30 s; plus 300-3000 ms jitter. + var seconds = Math.Min(30, Math.Pow(2, attempt)); + var jitterMs = Random.Shared.Next(300, 3001); + return TimeSpan.FromSeconds(seconds) + TimeSpan.FromMilliseconds(jitterMs); + } + + private void SendSubscription() + { + lock (stateLock) + { + if (disposed || !initSent || !acknowledged || subscriptionSent + || Volatile.Read(ref terminalFailure) != 0) + { + return; + } + + subscriptionSent = true; + } + + SendRequest(new WebsocketRequest(GetSubscribeCommand()) + { + Id = subscriptionId, + Payload = payload + }); + } + + private bool MatchesActiveSubscription(string id) + { + return !string.IsNullOrEmpty(id) && string.Equals(id, subscriptionId, StringComparison.Ordinal); + } + + private Exception CreateServerException(JsonElement payloadElement) + { + List errors; + if (payloadElement.ValueKind == JsonValueKind.Object + && payloadElement.TryGetProperty("errors", out var errorsElement)) + { + errors = errorsElement.Deserialize>(graphClient.SerializerOptions); + } + else if (payloadElement.ValueKind == JsonValueKind.Array) + { + errors = payloadElement.Deserialize>(graphClient.SerializerOptions); + } + else + { + errors = new List + { + new() { Message = payloadElement.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null + ? "Unknown subscription error" + : payloadElement.GetRawText() } + }; + } + + return new GraphQueryExecutionException(errors, payload.Query, payload.Variables); + } + + private void ReportFailure(Exception exception) + { + // Idempotent: the first failure terminates the stream; late/concurrent + // callbacks (message pump, reconnect, disconnect) must not re-emit after + // OnCompleted, and the recoverable paths must not resubscribe post-failure. + if (Interlocked.Exchange(ref terminalFailure, 1) != 0) + { + return; + } + + intentionalDisconnection = true; + + if (exception is GraphQueryExecutionException executionException) + { + errorSubject.OnNext(executionException); + } + + failureSubject.OnNext(exception); + failureSubject.OnCompleted(); + // Failure was already published above, so the typed pipeline keeps the error + // (its terminal transition is first-wins); this only releases the feed. + payloadSubject.OnCompleted(); + subscriptionSubject.OnCompleted(); + ackSource?.TrySetException(exception); + } + + private void SendPong(JsonElement? payloadElement) + { + var response = new WebsocketRequest(WebsocketRequestTypes.PONG); + if (payloadElement is { } value && value.ValueKind is not (JsonValueKind.Null or JsonValueKind.Undefined)) + { + // Serialize the element directly (valid while the parsed document is alive). + response.Payload = value; + } + + SendRequest(response); + } + + private string GetSubprotocolString() + { + return graphClient.SubscriptionProtocol switch + { + SubscriptionProtocol.GraphQLWebSocket => SubscriptionProtocols.GRAPGQL_TRANSPORT_WS, + SubscriptionProtocol.ApolloWebSocket => SubscriptionProtocols.GRAPHQL_WS, + _ => throw new InvalidOperationException($"{graphClient.SubscriptionProtocol} is unknown") + }; + } + + private string GetSubscribeCommand() + { + return graphClient.SubscriptionProtocol switch + { + SubscriptionProtocol.GraphQLWebSocket => SubscribeCommands.SUBSCRIBE, + SubscriptionProtocol.ApolloWebSocket => SubscribeCommands.START, + _ => throw new InvalidOperationException($"{graphClient.SubscriptionProtocol} is unknown") + }; + } + + private string GetCompleteCommand() + { + return graphClient.SubscriptionProtocol == SubscriptionProtocol.ApolloWebSocket + ? SubscribeCommands.STOP + : WebsocketRequestTypes.COMPLETE; + } + + private void SendRequest(WebsocketRequest request) + { + var json = JsonSerializer.Serialize(request, jsonOptions); + if (!client.Send(json)) + { + throw new IOException("WebSocket message could not be queued for sending."); + } + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequest.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequest.cs new file mode 100644 index 00000000..643d76a3 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequest.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class WebsocketRequest +{ + public WebsocketRequest(string type) + { + Type = type; + } + + [JsonPropertyName("id")] public string Id { get; set; } + + [JsonPropertyName("type")] public string Type { get; set; } + + [JsonPropertyName("payload")] public object Payload { get; set; } +} + +//public class WebsocketRequestPayload +//{ +// [JsonPropertyName("query")] +// public string Query { get; set; } + +// [JsonPropertyName("variables")] +// public Dictionary Variables { get; set; } +//} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequestTypes.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequestTypes.cs new file mode 100644 index 00000000..c77f5d77 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketRequestTypes.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions +{ + internal class WebsocketRequestTypes + { + internal const string PING = "ping"; + internal const string PONG = "pong"; + internal const string CONNECTION_INIT = "connection_init"; + internal const string CONNECTION_ACK = "connection_ack"; + internal const string ERROR = "error"; + internal const string COMPLETE = "complete"; + internal const string NEXT = "next"; + internal const string DATA = "data"; + internal const string CONNECTION_ERROR = "connection_error"; + } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketResponse.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketResponse.cs new file mode 100644 index 00000000..871fba7a --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/Subscriptions/WebsocketResponse.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copy of the Linq2GraphQL runtime, written by the Linq2GraphQL generator +// in standalone mode. Please don't edit this file - regenerate instead. +// The runtime version is in RuntimeInfo.cs. +// Github:https://github.com/linq2graphql/linq2graphql.client +//--------------------------------------------------------------------- +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +public class WebsocketResponse +{ + [JsonPropertyName("id")] public string Id { get; set; } + + [JsonPropertyName("type")] public string Type { get; set; } + + [JsonPropertyName("payload")] public object Payload { get; set; } +} \ No newline at end of file diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Address.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Address.cs new file mode 100644 index 00000000..cae6a429 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Address.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public partial class Address : GraphQLTypeBase +{ + [GraphQLMember("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + + [GraphQLMember("street")] + [JsonPropertyName("street")] + public string Street { get; set; } + + [GraphQLMember("postalCode")] + [JsonPropertyName("postalCode")] + public string PostalCode { get; set; } + + [GraphQLMember("createdDate")] + [JsonPropertyName("createdDate")] + public DateOnly CreatedDate { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsConnection.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsConnection.cs new file mode 100644 index 00000000..d5dc82ed --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsConnection.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// A connection to a list of items. +/// +[global::System.ComponentModel.Description("A connection to a list of items.")] +public partial class AnimalsConnection : GraphQLTypeBase, Linq2GraphQL.TestClientStandalone.Runtime.Common.ICursorPaging +{ + /// + /// Information to aid in pagination. + /// + [global::System.ComponentModel.Description("Information to aid in pagination.")] + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public Linq2GraphQL.TestClientStandalone.Runtime.Common.PageInfo PageInfo { get; set; } + + /// + /// A list of edges. + /// + [global::System.ComponentModel.Description("A list of edges.")] + [GraphQLMember("edges")] + [JsonPropertyName("edges")] + public List Edges { get; set; } + + /// + /// A flattened list of the nodes. + /// + [global::System.ComponentModel.Description("A flattened list of the nodes.")] + [GraphQLMember("nodes")] + [JsonPropertyName("nodes")] + public List Nodes { get; set; } + + /// + /// Identifies the total count of items in the connection. + /// + [global::System.ComponentModel.Description("Identifies the total count of items in the connection.")] + [GraphQLMember("totalCount")] + [JsonPropertyName("totalCount")] + public int TotalCount { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsEdge.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsEdge.cs new file mode 100644 index 00000000..6c88c3a2 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/AnimalsEdge.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// An edge in a connection. +/// +[global::System.ComponentModel.Description("An edge in a connection.")] +public partial class AnimalsEdge : GraphQLTypeBase +{ + /// + /// A cursor for use in pagination. + /// + [global::System.ComponentModel.Description("A cursor for use in pagination.")] + [GraphQLMember("cursor")] + [JsonPropertyName("cursor")] + public string Cursor { get; set; } + + /// + /// The item at the end of the edge. + /// + [global::System.ComponentModel.Description("The item at the end of the edge.")] + [GraphQLMember("node")] + [JsonPropertyName("node")] + public IAnimal Node { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Customer.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Customer.cs new file mode 100644 index 00000000..c4e0f651 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Customer.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public static class CustomerExtensions +{ + [GraphQLMember("relatedCustomers")] + public static List RelatedCustomers(this Customer customer, [GraphQLArgument("relationType", "Int!")] int relationType) + { + return customer.GetMethodValue>("relatedCustomers", relationType); + } + + [GraphQLMember("relatedCustomer")] + public static Customer RelatedCustomer(this Customer customer, [GraphQLArgument("relationType", "Int!")] int relationType) + { + return customer.GetMethodValue("relatedCustomer", relationType); + } + +} + +public partial class Customer : GraphQLTypeBase +{ + [GraphQLMember("customerId")] + [JsonPropertyName("customerId")] + public Guid CustomerId { get; set; } + + [GraphQLMember("customerName")] + [JsonPropertyName("customerName")] + public string CustomerName { get; set; } + + [GraphQLMember("status")] + [JsonPropertyName("status")] + public CustomerStatus Status { get; set; } + + [GraphQLMember("orders")] + [JsonPropertyName("orders")] + public List Orders { get; set; } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public Address Address { get; set; } + + [GraphQLMember("created")] + [JsonPropertyName("created")] + public DateTimeOffset? Created { get; set; } + + private LazyProperty> _relatedCustomers = new(); + /// + /// Do not use in Query, only to retrive result + /// + public List RelatedCustomers => _relatedCustomers.Value(() => GetFirstMethodValue>("relatedCustomers")); + + private LazyProperty _relatedCustomer = new(); + /// + /// Do not use in Query, only to retrive result + /// + public Customer RelatedCustomer => _relatedCustomer.Value(() => GetFirstMethodValue("relatedCustomer")); + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Item.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Item.cs new file mode 100644 index 00000000..941a6f1d --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Item.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public partial class Item : GraphQLTypeBase +{ + [GraphQLMember("itemId")] + [JsonPropertyName("itemId")] + public string ItemId { get; set; } + + [GraphQLMember("itemName")] + [JsonPropertyName("itemName")] + public string ItemName { get; set; } + + [GraphQLMember("data")] + [JsonPropertyName("data")] + public List Data { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Order.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Order.cs new file mode 100644 index 00000000..55f16cd4 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Order.cs @@ -0,0 +1,98 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public static class OrderExtensions +{ + [GraphQLMember("myGradeMethod")] + [Obsolete("This method should not be used anymore")] + public static string MyGradeMethod(this Order order, [GraphQLArgument("name", "String!")] string name) + { + return order.GetMethodValue("myGradeMethod", name); + } + + [GraphQLMember("orderHello")] + public static string OrderHello(this Order order, [GraphQLArgument("name", "String!")] string name, [GraphQLArgument("first", "Int!")] int first) + { + return order.GetMethodValue("orderHello", name, first); + } + + [GraphQLMember("orderAddress")] + public static Address OrderAddress(this Order order, [GraphQLArgument("addressType", "AddressType!")] AddressType addressType) + { + return order.GetMethodValue
("orderAddress", addressType); + } + +} + +public partial class Order : GraphQLTypeBase +{ + private LazyProperty _myGradeMethod = new(); + /// + /// Do not use in Query, only to retrive result + /// + [Obsolete("This method should not be used anymore")] + public string MyGradeMethod => _myGradeMethod.Value(() => GetFirstMethodValue("myGradeMethod")); + + private LazyProperty _orderHello = new(); + /// + /// Do not use in Query, only to retrive result + /// + public string OrderHello => _orderHello.Value(() => GetFirstMethodValue("orderHello")); + + private LazyProperty
_orderAddress = new(); + /// + /// Do not use in Query, only to retrive result + /// + public Address OrderAddress => _orderAddress.Value(() => GetFirstMethodValue
("orderAddress")); + + [GraphQLMember("orderId")] + [JsonPropertyName("orderId")] + public Guid OrderId { get; set; } + + [GraphQLMember("customer")] + [JsonPropertyName("customer")] + public Customer Customer { get; set; } + + [GraphQLMember("address")] + [JsonPropertyName("address")] + public Address Address { get; set; } + + [GraphQLMember("orderDate")] + [JsonPropertyName("orderDate")] + public DateTimeOffset OrderDate { get; set; } + + [GraphQLMember("orderDateTime")] + [JsonPropertyName("orderDateTime")] + public DateTimeOffset OrderDateTime { get; set; } + + [GraphQLMember("orderDay")] + [JsonPropertyName("orderDay")] + public DateOnly OrderDay { get; set; } + + [GraphQLMember("lines")] + [JsonPropertyName("lines")] + public List Lines { get; set; } + + [GraphQLMember("entryTime")] + [JsonPropertyName("entryTime")] + public TimeOnly? EntryTime { get; set; } + + [Obsolete("This propery is obsolete and should not be used!")] + [GraphQLMember("grade")] + [JsonPropertyName("grade")] + public string Grade { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrderLine.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrderLine.cs new file mode 100644 index 00000000..99c4992e --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrderLine.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public partial class OrderLine : GraphQLTypeBase +{ + [GraphQLMember("lineNumber")] + [JsonPropertyName("lineNumber")] + public int LineNumber { get; set; } + + [GraphQLMember("order")] + [JsonPropertyName("order")] + public Order Order { get; set; } + + [GraphQLMember("item")] + [JsonPropertyName("item")] + public Item Item { get; set; } + + [GraphQLMember("price")] + [JsonPropertyName("price")] + public decimal Price { get; set; } + + [GraphQLMember("quantity")] + [JsonPropertyName("quantity")] + public double Quantity { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersConnection.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersConnection.cs new file mode 100644 index 00000000..914fa116 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersConnection.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// A connection to a list of items. +/// +[global::System.ComponentModel.Description("A connection to a list of items.")] +public partial class OrdersConnection : GraphQLTypeBase, Linq2GraphQL.TestClientStandalone.Runtime.Common.ICursorPaging +{ + /// + /// Information to aid in pagination. + /// + [global::System.ComponentModel.Description("Information to aid in pagination.")] + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public Linq2GraphQL.TestClientStandalone.Runtime.Common.PageInfo PageInfo { get; set; } + + /// + /// A list of edges. + /// + [global::System.ComponentModel.Description("A list of edges.")] + [GraphQLMember("edges")] + [JsonPropertyName("edges")] + public List Edges { get; set; } + + /// + /// A flattened list of the nodes. + /// + [global::System.ComponentModel.Description("A flattened list of the nodes.")] + [GraphQLMember("nodes")] + [JsonPropertyName("nodes")] + public List Nodes { get; set; } + + /// + /// Identifies the total count of items in the connection. + /// + [global::System.ComponentModel.Description("Identifies the total count of items in the connection.")] + [GraphQLMember("totalCount")] + [JsonPropertyName("totalCount")] + public int TotalCount { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersEdge.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersEdge.cs new file mode 100644 index 00000000..38276cfb --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersEdge.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// An edge in a connection. +/// +[global::System.ComponentModel.Description("An edge in a connection.")] +public partial class OrdersEdge : GraphQLTypeBase +{ + /// + /// A cursor for use in pagination. + /// + [global::System.ComponentModel.Description("A cursor for use in pagination.")] + [GraphQLMember("cursor")] + [JsonPropertyName("cursor")] + public string Cursor { get; set; } + + /// + /// The item at the end of the edge. + /// + [global::System.ComponentModel.Description("The item at the end of the edge.")] + [GraphQLMember("node")] + [JsonPropertyName("node")] + public Order Node { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationConnection.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationConnection.cs new file mode 100644 index 00000000..d39b9ab4 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationConnection.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// A connection to a list of items. +/// +[global::System.ComponentModel.Description("A connection to a list of items.")] +public partial class OrdersNoBackwardPaginationConnection : GraphQLTypeBase, Linq2GraphQL.TestClientStandalone.Runtime.Common.ICursorPaging +{ + /// + /// Information to aid in pagination. + /// + [global::System.ComponentModel.Description("Information to aid in pagination.")] + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public Linq2GraphQL.TestClientStandalone.Runtime.Common.PageInfo PageInfo { get; set; } + + /// + /// A list of edges. + /// + [global::System.ComponentModel.Description("A list of edges.")] + [GraphQLMember("edges")] + [JsonPropertyName("edges")] + public List Edges { get; set; } + + /// + /// A flattened list of the nodes. + /// + [global::System.ComponentModel.Description("A flattened list of the nodes.")] + [GraphQLMember("nodes")] + [JsonPropertyName("nodes")] + public List Nodes { get; set; } + + /// + /// Identifies the total count of items in the connection. + /// + [global::System.ComponentModel.Description("Identifies the total count of items in the connection.")] + [GraphQLMember("totalCount")] + [JsonPropertyName("totalCount")] + public int TotalCount { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationEdge.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationEdge.cs new file mode 100644 index 00000000..2ba50b75 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoBackwardPaginationEdge.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// An edge in a connection. +/// +[global::System.ComponentModel.Description("An edge in a connection.")] +public partial class OrdersNoBackwardPaginationEdge : GraphQLTypeBase +{ + /// + /// A cursor for use in pagination. + /// + [global::System.ComponentModel.Description("A cursor for use in pagination.")] + [GraphQLMember("cursor")] + [JsonPropertyName("cursor")] + public string Cursor { get; set; } + + /// + /// The item at the end of the edge. + /// + [global::System.ComponentModel.Description("The item at the end of the edge.")] + [GraphQLMember("node")] + [JsonPropertyName("node")] + public Order Node { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountConnection.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountConnection.cs new file mode 100644 index 00000000..d98a1bac --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountConnection.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// A connection to a list of items. +/// +[global::System.ComponentModel.Description("A connection to a list of items.")] +public partial class OrdersNoTotalCountConnection : GraphQLTypeBase, Linq2GraphQL.TestClientStandalone.Runtime.Common.ICursorPaging +{ + /// + /// Information to aid in pagination. + /// + [global::System.ComponentModel.Description("Information to aid in pagination.")] + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public Linq2GraphQL.TestClientStandalone.Runtime.Common.PageInfo PageInfo { get; set; } + + /// + /// A list of edges. + /// + [global::System.ComponentModel.Description("A list of edges.")] + [GraphQLMember("edges")] + [JsonPropertyName("edges")] + public List Edges { get; set; } + + /// + /// A flattened list of the nodes. + /// + [global::System.ComponentModel.Description("A flattened list of the nodes.")] + [GraphQLMember("nodes")] + [JsonPropertyName("nodes")] + public List Nodes { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountEdge.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountEdge.cs new file mode 100644 index 00000000..80a88f92 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersNoTotalCountEdge.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// An edge in a connection. +/// +[global::System.ComponentModel.Description("An edge in a connection.")] +public partial class OrdersNoTotalCountEdge : GraphQLTypeBase +{ + /// + /// A cursor for use in pagination. + /// + [global::System.ComponentModel.Description("A cursor for use in pagination.")] + [GraphQLMember("cursor")] + [JsonPropertyName("cursor")] + public string Cursor { get; set; } + + /// + /// The item at the end of the edge. + /// + [global::System.ComponentModel.Description("The item at the end of the edge.")] + [GraphQLMember("node")] + [JsonPropertyName("node")] + public Order Node { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingConnection.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingConnection.cs new file mode 100644 index 00000000..527b710b --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingConnection.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// A connection to a list of items. +/// +[global::System.ComponentModel.Description("A connection to a list of items.")] +public partial class OrdersOffsetPagingConnection : GraphQLTypeBase, Linq2GraphQL.TestClientStandalone.Runtime.Common.ICursorPaging +{ + /// + /// Information to aid in pagination. + /// + [global::System.ComponentModel.Description("Information to aid in pagination.")] + [GraphQLMember("pageInfo")] + [JsonPropertyName("pageInfo")] + public Linq2GraphQL.TestClientStandalone.Runtime.Common.PageInfo PageInfo { get; set; } + + /// + /// A list of edges. + /// + [global::System.ComponentModel.Description("A list of edges.")] + [GraphQLMember("edges")] + [JsonPropertyName("edges")] + public List Edges { get; set; } + + /// + /// A flattened list of the nodes. + /// + [global::System.ComponentModel.Description("A flattened list of the nodes.")] + [GraphQLMember("nodes")] + [JsonPropertyName("nodes")] + public List Nodes { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingEdge.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingEdge.cs new file mode 100644 index 00000000..b20d5795 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/OrdersOffsetPagingEdge.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +/// +/// An edge in a connection. +/// +[global::System.ComponentModel.Description("An edge in a connection.")] +public partial class OrdersOffsetPagingEdge : GraphQLTypeBase +{ + /// + /// A cursor for use in pagination. + /// + [global::System.ComponentModel.Description("A cursor for use in pagination.")] + [GraphQLMember("cursor")] + [JsonPropertyName("cursor")] + public string Cursor { get; set; } + + /// + /// The item at the end of the edge. + /// + [global::System.ComponentModel.Description("The item at the end of the edge.")] + [GraphQLMember("node")] + [JsonPropertyName("node")] + public Order Node { get; set; } + +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Pig.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Pig.cs new file mode 100644 index 00000000..8b6f29a7 --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Pig.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public partial class Pig : GraphQLTypeBase, IAnimal +{ + [GraphQLMember("spices")] + [JsonPropertyName("spices")] + public string Spices { get; set; } + + [GraphQLMember("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + + [GraphQLMember("numberOfLegs")] + [JsonPropertyName("numberOfLegs")] + public int NumberOfLegs { get; set; } + + [GraphQLMember("speed")] + [JsonPropertyName("speed")] + public int Speed { get; set; } + + [GraphQLMember("__typename")] + [JsonPropertyName("__typename")] + public string __TypeName { get; set; } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Spider.cs b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Spider.cs new file mode 100644 index 00000000..9a08069c --- /dev/null +++ b/test/Linq2GraphQL.TestClientStandalone/Generated/Types/Spider.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// This code was automatically generated by Linq2GraphQL +// Please don't edit this file +// Github:https://github.com/linq2graphql/linq2graphql.client +// Url: https://linq2graphql.com +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Linq2GraphQL.TestClientStandalone.Runtime; +using Linq2GraphQL.TestClientStandalone.Runtime.Common; + +namespace Linq2GraphQL.TestClientStandalone; + + +public partial class Spider : GraphQLTypeBase, IAnimal +{ + [GraphQLMember("poisonous")] + [JsonPropertyName("poisonous")] + public bool Poisonous { get; set; } + + [GraphQLMember("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + + [GraphQLMember("numberOfLegs")] + [JsonPropertyName("numberOfLegs")] + public int NumberOfLegs { get; set; } + + [GraphQLMember("speed")] + [JsonPropertyName("speed")] + public int Speed { get; set; } + + [GraphQLMember("__typename")] + [JsonPropertyName("__typename")] + public string __TypeName { get; set; } +} diff --git a/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj b/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj index 14f3bda7..0b20678a 100644 --- a/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj +++ b/test/Linq2GraphQL.TestClientStandalone/Linq2GraphQL.TestClientStandalone.csproj @@ -1,7 +1,10 @@ - + net10.0 enable @@ -15,6 +18,7 @@ + diff --git a/test/Linq2GraphQL.Tests.Standalone/.gitignore b/test/Linq2GraphQL.Tests.Standalone/.gitignore deleted file mode 100644 index 7726b485..00000000 --- a/test/Linq2GraphQL.Tests.Standalone/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Written by scripts/spike-standalone.ps1 -Tests/ diff --git a/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs b/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs deleted file mode 100644 index 066e615c..00000000 --- a/test/Linq2GraphQL.Tests.Standalone/CoexistenceTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.Options; -using Shouldly; -// An app using several clients imports every runtime namespace; extension methods such as -// ExecuteAsync must still resolve because each one targets its own copy's types. -using Linq2GraphQL.TestClient.Runtime; -using Linq2GraphQL.SecondClient.Runtime; -using Linq2GraphQL.LegacyClient.Runtime; -using Current = Linq2GraphQL.TestClient; -using Second = Linq2GraphQL.SecondClient; -using Legacy = Linq2GraphQL.LegacyClient; - -namespace Linq2GraphQL.Tests; - -/// -/// Several standalone clients in one process, sharing one HttpClient base address and one -/// IMemoryCache, as an app that depends on clients from several teams would. -/// -public class CoexistenceTests -{ - private static readonly Uri baseAddress = new("https://localhost:7184/graphql/"); - - [Fact] - public async Task LegacyAndCurrentClient_InOneProcess_BothWork() - { - using var application = new WebApplicationFactory(); - - var legacy = new Legacy.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), - Options.Create(new Legacy.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); - var current = new Current.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), - Options.Create(new Current.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); - - (await legacy.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); - (await current.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); - (await legacy.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); - (await current.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); - } - - [Fact] - public async Task TwoClientsFromTheSameVersion_SharingCacheAndEndpoint_BothWork() - { - using var application = new WebApplicationFactory(); - - var current = new Current.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), - Options.Create(new Current.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); - var second = new Second.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), - Options.Create(new Second.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); - - (await current.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); - (await second.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); - } -} diff --git a/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj index e8704813..0ebe38e0 100644 --- a/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj +++ b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj @@ -1,7 +1,11 @@ - + net10.0 enable @@ -27,8 +31,30 @@ - - + + + + + diff --git a/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs b/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs new file mode 100644 index 00000000..e32cef4f --- /dev/null +++ b/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs @@ -0,0 +1,21 @@ +// The shared test files import the package namespaces. Nothing here references those packages, so the +// namespaces are declared empty to keep the imports valid, and the standalone copy is imported instead. +global using Linq2GraphQL.TestClientStandalone; +global using Linq2GraphQL.TestClientStandalone.Runtime; +global using Linq2GraphQL.TestClientStandalone.Runtime.Common; +global using Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; + +namespace Linq2GraphQL.Client +{ + namespace Common + { + } + + namespace Subscriptions + { + } +} + +namespace Linq2GraphQL.TestClient +{ +} diff --git a/test/Linq2GraphQL.Tests/CoexistenceTests.cs b/test/Linq2GraphQL.Tests/CoexistenceTests.cs new file mode 100644 index 00000000..12170f93 --- /dev/null +++ b/test/Linq2GraphQL.Tests/CoexistenceTests.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Options; +using Shouldly; +using Package = Linq2GraphQL.TestClient; +using Standalone = Linq2GraphQL.TestClientStandalone; + +namespace Linq2GraphQL.Tests; + +/// +/// A standalone client (its own copy of the runtime) and a package-based client in one process, sharing +/// an endpoint and the app's IMemoryCache, as an app that uses clients from several teams would. +/// +public class CoexistenceTests +{ + private static readonly Uri baseAddress = new("https://localhost:7184/graphql/"); + + [Fact] + public async Task StandaloneAndPackageClient_SharingCacheAndEndpoint_BothWork() + { + using var application = new WebApplicationFactory(); + + var package = new Package.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Client.GraphClientOptions { UseSafeMode = true }), application.Services); + var standalone = new Standalone.SampleClient(application.CreateClient(new() { BaseAddress = baseAddress }), + Options.Create(new Standalone.Runtime.GraphClientOptions { UseSafeMode = true }), application.Services); + + // Safe mode caches the schema per endpoint; each client must get its own runtime's schema type. + (await package.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + (await standalone.Query.Customers().Select().ExecuteAsync()).Count.ShouldBe(2); + (await package.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); + (await standalone.Query.Hello().Select().ExecuteAsync()).ShouldBe("Hello, World!"); + } + + [Fact] + public void StandaloneClient_DoesNotReferenceTheRuntimePackages() + { + var references = typeof(Standalone.SampleClient).Assembly.GetReferencedAssemblies().Select(e => e.Name); + + references.ShouldNotContain("Linq2GraphQL.Client"); + references.ShouldNotContain("Linq2GraphQL.Client.Subscriptions"); + } +} diff --git a/test/Linq2GraphQL.Tests/Linq2GraphQL.Tests.csproj b/test/Linq2GraphQL.Tests/Linq2GraphQL.Tests.csproj index 09926f77..589c11fb 100644 --- a/test/Linq2GraphQL.Tests/Linq2GraphQL.Tests.csproj +++ b/test/Linq2GraphQL.Tests/Linq2GraphQL.Tests.csproj @@ -31,6 +31,7 @@ + diff --git a/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs b/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs new file mode 100644 index 00000000..d4dc64b1 --- /dev/null +++ b/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs @@ -0,0 +1,87 @@ +using Linq2GraphQL.Generator; +using Shouldly; + +namespace Linq2GraphQL.Tests; + +/// +/// Standalone mode writes a copy of the runtime under <namespace>.Runtime next to the client. That the +/// copy compiles and works is covered by Linq2GraphQL.TestClientStandalone and Linq2GraphQL.Tests.Standalone; +/// these tests pin down what the generator emits. +/// +[Collection(GeneratorCollection.Name)] +public class StandaloneGeneratorTests +{ + private const string Sdl = """ + type Query { thing: Thing } + type Subscription { thingAdded: Thing } + type Thing { id: ID! } + """; + + [Fact] + public void Standalone_NothingReferencesTheRuntimePackages() + { + var entries = NewGenerator(standalone: true, includeSubscriptions: true).GenerateFromSdl(Sdl); + + entries.ShouldAllBe(e => !e.Content.Contains("Linq2GraphQL.Client")); + Content(entries, "Client", "TestClient.cs").ShouldContain("using TestNs.Runtime;"); + Content(entries, "Runtime/Client", "GraphClient.cs").ShouldContain("namespace TestNs.Runtime;"); + Content(entries, "Runtime/Subscriptions", "WSClient.cs").ShouldContain("namespace TestNs.Runtime.Subscriptions;"); + } + + [Fact] + public void Standalone_CopiesTheRuntime_WithoutAssemblyAttributes() + { + var entries = NewGenerator(standalone: true, includeSubscriptions: true).GenerateFromSdl(Sdl); + var runtime = entries.Where(e => e.DirectoryName.StartsWith("Runtime")).ToList(); + + runtime.ShouldContain(e => e.DirectoryName == "Runtime/Client/Visitors" && e.FileName == "QueryExpressionVisitor.cs"); + runtime.ShouldNotContain(e => e.FileName == "Assembly.cs"); + runtime.ShouldAllBe(e => !e.Content.Contains("[assembly:")); + runtime.ShouldAllBe(e => e.Content.Contains("")); + } + + [Fact] + public void Standalone_WithoutSubscriptions_LeavesOutTheSubscriptionsRuntime() + { + var entries = NewGenerator(standalone: true, includeSubscriptions: false).GenerateFromSdl(Sdl); + + entries.ShouldNotContain(e => e.DirectoryName.StartsWith("Runtime/Subscriptions")); + Content(entries, "Runtime", "RuntimeInfo.cs").ShouldNotContain("Websocket.Client"); + } + + [Fact] + public void Standalone_RuntimeInfo_RecordsVersionAndPackages() + { + var info = Content(NewGenerator(standalone: true, includeSubscriptions: true).GenerateFromSdl(Sdl), + "Runtime", "RuntimeInfo.cs"); + + info.ShouldContain("namespace TestNs.Runtime;"); + info.ShouldMatch(@"public const string Version = ""\d+\.\d+\.\d+[^""+]*"";"); + info.ShouldContain("Microsoft.Extensions.Caching.Memory"); + info.ShouldContain("Microsoft.Extensions.Http"); + info.ShouldContain("Websocket.Client"); + } + + [Fact] + public void PackageMode_IsUnchangedByStandalone() + { + var package = NewGenerator(standalone: false, includeSubscriptions: true).GenerateFromSdl(Sdl); + var standalone = NewGenerator(standalone: true, includeSubscriptions: true).GenerateFromSdl(Sdl) + .Where(e => !e.DirectoryName.StartsWith("Runtime")) + .ToList(); + + package.ShouldNotContain(e => e.DirectoryName.StartsWith("Runtime")); + standalone.Select(e => e.FileName).ShouldBe(package.Select(e => e.FileName)); + for (var i = 0; i < package.Count; i++) + { + standalone[i].Content.ShouldBe(package[i].Content.Replace("Linq2GraphQL.Client", "TestNs.Runtime")); + } + } + + private static ClientGenerator NewGenerator(bool standalone, bool includeSubscriptions) => + new("TestNs", "TestClient", includeSubscriptions, EnumGeneratorStrategy.FailIfMissing, false, false, + standalone: standalone); + + private static string Content(List entries, string directory, string fileName) => + entries.Single(e => e.DirectoryName == directory && e.FileName == fileName).Content; +} diff --git a/test/Linq2GraphQL.Tests/packages.lock.json b/test/Linq2GraphQL.Tests/packages.lock.json index 79a71836..52191f54 100644 --- a/test/Linq2GraphQL.Tests/packages.lock.json +++ b/test/Linq2GraphQL.Tests/packages.lock.json @@ -926,6 +926,14 @@ "Linq2GraphQL.Client.Subscriptions": "[1.0.0, )" } }, + "linq2graphql.testclientstandalone": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", + "Microsoft.Extensions.Http": "[10.0.11, )", + "Websocket.Client": "[5.3.0, )" + } + }, "linq2graphql.testserver": { "type": "Project", "dependencies": { From 3fe9516ec164a28cb867494742c0c73bc06a1e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Mon, 26 Oct 2026 21:40:05 +0100 Subject: [PATCH 4/6] CLAUDE.md: runtime changes need a test client regenerate --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9911361c..01ef6abc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,6 @@ Key details that bite: `Linq2GraphQL.Tests` is xUnit + Shouldly + Moq. It spins up the real GraphQL server in-process with `WebApplicationFactory` — `Linq2GraphQL.TestServer` is HotChocolate over the POCOs in `TestServer.Shared`, and `TestServerNullable` is the same schema for the nullable client. `SampleClientFixture` / `SampleClientNullableFixture` wire the generated client to that in-memory host (safe mode on, SSE subscriptions), and test classes take them via `IClassFixture<>`. So most tests are end-to-end: an assertion failure can come from the expression parser, the query text, or the server's own resolvers. -`Linq2GraphQL.TestClient` / `TestClientNullable` / `TestClientStandalone` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots both test servers over plain HTTP, regenerates both clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. +`Linq2GraphQL.TestClient` / `TestClientNullable` / `TestClientStandalone` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots the test servers over plain HTTP, regenerates the clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. `TestClientStandalone` contains a copy of the runtime, so **any change under `src/Linq2GraphQL.Client` or `.Subscriptions` also needs a regenerate**; `-Check` ignores only the version line in its `RuntimeInfo.cs`. `docs/` (the Blazor documentation site) and `StartGG/` (a sample client) are outside the CI solution filter and can drift. From bb4ff07852b96930e9fdb3c44962e35256434140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Tue, 27 Oct 2026 00:50:01 +0100 Subject: [PATCH 5/6] Docs: standalone clients --- CHANGELOG.md | 9 +++ README.md | 20 +++++++ docs/Linq2GraphQL.Docs/Pages/Generator.razor | 62 ++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf55d54b..51ab570a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added +- **Standalone clients** (`--standalone`, `-sa`, or `"standalone": true` in the settings file): the + generator writes a copy of the runtime into `Runtime/` under `.Runtime`, so the client + references no Linq2GraphQL package. Clients generated with different generator versions can then be + used in one app. `Runtime/RuntimeInfo.cs` records the runtime version and the packages it needs, which + the generator also prints. `Linq2GraphQL.Client.AI` does not support standalone clients yet. + - `GraphSubscriptionExecute.StartWithErrorsAsync()` returns `IObservable>`. Each event carries its data AND its GraphQL `errors`. A `next` event with field errors no longer terminates the subscription: partial data and later events keep flowing. @@ -111,6 +117,9 @@ Behaviour changes that callers may notice. None of them changes a public signatu ### Fixed +- The safe-mode schema cache key includes the schema type, so clients with their own runtime copy + that share an `IMemoryCache` and an endpoint no longer read each other's cached schema + (`InvalidCastException`). - A server `complete` frame now completes the typed subscription observable. Previously the observable stayed open after the server finished the stream. - A `Fault` overflow in one subscriber no longer starves other subscribers of the same diff --git a/README.md b/README.md index bf245945..ed64f92d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Usage: -d --deprecated Include Deprecated as Obsolete -sc --schema Schema file (SDL) to generate from instead of an endpoint -ag --agent Agent mode: also emit the schema as SDL (see docs/design/agent-mode.md) + -sa --standalone Write a copy of the runtime into the output, see Standalone clients below -cf --config Json settings file, see Configuration file below As an example: @@ -139,6 +140,7 @@ file is allowed to be absent. | `nullable` | bool | `false` | `-nu`, `--nullable` | | `deprecated` | bool | `false` | `-d`, `--deprecated` | | `agent` | bool | `false` | `-ag`, `--agent` | +| `standalone` | bool | `false` | `-sa`, `--standalone` | | `scalarMappings` | object | none | *no equivalent* | | `descriptionAttributes` | bool | `true` | *no equivalent* | @@ -181,6 +183,24 @@ Scalar names are matched case insensitively. The target must be one of the suppo (`System.Int64`). Anything else fails the run with an error rather than generating a client that does not compile. +### Standalone clients + +A generated client normally references the `Linq2GraphQL.Client` package, and an app gets one version +of it however many clients it uses. When clients are shipped as packages of their own by different +teams, an app that references clients made with different generator versions ends up with whichever +runtime NuGet picks. With `--standalone` (`-sa`) the generator instead writes a copy of the runtime into +`Runtime/` in the output, under the namespace `.Runtime`, so the client depends on nothing +from Linq2GraphQL: + + Linq2GraphQL https://spacex-production.up.railway.app/ -c="SpaceXClient" -n="SpaceX" -o="Generated" --standalone + +`Runtime/RuntimeInfo.cs` records the runtime version and the packages the copy needs, and the generator +prints them as `PackageReference` lines to add instead of `Linq2GraphQL.Client`. Code using the client +does not change; runtime types you name yourself (`GraphClientOptions`, `GraphQueryExecutionException`) +come from `SpaceX.Runtime`. A runtime fix reaches the client only when you regenerate, the `Runtime` +folder is rewritten on every run (don't edit it), and `Linq2GraphQL.Client.AI` does not work with +standalone clients yet. + ## Add Nuget Latest diff --git a/docs/Linq2GraphQL.Docs/Pages/Generator.razor b/docs/Linq2GraphQL.Docs/Pages/Generator.razor index 932f543b..c645329a 100644 --- a/docs/Linq2GraphQL.Docs/Pages/Generator.razor +++ b/docs/Linq2GraphQL.Docs/Pages/Generator.razor @@ -29,6 +29,7 @@ -es, --enum-strategyFailIfMissingAddUnknownOption gives every enum an extra Unknown member, so values added to the schema later don't break deserialization. -d, --deprecatedfalseInclude deprecated fields and enum values, marked [Obsolete]. Without it they are left out. -ag, --agentfalseAgent mode, in preview: also emit the schema as SDL so Linq2GraphQL.Client.AI can expose it to AI agents. + -sa, --standalonefalseWrite a copy of the runtime into the output, so the client needs no Linq2GraphQL.Client package. See standalone clients. -cf, --configlinq2graphql.jsonA json settings file, see below. @@ -86,6 +87,7 @@ nullableboolfalse-nu, --nullable deprecatedboolfalse-d, --deprecated agentboolfalse-ag, --agent + standaloneboolfalse-sa, --standalone scalarMappingsobjectnoneno equivalent descriptionAttributesbooltrueno equivalent @@ -153,6 +155,57 @@

+
+

Standalone clients

+

+ A generated client normally references the Linq2GraphQL.Client package, and an app gets one + version of that package however many clients it uses. That is a problem when clients are built and + shipped as packages of their own, by different teams: an app that references two clients made with + different generator versions ends up with whichever runtime NuGet picks, which may not be the one either + client was generated against. +

+

+ With --standalone the generator writes a copy of the runtime next to the client instead, + under the namespace <namespace>.Runtime. The client then depends on nothing from + Linq2GraphQL, so any number of standalone clients, of any generator version, can live in one app, next to + clients that use the package. +

+ +

+ The runtime lands in a Runtime folder in the output. Runtime/Subscriptions is + only written with --subscriptions. Runtime/RuntimeInfo.cs records the runtime + version the copy was taken from and the packages it needs, and the generator prints them as + PackageReference lines to add to the client's project, in place of + Linq2GraphQL.Client: +

+ +

+ Code that uses the client does not change: AddSpaceXClient(), Select and + ExecuteAsync work as before. Runtime types you name yourself, such as + GraphClientOptions or GraphQueryExecutionException, come from + SpaceX.Runtime rather than Linq2GraphQL.Client. +

+

Things to know before choosing it:

+
    +
  • + A runtime fix reaches a standalone client only when the client is regenerated with a newer generator. + That is the point (each client keeps the runtime it was tested with), but nothing updates it for you. +
  • +
  • + Don't edit the files in Runtime. The folder belongs to the generator and is deleted and + rewritten on every run, and turning standalone off removes it. +
  • +
  • + Each client has its own runtime types, so two standalone clients throw different + GraphQueryExecutionException types; catch each one in terms of its own client. +
  • +
  • + Linq2GraphQL.Client.AI is built against the package's types and does not work with a + standalone client yet. +
  • +
+
+ @code { private const string Usage = """ Linq2GraphQL [] [options] @@ -178,6 +231,15 @@ Linq2GraphQL --config linq2graphql.json -o=SomewhereElse """; + private const string Standalone = """ + Linq2GraphQL https://spacex-production.up.railway.app/ -c=SpaceXClient -n=SpaceX -o=Generated --standalone + """; + + private const string StandalonePackages = """ + + + """; + private const string ConfigJson = """ { "endpoint": "https://spacex-production.up.railway.app/", From 0cda0751883e678df15b11a0eb8ed0f58633a8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Tue, 27 Oct 2026 01:03:17 +0100 Subject: [PATCH 6/6] Standalone: copy the agent tools (Linq2GraphQL.Client.AI) in agent mode --- CHANGELOG.md | 3 +- CLAUDE.md | 4 +- README.md | 13 +- docs/Linq2GraphQL.Docs/Pages/Generator.razor | 13 +- src/Linq2GraphQL.Generator/ClientGenerator.cs | 2 +- .../Linq2GraphQL.Generator.csproj | 6 +- src/Linq2GraphQL.Generator/Program.cs | 2 +- .../StandaloneRuntime.cs | 21 +- .../Runtime/AI/AgentConfigurationException.cs | 32 + .../Generated/Runtime/AI/AgentOptions.cs | 226 +++++++ .../Runtime/AI/AgentToolsExtensions.cs | 113 ++++ .../Runtime/AI/ExpressionReferences.cs | 83 +++ .../Runtime/AI/Schema/ExampleFormatter.cs | 111 ++++ .../Runtime/AI/Schema/NameSuggestions.cs | 87 +++ .../Runtime/AI/Schema/QueryValidator.cs | 629 ++++++++++++++++++ .../Runtime/AI/Schema/SchemaModel.cs | 121 ++++ .../Runtime/AI/Schema/SchemaRenderer.cs | 462 +++++++++++++ .../Runtime/AI/Schema/SchemaShape.cs | 145 ++++ .../Runtime/AI/Schema/ScopedSchema.cs | 419 ++++++++++++ .../Generated/Runtime/AI/Schema/SdlReader.cs | 215 ++++++ .../Runtime/AI/Tools/AgentToolset.cs | 449 +++++++++++++ .../Generated/Runtime/RuntimeInfo.cs | 4 +- .../Linq2GraphQL.TestClientStandalone.csproj | 5 +- .../packages.lock.json | 12 + .../Linq2GraphQL.Tests.Standalone.csproj | 11 +- .../StandaloneNamespaces.cs | 14 + .../packages.lock.json | 68 +- .../StandaloneGeneratorTests.cs | 27 +- test/Linq2GraphQL.Tests/packages.lock.json | 2 + 29 files changed, 3240 insertions(+), 59 deletions(-) create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/AgentConfigurationException.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/AgentOptions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/AgentToolsExtensions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/ExpressionReferences.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/ExampleFormatter.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/NameSuggestions.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/QueryValidator.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/SchemaModel.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/SchemaRenderer.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/SchemaShape.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/ScopedSchema.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Schema/SdlReader.cs create mode 100644 test/Linq2GraphQL.TestClientStandalone/Generated/Runtime/AI/Tools/AgentToolset.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ab570a..f2d9bcf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). generator writes a copy of the runtime into `Runtime/` under `.Runtime`, so the client references no Linq2GraphQL package. Clients generated with different generator versions can then be used in one app. `Runtime/RuntimeInfo.cs` records the runtime version and the packages it needs, which - the generator also prints. `Linq2GraphQL.Client.AI` does not support standalone clients yet. + the generator also prints. With `--agent` the copy includes the agent tools from + `Linq2GraphQL.Client.AI` too, in `.Runtime.AI`. - `GraphSubscriptionExecute.StartWithErrorsAsync()` returns `IObservable>`. Each event carries its data AND its GraphQL `errors`. A `next` event with field errors diff --git a/CLAUDE.md b/CLAUDE.md index 01ef6abc..4b310701 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,7 @@ Key details that bite: **T4 workflow (from DEVELOPER.md — read it before touching templates):** each template is three files — `X.tt` (source, edit this), `X.tt.cs` (hand-written partial with constructor params and helpers), and `X.g.cs` (preprocessed output, **generated at build time, gitignored**). The generator's csproj runs the pinned `dotnet-t4` local tool (`.config/dotnet-tools.json`) over every `Templates\**\*.tt` before compiling, so editing a `.tt` and building is the whole loop and stale template logic cannot be built. Compile errors inside template code are reported against the `.tt` file and line. A template in a new folder needs a `T4Template` item with a `TemplateNamespace` in the csproj. -**Standalone mode** (`--standalone`, `StandaloneRuntime.cs`) makes the output carry its own copy of the runtime instead of depending on the packages, so clients generated by different versions can share an app. The csproj embeds `src/Linq2GraphQL.Client` and `.Subscriptions` source (minus `Assembly.cs`) as resources; the generator writes it to `Runtime/` with `Linq2GraphQL.Client` rewritten to `.Runtime` everywhere, including the generated client files. So the runtime must stay copyable: no reliance on its own assembly name, no `InternalsVisibleTo` from generated code, no hard-coded `"Linq2GraphQL.Client"` strings, and anything shared across apps (like the safe-mode cache key) must be keyed by type so copies don't collide. `TestClientStandalone` is the checked-in standalone output; `Linq2GraphQL.Tests.Standalone` compiles the main suite's test files against it, with `StandaloneNamespaces.cs` pointing the package namespaces at the copy. +**Standalone mode** (`--standalone`, `StandaloneRuntime.cs`) makes the output carry its own copy of the runtime instead of depending on the packages, so clients generated by different versions can share an app. The csproj embeds `src/Linq2GraphQL.Client`, `.Subscriptions` and `.AI` source (minus `Assembly.cs`) as resources; Subscriptions is copied only with `--subscriptions` and AI only with `--agent`; the generator writes it to `Runtime/` with `Linq2GraphQL.Client` rewritten to `.Runtime` everywhere, including the generated client files. So the runtime must stay copyable: no reliance on its own assembly name, no `InternalsVisibleTo` from generated code, no hard-coded `"Linq2GraphQL.Client"` strings, and anything shared across apps (like the safe-mode cache key) must be keyed by type so copies don't collide. `TestClientStandalone` is the checked-in standalone output; `Linq2GraphQL.Tests.Standalone` compiles the main suite's test files against it, with `StandaloneNamespaces.cs` pointing the package namespaces at the copy. `GeneratorSettings.Current.Nullable` is ambient static state read from inside templates; the nullable and non-nullable clients differ mainly in nullable annotations and `#pragma warning disable CS8618`. @@ -75,6 +75,6 @@ Key details that bite: `Linq2GraphQL.Tests` is xUnit + Shouldly + Moq. It spins up the real GraphQL server in-process with `WebApplicationFactory` — `Linq2GraphQL.TestServer` is HotChocolate over the POCOs in `TestServer.Shared`, and `TestServerNullable` is the same schema for the nullable client. `SampleClientFixture` / `SampleClientNullableFixture` wire the generated client to that in-memory host (safe mode on, SSE subscriptions), and test classes take them via `IClassFixture<>`. So most tests are end-to-end: an assertion failure can come from the expression parser, the query text, or the server's own resolvers. -`Linq2GraphQL.TestClient` / `TestClientNullable` / `TestClientStandalone` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots the test servers over plain HTTP, regenerates the clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. `TestClientStandalone` contains a copy of the runtime, so **any change under `src/Linq2GraphQL.Client` or `.Subscriptions` also needs a regenerate**; `-Check` ignores only the version line in its `RuntimeInfo.cs`. +`Linq2GraphQL.TestClient` / `TestClientNullable` / `TestClientStandalone` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots the test servers over plain HTTP, regenerates the clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. `TestClientStandalone` contains a copy of the runtime, so **any change under `src/Linq2GraphQL.Client`, `.Subscriptions` or `.AI` also needs a regenerate**; `-Check` ignores only the version line in its `RuntimeInfo.cs`. `docs/` (the Blazor documentation site) and `StartGG/` (a sample client) are outside the CI solution filter and can drift. diff --git a/README.md b/README.md index ed64f92d..2366282f 100644 --- a/README.md +++ b/README.md @@ -194,12 +194,13 @@ from Linq2GraphQL: Linq2GraphQL https://spacex-production.up.railway.app/ -c="SpaceXClient" -n="SpaceX" -o="Generated" --standalone -`Runtime/RuntimeInfo.cs` records the runtime version and the packages the copy needs, and the generator -prints them as `PackageReference` lines to add instead of `Linq2GraphQL.Client`. Code using the client -does not change; runtime types you name yourself (`GraphClientOptions`, `GraphQueryExecutionException`) -come from `SpaceX.Runtime`. A runtime fix reaches the client only when you regenerate, the `Runtime` -folder is rewritten on every run (don't edit it), and `Linq2GraphQL.Client.AI` does not work with -standalone clients yet. +With `--agent` the copy also includes the agent tools from `Linq2GraphQL.Client.AI`, in +`.Runtime.AI`. `Runtime/RuntimeInfo.cs` records the runtime version and the packages the +copy needs, and the generator prints them as `PackageReference` lines to add instead of the +Linq2GraphQL packages. Code using the client does not change; runtime types you name yourself +(`GraphClientOptions`, `GraphQueryExecutionException`) come from `SpaceX.Runtime`. A runtime fix +reaches the client only when you regenerate, and the `Runtime` folder is rewritten on every run (don't +edit it). ## Add Nuget diff --git a/docs/Linq2GraphQL.Docs/Pages/Generator.razor b/docs/Linq2GraphQL.Docs/Pages/Generator.razor index c645329a..78a7e811 100644 --- a/docs/Linq2GraphQL.Docs/Pages/Generator.razor +++ b/docs/Linq2GraphQL.Docs/Pages/Generator.razor @@ -173,7 +173,8 @@

The runtime lands in a Runtime folder in the output. Runtime/Subscriptions is - only written with --subscriptions. Runtime/RuntimeInfo.cs records the runtime + only written with --subscriptions, and Runtime/AI, a copy of the agent tools + from Linq2GraphQL.Client.AI, only with --agent. Runtime/RuntimeInfo.cs records the runtime version the copy was taken from and the packages it needs, and the generator prints them as PackageReference lines to add to the client's project, in place of Linq2GraphQL.Client: @@ -183,7 +184,9 @@ Code that uses the client does not change: AddSpaceXClient(), Select and ExecuteAsync work as before. Runtime types you name yourself, such as GraphClientOptions or GraphQueryExecutionException, come from - SpaceX.Runtime rather than Linq2GraphQL.Client. + SpaceX.Runtime rather than Linq2GraphQL.Client. In agent mode, + AsAgentTools comes from SpaceX.Runtime.AI, so don't add the + Linq2GraphQL.Client.AI package either.

Things to know before choosing it:

    @@ -200,8 +203,10 @@ GraphQueryExecutionException types; catch each one in terms of its own client.
  • - Linq2GraphQL.Client.AI is built against the package's types and does not work with a - standalone client yet. + The packages the copy needs (Microsoft.Extensions.*, Websocket.Client and, + in agent mode, GraphQL-Parser and Microsoft.Extensions.AI.Abstractions) are + still ordinary NuGet references, so an app gets one version of each. They keep to semantic + versioning, so the highest one requested works for every client.
diff --git a/src/Linq2GraphQL.Generator/ClientGenerator.cs b/src/Linq2GraphQL.Generator/ClientGenerator.cs index 7e233093..45cc8f85 100644 --- a/src/Linq2GraphQL.Generator/ClientGenerator.cs +++ b/src/Linq2GraphQL.Generator/ClientGenerator.cs @@ -248,7 +248,7 @@ private List Generate(RootSchema rootSchema) entry.Content = StandaloneRuntime.RewriteNamespaces(entry.Content, namespaceName); } - entries.AddRange(StandaloneRuntime.GetFiles(namespaceName, includeSubscriptions)); + entries.AddRange(StandaloneRuntime.GetFiles(namespaceName, includeSubscriptions, agent)); } return entries; diff --git a/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj b/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj index a4bb853a..82496202 100644 --- a/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj +++ b/src/Linq2GraphQL.Generator/Linq2GraphQL.Generator.csproj @@ -74,7 +74,8 @@ + net10.0 @@ -12,6 +13,8 @@ + + diff --git a/test/Linq2GraphQL.TestClientStandalone/packages.lock.json b/test/Linq2GraphQL.TestClientStandalone/packages.lock.json index 99638226..f4321b9e 100644 --- a/test/Linq2GraphQL.TestClientStandalone/packages.lock.json +++ b/test/Linq2GraphQL.TestClientStandalone/packages.lock.json @@ -2,6 +2,18 @@ "version": 2, "dependencies": { "net10.0": { + "GraphQL-Parser": { + "type": "Direct", + "requested": "[9.5.1, )", + "resolved": "9.5.1", + "contentHash": "pUYpupTsVyNcKemko8f0iknJa2z/oZsfzwb4crgEFN9/pKf3k35gxVXDDOkD3NVPF1ea6GUV0jgxkjCozFj7Fg==" + }, + "Microsoft.Extensions.AI.Abstractions": { + "type": "Direct", + "requested": "[10.10.0, )", + "resolved": "10.10.0", + "contentHash": "frBHj2ckpevA+KdDLikxv8oxGPrJiyTvaiblYp+vtVEt0q6J73XP5m2SICm86nFK6rl+L1GCKxIy4S5Lb3p2kQ==" + }, "Microsoft.Extensions.Caching.Memory": { "type": "Direct", "requested": "[10.0.11, )", diff --git a/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj index 0ebe38e0..15967aa7 100644 --- a/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj +++ b/test/Linq2GraphQL.Tests.Standalone/Linq2GraphQL.Tests.Standalone.csproj @@ -17,6 +17,7 @@ + @@ -34,24 +35,20 @@ diff --git a/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs b/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs index e32cef4f..25265a60 100644 --- a/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs +++ b/test/Linq2GraphQL.Tests.Standalone/StandaloneNamespaces.cs @@ -4,6 +4,9 @@ global using Linq2GraphQL.TestClientStandalone.Runtime; global using Linq2GraphQL.TestClientStandalone.Runtime.Common; global using Linq2GraphQL.TestClientStandalone.Runtime.Subscriptions; +global using Linq2GraphQL.TestClientStandalone.Runtime.AI; +global using Linq2GraphQL.TestClientStandalone.Runtime.AI.Schema; +global using Linq2GraphQL.TestClientStandalone.Runtime.AI.Tools; namespace Linq2GraphQL.Client { @@ -11,6 +14,17 @@ namespace Common { } + namespace AI + { + namespace Schema + { + } + + namespace Tools + { + } + } + namespace Subscriptions { } diff --git a/test/Linq2GraphQL.Tests.Standalone/packages.lock.json b/test/Linq2GraphQL.Tests.Standalone/packages.lock.json index f6934277..50bda511 100644 --- a/test/Linq2GraphQL.Tests.Standalone/packages.lock.json +++ b/test/Linq2GraphQL.Tests.Standalone/packages.lock.json @@ -13,6 +13,19 @@ "Microsoft.Extensions.Hosting": "10.0.11" } }, + "Microsoft.Extensions.AI": { + "type": "Direct", + "requested": "[10.10.0, )", + "resolved": "10.10.0", + "contentHash": "XKvtDUD3KySkvA5UpS+UF67M5rcqiXjAQchTZZ26WnavVKXxwlUTVdK/Ju0tc/25+16AVUdNl8cnVVGX/tx03A==", + "dependencies": { + "Microsoft.Extensions.AI.Abstractions": "10.10.0", + "Microsoft.Extensions.Caching.Abstractions": "10.0.12", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", + "Microsoft.Extensions.Logging.Abstractions": "10.0.12", + "System.Numerics.Tensors": "10.0.12" + } + }, "Microsoft.Extensions.TimeProvider.Testing": { "type": "Direct", "requested": "[10.10.0, )", @@ -542,10 +555,10 @@ }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", + "resolved": "10.0.12", + "contentHash": "sI3A6MUAyZwccLnwq0cXuRmC3WBtXieimBWafXSfhRJ4jnM2uGVXrqImDDVyW27u4vVaTOGlJxNJd8d1FGvCNQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.11" + "Microsoft.Extensions.Primitives": "10.0.12" } }, "Microsoft.Extensions.Configuration.Abstractions": { @@ -576,8 +589,8 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" + "resolved": "10.0.12", + "contentHash": "9/qymSh7hVDMGTGwrLz8MRp5zRyXy9adGDOs4HwRdnLil3oZGYuWeZjbmHgCQ9BL1qBroVfgUK3U/nb61617Cw==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -669,10 +682,10 @@ }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", + "resolved": "10.0.12", + "contentHash": "+24lC4plfbEDNfLAdTV/SWKS7dW+16X4HdydO3R++134kSNTzcbYA4KpR1Hdh6uWisB8Za3AzwyOn+K+NxWIug==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12" } }, "Microsoft.Extensions.Logging.Configuration": { @@ -752,8 +765,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.11", - "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" + "resolved": "10.0.12", + "contentHash": "dYfCLR52UA+3DL7C4I/pvSaRPkNqxrUAQmbFL2u0zvYKKzqgrFCJl08Df+F1aYc8leu9JvpC9bsURUdpExcBXQ==" }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", @@ -797,6 +810,11 @@ "System.CodeDom": "6.0.0" } }, + "System.Numerics.Tensors": { + "type": "Transitive", + "resolved": "10.0.12", + "contentHash": "17brFrnQHV/XMMazXNqFq4GkvzWy8yzDPo5Gb8TT8zJtMFbUqDycOCyPCp9F+eMc4L75kcCKRZqaeBHMwLCL9Q==" + }, "System.Reactive": { "type": "Transitive", "resolved": "6.0.0", @@ -850,25 +868,11 @@ "System.IO.Hashing": "8.0.0" } }, - "linq2graphql.testclientlegacy": { - "type": "Project", - "dependencies": { - "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", - "Microsoft.Extensions.Http": "[10.0.11, )", - "Websocket.Client": "[5.3.0, )" - } - }, - "linq2graphql.testclientsecond": { - "type": "Project", - "dependencies": { - "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", - "Microsoft.Extensions.Http": "[10.0.11, )", - "Websocket.Client": "[5.3.0, )" - } - }, "linq2graphql.testclientstandalone": { "type": "Project", "dependencies": { + "GraphQL-Parser": "[9.5.1, )", + "Microsoft.Extensions.AI.Abstractions": "[10.10.0, )", "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", "Microsoft.Extensions.Http": "[10.0.11, )", "Websocket.Client": "[5.3.0, )" @@ -890,6 +894,12 @@ "HotChocolate.Data": "[16.6.4, )" } }, + "GraphQL-Parser": { + "type": "CentralTransitive", + "requested": "[9.5.1, )", + "resolved": "9.5.1", + "contentHash": "pUYpupTsVyNcKemko8f0iknJa2z/oZsfzwb4crgEFN9/pKf3k35gxVXDDOkD3NVPF1ea6GUV0jgxkjCozFj7Fg==" + }, "HotChocolate.AspNetCore": { "type": "CentralTransitive", "requested": "[16.6.4, )", @@ -927,6 +937,12 @@ "resolved": "16.6.4", "contentHash": "bigjWuAkEQvxoQCrUzAvowiqS7YrTciape6++hB9YpA2cFH5RQnG+xyTo/MvWCN5jSvnW4izvdae9mhxtfhZZA==" }, + "Microsoft.Extensions.AI.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.10.0, )", + "resolved": "10.10.0", + "contentHash": "frBHj2ckpevA+KdDLikxv8oxGPrJiyTvaiblYp+vtVEt0q6J73XP5m2SICm86nFK6rl+L1GCKxIy4S5Lb3p2kQ==" + }, "Microsoft.Extensions.Caching.Memory": { "type": "CentralTransitive", "requested": "[10.0.11, )", diff --git a/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs b/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs index d4dc64b1..c0c05efc 100644 --- a/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs +++ b/test/Linq2GraphQL.Tests/StandaloneGeneratorTests.cs @@ -49,6 +49,29 @@ public void Standalone_WithoutSubscriptions_LeavesOutTheSubscriptionsRuntime() Content(entries, "Runtime", "RuntimeInfo.cs").ShouldNotContain("Websocket.Client"); } + [Fact] + public void Standalone_InAgentMode_CopiesTheAgentTools() + { + var entries = NewGenerator(standalone: true, includeSubscriptions: false, agent: true).GenerateFromSdl(Sdl); + + Content(entries, "Runtime/AI", "AgentToolsExtensions.cs").ShouldContain("namespace TestNs.Runtime.AI;"); + Content(entries, "Runtime/AI/Schema", "SdlReader.cs").ShouldContain("namespace TestNs.Runtime.AI.Schema;"); + entries.ShouldAllBe(e => !e.Content.Contains("Linq2GraphQL.Client")); + + var info = Content(entries, "Runtime", "RuntimeInfo.cs"); + info.ShouldContain("GraphQL-Parser"); + info.ShouldContain("Microsoft.Extensions.AI.Abstractions"); + } + + [Fact] + public void Standalone_WithoutAgentMode_LeavesOutTheAgentTools() + { + var entries = NewGenerator(standalone: true, includeSubscriptions: true).GenerateFromSdl(Sdl); + + entries.ShouldNotContain(e => e.DirectoryName.StartsWith("Runtime/AI")); + Content(entries, "Runtime", "RuntimeInfo.cs").ShouldNotContain("Microsoft.Extensions.AI"); + } + [Fact] public void Standalone_RuntimeInfo_RecordsVersionAndPackages() { @@ -78,9 +101,9 @@ public void PackageMode_IsUnchangedByStandalone() } } - private static ClientGenerator NewGenerator(bool standalone, bool includeSubscriptions) => + private static ClientGenerator NewGenerator(bool standalone, bool includeSubscriptions, bool agent = false) => new("TestNs", "TestClient", includeSubscriptions, EnumGeneratorStrategy.FailIfMissing, false, false, - standalone: standalone); + agent: agent, standalone: standalone); private static string Content(List entries, string directory, string fileName) => entries.Single(e => e.DirectoryName == directory && e.FileName == fileName).Content; diff --git a/test/Linq2GraphQL.Tests/packages.lock.json b/test/Linq2GraphQL.Tests/packages.lock.json index 52191f54..8c334c70 100644 --- a/test/Linq2GraphQL.Tests/packages.lock.json +++ b/test/Linq2GraphQL.Tests/packages.lock.json @@ -929,6 +929,8 @@ "linq2graphql.testclientstandalone": { "type": "Project", "dependencies": { + "GraphQL-Parser": "[9.5.1, )", + "Microsoft.Extensions.AI.Abstractions": "[10.10.0, )", "Microsoft.Extensions.Caching.Memory": "[10.0.11, )", "Microsoft.Extensions.Http": "[10.0.11, )", "Websocket.Client": "[5.3.0, )"