Browse Source

Update graphql. (#880)

* Update graphql.

* Fix reading.
pull/881/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
2cfa0c7e47
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs
  2. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs
  5. 30
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs
  6. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Directives/CacheDirective.cs
  7. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs
  8. 72
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs
  9. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonNoopGraphType.cs
  10. 11
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs
  11. 26
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Resolvers.cs
  12. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs
  13. 4
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  14. 35
      backend/src/Squidex.Web/GraphQL/BufferingDocumentWriter.cs
  15. 61
      backend/src/Squidex.Web/GraphQL/BufferingGraphQLSerializer.cs
  16. 3
      backend/src/Squidex.Web/GraphQL/DynamicUserContextBuilder.cs
  17. 7
      backend/src/Squidex.Web/GraphQL/GraphQLRunner.cs
  18. 2
      backend/src/Squidex.Web/Services/UrlGenerator.cs
  19. 5
      backend/src/Squidex.Web/Squidex.Web.csproj
  20. 24
      backend/src/Squidex/Config/Domain/SerializationServices.cs
  21. 34
      backend/src/Squidex/Config/Web/WebServices.cs
  22. 7
      backend/src/Squidex/Squidex.csproj
  23. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs
  24. 10
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  25. 22
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  26. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

8
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<ExecutionResult> 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<GraphQLSchema> GetSchemaAsync(IAppEntity app)

6
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private IDataLoader<DomainId, IEnrichedAssetEntity> GetAssetsLoader()
{
return dataLoaders.Context.GetOrAddBatchLoader<DomainId, IEnrichedAssetEntity>(nameof(GetAssetsLoader),
return dataLoaders.Context!.GetOrAddBatchLoader<DomainId, IEnrichedAssetEntity>(nameof(GetAssetsLoader),
async (batch, ct) =>
{
var result = await GetReferencedAssetsAsync(new List<DomainId>(batch), ct);
@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private IDataLoader<DomainId, IEnrichedContentEntity> GetContentsLoader()
{
return dataLoaders.Context.GetOrAddBatchLoader<DomainId, IEnrichedContentEntity>(nameof(GetContentsLoader),
return dataLoaders.Context!.GetOrAddBatchLoader<DomainId, IEnrichedContentEntity>(nameof(GetContentsLoader),
async (batch, ct) =>
{
var result = await GetReferencedContentsAsync(new List<DomainId>(batch), ct);
@ -173,7 +173,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private IDataLoader<string, IUser> GetUserLoader()
{
return dataLoaders.Context.GetOrAddBatchLoader<string, IUser>(nameof(GetUserLoader),
return dataLoaders.Context!.GetOrAddBatchLoader<string, IUser>(nameof(GetUserLoader),
async (batch, ct) =>
{
var result = await Resolve<IUserResolver>().QueryManyAsync(batch.ToArray(), ct);

2
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<object> Resolver = (value, fieldContext, context) =>
{
if (fieldContext.Arguments != null &&
fieldContext.Arguments.TryGetValue("path", out var contextValue) &&

2
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);
}
}

30
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<T>(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
public delegate Task<T> AsyncValueResolver<T>(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
internal sealed class FieldVisitor : IFieldVisitor<FieldGraphSchema, FieldInfo>
{
@ -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<T>(ValueResolver<T> valueResolver)
{
return Resolvers.Sync<IReadOnlyDictionary<string, IJsonValue>, object?>((source, fieldContext, context) =>
{
@ -293,5 +295,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return null;
});
}
private static IFieldResolver CreateAsyncValueResolver<T>(AsyncValueResolver<T> valueResolver)
{
return Resolvers.Async<IReadOnlyDictionary<string, IJsonValue>, 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;
});
}
}
}

3
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)

6
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;

72
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<string, object> 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<object> list:
{
var array = JsonValue.Array();
var json = JsonValue.Array();
foreach (var item in list)
{
array.Add(ParseJson(item));
json.Add(ParseJson(item));
}
return json;
}
case IDictionary<string, object> obj:
{
var json = JsonValue.Object();
foreach (var (key, value) in obj)
{
json[key] = ParseJson(value);
}
return array;
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));
}

6
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)

11
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<IJsonValue>
internal sealed class JsonValueNode : GraphQLValue
{
public override ASTNodeKind Kind => ASTNodeKind.ObjectValue;
public IJsonValue Value { get; }
public JsonValueNode(IJsonValue value)
: base(value)
{
Value = value;
}
}
}

26
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<TSource, T>(resolver);
}
private sealed class SyncResolver<TSource, T> : IFieldResolver<T>, IFieldResolver
private sealed class SyncResolver<TSource, T> : IFieldResolver
{
private readonly Func<TSource, IResolveFieldContext, GraphQLExecutionContext, T> resolver;
@ -44,13 +44,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
this.resolver = resolver;
}
public T Resolve(IResolveFieldContext context)
public ValueTask<object?> 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<object?>(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<TSource, T> : IFieldResolver<Task<T>>, IFieldResolver
private sealed class AsyncResolver<TSource, T> : IFieldResolver
{
private readonly Func<TSource, IResolveFieldContext, GraphQLExecutionContext, Task<T>> resolver;
@ -84,13 +81,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
this.resolver = resolver;
}
public async Task<T> Resolve(IResolveFieldContext context)
public async ValueTask<object?> 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)!;
}
}
}
}

10
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<int>("duration");
if (duration is IntValue value && value.Value > 0)
{
return TimeSpan.FromSeconds(value.Value);
}
return TimeSpan.FromSeconds(duration);
}
return TimeSpan.Zero;

4
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -20,8 +20,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="27.2.1" />
<PackageReference Include="GraphQL" Version="4.7.1" />
<PackageReference Include="GraphQL.DataLoader" Version="4.7.1" />
<PackageReference Include="GraphQL" Version="5.3.0" />
<PackageReference Include="GraphQL.DataLoader" Version="5.3.0" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.694">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

35
backend/src/Squidex.Web/GraphQL/BufferingDocumentWriter.cs

@ -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<JsonSerializerSettings> action)
{
documentWriter = new DocumentWriter(action);
}
public async Task WriteAsync<T>(Stream stream, T value,
CancellationToken cancellationToken = default)
{
await using (var bufferStream = new FileBufferingWriteStream())
{
await documentWriter.WriteAsync(bufferStream, value, cancellationToken);
await bufferStream.DrainBufferAsync(stream, cancellationToken);
}
}
}
}

61
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<T?> ReadAsync<T>(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<T>(bufferStream, cancellationToken);
}
}
public async Task WriteAsync<T>(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>(T? value)
{
return inner.Serialize<T>(value);
}
public T? Deserialize<T>(string? value)
{
return inner.Deserialize<T>(value);
}
public T? ReadNode<T>(object? value)
{
return inner.ReadNode<T>(value);
}
}
}

3
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<IDictionary<string, object>> BuildUserContext(HttpContext httpContext)
{
var x = httpContext.RequestServices.GetRequiredService<IDocumentWriter>();
var executionContext = (GraphQLExecutionContext)factory(httpContext.RequestServices, new object[] { httpContext.Context() });
return Task.FromResult<IDictionary<string, object>>(executionContext);

7
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<DummySchema> middleware;
public GraphQLRunner(IGraphQLRequestDeserializer deserializer)
public GraphQLRunner(IGraphQLTextSerializer deserializer)
{
middleware = new GraphQLHttpMiddleware<DummySchema>(x => Task.CompletedTask, deserializer);
middleware = new GraphQLHttpMiddleware<DummySchema>(deserializer);
}
public Task InvokeAsync(HttpContext context)
{
return middleware.InvokeAsync(context);
return middleware.InvokeAsync(context, x => Task.CompletedTask);
}
}
}

2
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()

5
backend/src/Squidex.Web/Squidex.Web.csproj

@ -17,8 +17,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="GraphQL.NewtonsoftJson" Version="4.7.1" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="5.2.0" />
<PackageReference Include="GraphQL" Version="5.3.0" />
<PackageReference Include="GraphQL.NewtonsoftJson" Version="5.3.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="6.1.0" />
<PackageReference Include="Lazy.Fody" Version="1.11.0" PrivateAssets="all" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.694">
<PrivateAssets>all</PrivateAssets>

24
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<IErrorInfoProvider>();
return new BufferingDocumentWriter(options =>
{
options.ContractResolver = new ExecutionResultContractResolver(errorInfoProvider);
options.Converters.Add(new JsonValueConverter());
options.Converters.Add(new WriteonlyGeoJsonConverter());
});
});
return builder;
}
}
}

34
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 =>
services.AddGraphQL(builder =>
{
options.EnableMetrics = false;
})
.AddSchema<DummySchema>()
.AddSystemTextJson()
.AddSquidexJson() // Use Newtonsoft.JSON for custom converters.
.AddDataLoader();
builder.AddApolloTracing();
builder.AddSchema<DummySchema>();
builder.AddSquidexJson(); // Use Newtonsoft.JSON for custom converters.
builder.AddDataLoader();
});
services.AddSingletonAs<DummySchema>()
.AsSelf();
@ -109,5 +111,21 @@ namespace Squidex.Config.Web
services.AddSingletonAs<GraphQLRunner>()
.AsSelf();
}
private static IGraphQLBuilder AddSquidexJson(this IGraphQLBuilder builder)
{
builder.AddSerializer(c =>
{
var errorInfoProvider = c.GetRequiredService<IErrorInfoProvider>();
return new BufferingGraphQLSerializer(new GraphQLSerializer(options =>
{
options.Converters.Add(new JsonValueConverter());
options.Converters.Add(new WriteonlyGeoJsonConverter());
}));
});
return builder;
}
}
}

7
backend/src/Squidex/Squidex.csproj

@ -35,11 +35,8 @@
<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="6.0.3" />
<PackageReference Include="GraphQL.DataLoader" Version="4.7.1" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="4.7.1" />
<PackageReference Include="GraphQL.Server.Core" Version="5.2.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.NewtonsoftJson" Version="5.2.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.SystemTextJson" Version="5.2.0" />
<PackageReference Include="GraphQL" Version="5.3.0" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="5.3.0" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.694">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

2
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);
}

10
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<Inputs>(JObject.FromObject(input))!;
}
}
}

22
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<TranslationsFixture>
{
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<IAssetQueryService>();
protected readonly ICommandBus commandBus = A.Fake<ICommandBus>();
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
@ -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<ExecutionResult> 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<ExecutionResult>(null!));
return await new DocumentExecuter().ExecuteAsync(options);
}

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -22,8 +22,8 @@
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.0" />
<PackageReference Include="FluentAssertions" Version="6.4.0" />
<PackageReference Include="GraphQL" Version="4.7.1" />
<PackageReference Include="GraphQL.NewtonsoftJson" Version="4.7.1" />
<PackageReference Include="GraphQL" Version="5.3.0" />
<PackageReference Include="GraphQL.NewtonsoftJson" Version="5.3.0" />
<PackageReference Include="Lorem.Universal.Net" Version="4.0.80" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.694">
<PrivateAssets>all</PrivateAssets>

Loading…
Cancel
Save