diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/StringReferenceExtractor.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/StringReferenceExtractor.cs index 24452831d..bd8f2a173 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/StringReferenceExtractor.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/StringReferenceExtractor.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public sealed class StringReferenceExtractor { - private readonly List contentsPatterns = new List(); + private readonly List contentsPatterns = new List(); private readonly List assetsPatterns = new List(); public StringReferenceExtractor(IUrlGenerator urlGenerator) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs index e467f5abc..8d0bc1f55 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs @@ -47,11 +47,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL this.options = options.Value; } - public async Task ConfigureAsync(ExecutionOptions executionOptions) + public async Task ExecuteAsync(ExecutionOptions options, ExecutionDelegate next) { - var context = ((GraphQLExecutionContext)executionOptions.UserContext!).Context; + var context = ((GraphQLExecutionContext)options.UserContext!).Context; - executionOptions.Schema = await GetSchemaAsync(context.App); + options.Schema = await GetSchemaAsync(context.App); + + return await next(options); } public async Task GetSchemaAsync(IAppEntity app) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index 170ed5728..17f1e7a3f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private IDataLoader GetAssetsLoader() { - return dataLoaders.Context.GetOrAddBatchLoader(nameof(GetAssetsLoader), + return dataLoaders.Context!.GetOrAddBatchLoader(nameof(GetAssetsLoader), async (batch, ct) => { var result = await GetReferencedAssetsAsync(new List(batch), ct); @@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private IDataLoader GetContentsLoader() { - return dataLoaders.Context.GetOrAddBatchLoader(nameof(GetContentsLoader), + return dataLoaders.Context!.GetOrAddBatchLoader(nameof(GetContentsLoader), async (batch, ct) => { var result = await GetReferencedContentsAsync(new List(batch), ct); @@ -173,7 +173,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private IDataLoader GetUserLoader() { - return dataLoaders.Context.GetOrAddBatchLoader(nameof(GetUserLoader), + return dataLoaders.Context!.GetOrAddBatchLoader(nameof(GetUserLoader), async (batch, ct) => { var result = await Resolve().QueryManyAsync(batch.ToArray(), ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs index ec7787237..b5e61c336 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types private readonly Dictionary componentTypes = new Dictionary(ReferenceEqualityComparer.Instance); private readonly Dictionary contentTypes = new Dictionary(ReferenceEqualityComparer.Instance); private readonly Dictionary contentResultTypes = new Dictionary(ReferenceEqualityComparer.Instance); - private readonly Dictionary embeddableStringTypes = new Dictionary(); + private readonly Dictionary embeddableStringTypes = new Dictionary(); private readonly Dictionary enumTypes = new Dictionary(); private readonly FieldVisitor fieldVisitor; private readonly FieldInputVisitor fieldInputVisitor; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs index 1b54fa289..962c6f761 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents } }; - public static readonly ValueResolver Resolver = (value, fieldContext, context) => + public static readonly ValueResolver Resolver = (value, fieldContext, context) => { if (fieldContext.Arguments != null && fieldContext.Arguments.TryGetValue("path", out var contextValue) && diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs index 4445f3392..08cd67d6c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents foreach (var value in values) { - AddValue(value, null, value); + Add(value, value, value); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs index 1b6f9177c..81f946ade 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs @@ -17,7 +17,9 @@ using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { - public delegate object ValueResolver(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context); + public delegate T ValueResolver(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context); + + public delegate Task AsyncValueResolver(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context); internal sealed class FieldVisitor : IFieldVisitor { @@ -79,14 +81,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents } }); - private static readonly IFieldResolver Assets = CreateValueResolver((value, fieldContext, context) => + private static readonly IFieldResolver Assets = CreateAsyncValueResolver((value, fieldContext, context) => { var cacheDuration = fieldContext.CacheDuration(); return context.GetReferencedAssetsAsync(value, cacheDuration, fieldContext.CancellationToken); }); - private static readonly IFieldResolver References = CreateValueResolver((value, fieldContext, context) => + private static readonly IFieldResolver References = CreateAsyncValueResolver((value, fieldContext, context) => { var cacheDuration = fieldContext.CacheDuration(); @@ -274,7 +276,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents return componentType; } - private static IFieldResolver CreateValueResolver(ValueResolver valueResolver) + private static IFieldResolver CreateValueResolver(ValueResolver valueResolver) { return Resolvers.Sync, object?>((source, fieldContext, context) => { @@ -293,5 +295,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents return null; }); } + + private static IFieldResolver CreateAsyncValueResolver(AsyncValueResolver valueResolver) + { + return Resolvers.Async, object?>(async (source, fieldContext, context) => + { + var key = fieldContext.FieldDefinition.SourceName(); + + if (source.TryGetValue(key, out var value)) + { + if (value is JsonNull) + { + return null; + } + + return await valueResolver(value, fieldContext, context); + } + + return null; + }); + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Directives/CacheDirective.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Directives/CacheDirective.cs index 1b2d5dced..28e6ee40a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Directives/CacheDirective.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Directives/CacheDirective.cs @@ -6,10 +6,11 @@ // ========================================================================== using GraphQL.Types; +using GraphQLParser.AST; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives { - public sealed class CacheDirective : DirectiveGraphType + public sealed class CacheDirective : Directive { public CacheDirective() : base("cache", DirectiveLocation.Field, DirectiveLocation.FragmentSpread, DirectiveLocation.InlineFragment) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs index 338b7a7ff..5b0253031 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL.Language.AST; using GraphQL.Types; +using GraphQLParser.AST; using NodaTime.Text; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives @@ -23,11 +23,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives return InstantPattern.ExtendedIso.Parse(value?.ToString()!).Value; } - public override object? ParseLiteral(IValue value) + public override object? ParseLiteral(GraphQLValue value) { switch (value) { - case StringValue stringValue: + case GraphQLStringValue stringValue: return ParseValue(stringValue.Value); default: return null; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs index 5fd325356..14baf542c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs @@ -5,7 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL.Language.AST; +using System.Globalization; +using GraphQLParser.AST; using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives @@ -22,23 +23,50 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives return ParseJson(value); } - public static IJsonValue ParseJson(object? value) + public static IJsonValue ParseJson(object? input) { - switch (value) + switch (input) { - case ListValue listValue: - return ParseJson(listValue.Value); + case GraphQLBooleanValue booleanValue: + return JsonValue.Create(booleanValue.BoolValue); - case ObjectValue objectValue: - return ParseJson(objectValue.Value); + case GraphQLFloatValue floatValue: + return JsonValue.Create(double.Parse((string)floatValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture)); - case IReadOnlyDictionary dictionary: + case GraphQLIntValue intValue: + return JsonValue.Create(int.Parse((string)intValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture)); + + case GraphQLNullValue: + return JsonValue.Null; + + case GraphQLStringValue stringValue: + return JsonValue.Create((string)stringValue.Value); + + case GraphQLListValue listValue: + { + var json = JsonValue.Array(); + + if (listValue.Values != null) + { + foreach (var item in listValue.Values) + { + json.Add(ParseJson(item)); + } + } + + return json; + } + + case GraphQLObjectValue objectValue: { var json = JsonValue.Object(); - foreach (var (key, inner) in dictionary) + if (objectValue.Fields != null) { - json[key] = ParseJson(inner); + foreach (var field in objectValue.Fields) + { + json[field.Name.ToString()] = ParseJson(field.Value); + } } return json; @@ -46,22 +74,34 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives case IEnumerable list: { - var array = JsonValue.Array(); + var json = JsonValue.Array(); foreach (var item in list) { - array.Add(ParseJson(item)); + json.Add(ParseJson(item)); } - return array; + return json; + } + + case IDictionary obj: + { + var json = JsonValue.Object(); + + foreach (var (key, value) in obj) + { + json[key] = ParseJson(value); + } + + return json; } default: - return JsonValue.Create(value); + return JsonValue.Create(input); } } - public override object ParseLiteral(IValue value) + public override object ParseLiteral(GraphQLValue value) { if (value is JsonValueNode jsonGraphType) { @@ -71,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives return value; } - public override IValue ToAST(object? value) + public override GraphQLValue ToAST(object? value) { return new JsonValueNode(ParseJson(value)); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonNoopGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonNoopGraphType.cs index 820812e93..f44e4a6fa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonNoopGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonNoopGraphType.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL.Language.AST; using GraphQL.Types; +using GraphQLParser.AST; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives { @@ -20,9 +20,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives Description = "Unstructured Json object"; } - public override object? ParseLiteral(IValue value) + public override object? ParseLiteral(GraphQLValue value) { - return value.Value; + return value; } public override object? ParseValue(object? value) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs index 47affa607..1ff8e5b9c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs @@ -5,16 +5,21 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL.Language.AST; +using GraphQLParser; +using GraphQLParser.AST; using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives { - internal sealed class JsonValueNode : ValueNode + internal sealed class JsonValueNode : GraphQLValue { + public override ASTNodeKind Kind => ASTNodeKind.ObjectValue; + + public IJsonValue Value { get; } + public JsonValueNode(IJsonValue value) - : base(value) { + Value = value; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Resolvers.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Resolvers.cs index 621bd4724..535427e39 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Resolvers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Resolvers.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return new AsyncResolver(resolver); } - private sealed class SyncResolver : IFieldResolver, IFieldResolver + private sealed class SyncResolver : IFieldResolver { private readonly Func resolver; @@ -44,13 +44,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types this.resolver = resolver; } - public T Resolve(IResolveFieldContext context) + public ValueTask ResolveAsync(IResolveFieldContext context) { var executionContext = (GraphQLExecutionContext)context.UserContext!; try { - return resolver((TSource)context.Source!, context, executionContext); + var result = resolver((TSource)context.Source!, context, executionContext); + + return new ValueTask(result); } catch (ValidationException ex) { @@ -68,14 +70,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types throw; } } - - object IFieldResolver.Resolve(IResolveFieldContext context) - { - return Resolve(context)!; - } } - private sealed class AsyncResolver : IFieldResolver>, IFieldResolver + private sealed class AsyncResolver : IFieldResolver { private readonly Func> resolver; @@ -84,13 +81,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types this.resolver = resolver; } - public async Task Resolve(IResolveFieldContext context) + public async ValueTask ResolveAsync(IResolveFieldContext context) { var executionContext = (GraphQLExecutionContext)context.UserContext!; try { - return await resolver((TSource)context.Source!, context, executionContext); + var result = await resolver((TSource)context.Source!, context, executionContext); + + return result; } catch (ValidationException ex) { @@ -108,11 +107,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types throw; } } - - object IFieldResolver.Resolve(IResolveFieldContext context) - { - return Resolve(context)!; - } } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs index 2b50ce5c3..c6b6043dc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs @@ -6,7 +6,6 @@ // ========================================================================== using GraphQL; -using GraphQL.Language.AST; using GraphQL.Types; using GraphQL.Utilities; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; @@ -127,16 +126,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types public static TimeSpan CacheDuration(this IResolveFieldContext context) { - var cacheDirective = context.FieldAst.Directives?.Find("cache"); + var cacheDirective = context.GetDirective("cache"); if (cacheDirective != null) { - var duration = cacheDirective.Arguments?.ValueFor("duration"); + var duration = cacheDirective.GetArgument("duration"); - if (duration is IntValue value && value.Value > 0) - { - return TimeSpan.FromSeconds(value.Value); - } + return TimeSpan.FromSeconds(duration); } return TimeSpan.Zero; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 6fa338409..10e83dbbd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -20,8 +20,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/src/Squidex.Web/GraphQL/BufferingDocumentWriter.cs b/backend/src/Squidex.Web/GraphQL/BufferingDocumentWriter.cs deleted file mode 100644 index 1ec945504..000000000 --- a/backend/src/Squidex.Web/GraphQL/BufferingDocumentWriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using GraphQL; -using GraphQL.NewtonsoftJson; -using Microsoft.AspNetCore.WebUtilities; -using Newtonsoft.Json; - -namespace Squidex.Web.GraphQL -{ - public sealed class BufferingDocumentWriter : IDocumentWriter - { - private readonly DocumentWriter documentWriter; - - public BufferingDocumentWriter(Action action) - { - documentWriter = new DocumentWriter(action); - } - - public async Task WriteAsync(Stream stream, T value, - CancellationToken cancellationToken = default) - { - await using (var bufferStream = new FileBufferingWriteStream()) - { - await documentWriter.WriteAsync(bufferStream, value, cancellationToken); - - await bufferStream.DrainBufferAsync(stream, cancellationToken); - } - } - } -} diff --git a/backend/src/Squidex.Web/GraphQL/BufferingGraphQLSerializer.cs b/backend/src/Squidex.Web/GraphQL/BufferingGraphQLSerializer.cs new file mode 100644 index 000000000..cf1d14793 --- /dev/null +++ b/backend/src/Squidex.Web/GraphQL/BufferingGraphQLSerializer.cs @@ -0,0 +1,61 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL; +using Microsoft.AspNetCore.WebUtilities; + +namespace Squidex.Web.GraphQL +{ + public sealed class BufferingGraphQLSerializer : IGraphQLTextSerializer + { + private readonly IGraphQLTextSerializer inner; + + public BufferingGraphQLSerializer(IGraphQLTextSerializer inner) + { + this.inner = inner; + } + + public async ValueTask ReadAsync(Stream stream, + CancellationToken cancellationToken = default) + { + await using (var bufferStream = new FileBufferingReadStream(stream, 30 * 1024)) + { + await bufferStream.DrainAsync(cancellationToken); + + bufferStream.Seek(0L, SeekOrigin.Begin); + + return await inner.ReadAsync(bufferStream, cancellationToken); + } + } + + public async Task WriteAsync(Stream stream, T? value, + CancellationToken cancellationToken = default) + { + await using (var bufferStream = new FileBufferingWriteStream()) + { + await inner.WriteAsync(bufferStream, value, cancellationToken); + + await bufferStream.DrainBufferAsync(stream, cancellationToken); + } + } + + public string Serialize(T? value) + { + return inner.Serialize(value); + } + + public T? Deserialize(string? value) + { + return inner.Deserialize(value); + } + + public T? ReadNode(object? value) + { + return inner.ReadNode(value); + } + } +} diff --git a/backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs b/backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs index a9987ef07..374bbb17e 100644 --- a/backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs +++ b/backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL; using GraphQL.Server.Transports.AspNetCore; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -20,8 +19,6 @@ namespace Squidex.Web.GraphQL public Task> BuildUserContext(HttpContext httpContext) { - var x = httpContext.RequestServices.GetRequiredService(); - var executionContext = (GraphQLExecutionContext)factory(httpContext.RequestServices, new object[] { httpContext.Context() }); return Task.FromResult>(executionContext); diff --git a/backend/src/Squidex.Web/GraphQL/GraphQLRunner.cs b/backend/src/Squidex.Web/GraphQL/GraphQLRunner.cs index 9c27b6f64..d8897df5b 100644 --- a/backend/src/Squidex.Web/GraphQL/GraphQLRunner.cs +++ b/backend/src/Squidex.Web/GraphQL/GraphQLRunner.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using GraphQL; using GraphQL.Server.Transports.AspNetCore; using Microsoft.AspNetCore.Http; @@ -14,14 +15,14 @@ namespace Squidex.Web.GraphQL { private readonly GraphQLHttpMiddleware middleware; - public GraphQLRunner(IGraphQLRequestDeserializer deserializer) + public GraphQLRunner(IGraphQLTextSerializer deserializer) { - middleware = new GraphQLHttpMiddleware(x => Task.CompletedTask, deserializer); + middleware = new GraphQLHttpMiddleware(deserializer); } public Task InvokeAsync(HttpContext context) { - return middleware.InvokeAsync(context); + return middleware.InvokeAsync(context, x => Task.CompletedTask); } } } diff --git a/backend/src/Squidex.Web/Services/UrlGenerator.cs b/backend/src/Squidex.Web/Services/UrlGenerator.cs index 5f2941975..260f39816 100644 --- a/backend/src/Squidex.Web/Services/UrlGenerator.cs +++ b/backend/src/Squidex.Web/Services/UrlGenerator.cs @@ -44,7 +44,7 @@ namespace Squidex.Web.Services public string AssetContentCDNBase() { - return contentOptions.CDN ?? string.Empty; + return assetOptions.CDN ?? string.Empty; } public string AssetContentBase() diff --git a/backend/src/Squidex.Web/Squidex.Web.csproj b/backend/src/Squidex.Web/Squidex.Web.csproj index 8cb7249a1..58c5df8f8 100644 --- a/backend/src/Squidex.Web/Squidex.Web.csproj +++ b/backend/src/Squidex.Web/Squidex.Web.csproj @@ -17,8 +17,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs index 466563a38..3fe76d62d 100644 --- a/backend/src/Squidex/Config/Domain/SerializationServices.cs +++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs @@ -6,10 +6,6 @@ // ========================================================================== using System.Security.Claims; -using GraphQL; -using GraphQL.Execution; -using GraphQL.NewtonsoftJson; -using GraphQL.Server; using Migrations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -30,8 +26,6 @@ using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Reflection; -using Squidex.Web.GraphQL; -using IGraphQLBuilder = GraphQL.DI.IGraphQLBuilder; namespace Squidex.Config.Domain { @@ -122,23 +116,5 @@ namespace Squidex.Config.Domain return builder; } - - public static IGraphQLBuilder AddSquidexJson(this IGraphQLBuilder builder) - { - builder.AddDocumentWriter(c => - { - var errorInfoProvider = c.GetRequiredService(); - - return new BufferingDocumentWriter(options => - { - options.ContractResolver = new ExecutionResultContractResolver(errorInfoProvider); - - options.Converters.Add(new JsonValueConverter()); - options.Converters.Add(new WriteonlyGeoJsonConverter()); - }); - }); - - return builder; - } } } diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs index ab62e64bc..3ccbcf83b 100644 --- a/backend/src/Squidex/Config/Web/WebServices.cs +++ b/backend/src/Squidex/Config/Web/WebServices.cs @@ -8,6 +8,9 @@ using GraphQL; using GraphQL.DataLoader; using GraphQL.DI; +using GraphQL.Execution; +using GraphQL.MicrosoftDI; +using GraphQL.NewtonsoftJson; using GraphQL.Server; using GraphQL.Server.Transports.AspNetCore; using Microsoft.AspNetCore.Mvc; @@ -18,6 +21,7 @@ using Squidex.Config.Domain; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Infrastructure.Caching; +using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Pipeline.Plugins; using Squidex.Web; using Squidex.Web.GraphQL; @@ -87,15 +91,13 @@ namespace Squidex.Config.Web public static void AddSquidexGraphQL(this IServiceCollection services) { - GraphQL.MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services) - .AddServer(false, options => - { - options.EnableMetrics = false; - }) - .AddSchema() - .AddSystemTextJson() - .AddSquidexJson() // Use Newtonsoft.JSON for custom converters. - .AddDataLoader(); + services.AddGraphQL(builder => + { + builder.AddApolloTracing(); + builder.AddSchema(); + builder.AddSquidexJson(); // Use Newtonsoft.JSON for custom converters. + builder.AddDataLoader(); + }); services.AddSingletonAs() .AsSelf(); @@ -109,5 +111,21 @@ namespace Squidex.Config.Web services.AddSingletonAs() .AsSelf(); } + + private static IGraphQLBuilder AddSquidexJson(this IGraphQLBuilder builder) + { + builder.AddSerializer(c => + { + var errorInfoProvider = c.GetRequiredService(); + + return new BufferingGraphQLSerializer(new GraphQLSerializer(options => + { + options.Converters.Add(new JsonValueConverter()); + options.Converters.Add(new WriteonlyGeoJsonConverter()); + })); + }); + + return builder; + } } } diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index 0103363e5..1e9658992 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -35,11 +35,8 @@ - - - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs index 1adf9e01e..239f75eb9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs @@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var result = await ExecuteAsync(new ExecutionOptions { Query = query, OperationName = "IntrospectionQuery" }); - var json = serializer.Serialize(result, true); + var json = serializer.Serialize(result); Assert.NotEmpty(json); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index 120638005..c79c1f104 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -156,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(content); - var result = await ExecuteAsync(new ExecutionOptions { Query = query, Inputs = GetInput() }, Permissions.AppContentsCreate); + var result = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, Permissions.AppContentsCreate); var expected = new { @@ -264,7 +264,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(content); - var result = await ExecuteAsync(new ExecutionOptions { Query = query, Inputs = GetInput() }, Permissions.AppContentsUpdateOwn); + var result = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, Permissions.AppContentsUpdateOwn); var expected = new { @@ -373,7 +373,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(content); - var result = await ExecuteAsync(new ExecutionOptions { Query = query, Inputs = GetInput() }, Permissions.AppContentsUpsert); + var result = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, Permissions.AppContentsUpsert); var expected = new { @@ -482,7 +482,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(content); - var result = await ExecuteAsync(new ExecutionOptions { Query = query, Inputs = GetInput() }, Permissions.AppContentsUpdateOwn); + var result = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, Permissions.AppContentsUpdateOwn); var expected = new { @@ -763,7 +763,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) }; - return JObject.FromObject(input).ToInputs(); + return serializer.ReadNode(JObject.FromObject(input))!; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index ed0f2f350..3cb347087 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -25,7 +25,7 @@ using Squidex.Domain.Apps.Entities.Contents.TestData; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Shared; using Squidex.Shared.Users; using Xunit; @@ -36,9 +36,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public class GraphQLTestBase : IClassFixture { - protected readonly IJsonSerializer serializer = - TestUtils.CreateSerializer(TypeNameHandling.None, - new ExecutionResultJsonConverter(new ErrorInfoProvider())); + protected readonly GraphQLSerializer serializer = new GraphQLSerializer(options => + { + options.Formatting = Formatting.Indented; + options.Converters.Add(new JsonValueConverter()); + options.Converters.Add(new WriteonlyGeoJsonConverter()); + }); + protected readonly IAssetQueryService assetQuery = A.Fake(); protected readonly ICommandBus commandBus = A.Fake(); protected readonly IContentQueryService contentQuery = A.Fake(); @@ -61,12 +65,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL requestContext = new Context(Mocks.FrontendUser(), TestApp.Default); } - protected void AssertResult(object lhs, ExecutionResult result) + protected void AssertResult(object expected, ExecutionResult result) { - var rhsJson = serializer.Serialize(result, true); - var lhsJson = serializer.Serialize(lhs, true); + var jsonOutputResult = serializer.Serialize(result); + var isonOutputExpected = serializer.Serialize(expected); - Assert.Equal(lhsJson, rhsJson); + Assert.Equal(isonOutputExpected, jsonOutputResult); } protected Task ExecuteAsync(ExecutionOptions options, string? permissionId = null) @@ -94,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL options.Listeners.Add(listener); } - await sut.ConfigureAsync(options); + await sut.ExecuteAsync(options, x => Task.FromResult(null!)); return await new DocumentExecuter().ExecuteAsync(options); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index 1a84d2c12..112f39c0d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -22,8 +22,8 @@ - - + + all