Browse Source

Use Visitors for GraphQL

pull/296/head
Sebastian 8 years ago
parent
commit
7fbda4c4a8
  1. 191
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  2. 12
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs
  3. 1
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  4. 6
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs
  5. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs
  6. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs
  7. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs
  9. 20
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs
  10. 66
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs
  11. 115
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs
  12. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/NoopGraphType.cs

191
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -22,117 +22,49 @@ using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using GraphQLSchema = GraphQL.Types.Schema; using GraphQLSchema = GraphQL.Types.Schema;
#pragma warning disable IDE0003
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public sealed class GraphQLModel : IGraphModel public sealed class GraphQLModel : IGraphModel
{ {
private readonly Dictionary<Type, Func<IField, (IGraphType ResolveType, IFieldResolver Resolver)>> fieldInfos; private readonly QueryGraphTypeVisitor schemaTypes;
private readonly Dictionary<Type, IGraphType> inputFieldInfos;
private readonly Dictionary<ISchemaEntity, ContentGraphType> contentTypes = new Dictionary<ISchemaEntity, ContentGraphType>(); private readonly Dictionary<ISchemaEntity, ContentGraphType> contentTypes = new Dictionary<ISchemaEntity, ContentGraphType>();
private readonly Dictionary<ISchemaEntity, ContentDataGraphType> contentDataTypes = new Dictionary<ISchemaEntity, ContentDataGraphType>(); private readonly Dictionary<ISchemaEntity, ContentDataGraphType> contentDataTypes = new Dictionary<ISchemaEntity, ContentDataGraphType>();
private readonly Dictionary<Guid, ISchemaEntity> schemas; private readonly Dictionary<Guid, ISchemaEntity> schemasById;
private readonly PartitionResolver partitionResolver; private readonly PartitionResolver partitionResolver;
private readonly IAppEntity app; private readonly IAppEntity app;
private readonly IGraphType assetListType; private readonly IGraphType assetType;
private readonly IComplexGraphType assetType;
private readonly GraphQLSchema graphQLSchema; private readonly GraphQLSchema graphQLSchema;
public bool CanGenerateAssetSourceUrl { get; } public bool CanGenerateAssetSourceUrl { get; private set; }
public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, IGraphQLUrlGenerator urlGenerator) public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, IGraphQLUrlGenerator urlGenerator)
{ {
this.app = app; this.app = app;
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl;
partitionResolver = app.PartitionResolver(); partitionResolver = app.PartitionResolver();
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl;
assetType = new AssetGraphType(this); assetType = new AssetGraphType(this);
assetListType = new ListGraphType(new NonNullGraphType(assetType)); schemasById = schemas.ToDictionary(x => x.Id);
schemaTypes = new QueryGraphTypeVisitor(GetContentType, new ListGraphType(new NonNullGraphType(assetType)));
inputFieldInfos = new Dictionary<Type, IGraphType> graphQLSchema = BuildSchema(this);
{
{
typeof(StringFieldProperties),
AllTypes.String
},
{
typeof(BooleanFieldProperties),
AllTypes.Boolean
},
{
typeof(NumberFieldProperties),
AllTypes.Boolean
},
{
typeof(DateTimeFieldProperties),
AllTypes.Date
},
{
typeof(GeolocationFieldProperties),
AllTypes.GeolocationInput
},
{
typeof(TagsFieldProperties),
AllTypes.ListOfNonNullString
},
{
typeof(AssetsFieldProperties),
AllTypes.ListOfNonNullGuid
},
{
typeof(ReferencesFieldProperties),
AllTypes.ListOfNonNullGuid
}
};
fieldInfos = new Dictionary<Type, Func<IField, (IGraphType ResolveType, IFieldResolver Resolver)>> InitializeContentTypes();
{
{
typeof(StringFieldProperties),
field => ResolveDefault(AllTypes.NoopString)
},
{
typeof(BooleanFieldProperties),
field => ResolveDefault(AllTypes.NoopBoolean)
},
{
typeof(NumberFieldProperties),
field => ResolveDefault(AllTypes.NoopFloat)
},
{
typeof(DateTimeFieldProperties),
field => ResolveDefault(AllTypes.NoopDate)
},
{
typeof(JsonFieldProperties),
field => ResolveDefault(AllTypes.NoopJson)
},
{
typeof(GeolocationFieldProperties),
field => ResolveDefault(AllTypes.NoopGeolocation)
},
{
typeof(TagsFieldProperties),
field => ResolveDefault(AllTypes.NoopTags)
},
{
typeof(AssetsFieldProperties),
field => ResolveAssets(assetListType)
},
{
typeof(ReferencesFieldProperties),
field => ResolveReferences(field)
} }
};
this.schemas = schemas.ToDictionary(x => x.Id); private static GraphQLSchema BuildSchema(GraphQLModel model)
{
var m = new AppMutationsGraphType(this, this.schemas.Values); var schemas = model.schemasById.Values;
var q = new AppQueriesGraphType(this, this.schemas.Values);
graphQLSchema = new GraphQLSchema { Query = q, Mutation = m }; return new GraphQLSchema { Query = new AppQueriesGraphType(model, schemas), Mutation = new AppMutationsGraphType(model, schemas) };
}
private void InitializeContentTypes()
{
foreach (var kvp in contentDataTypes) foreach (var kvp in contentDataTypes)
{ {
kvp.Value.Initialize(this, kvp.Key); kvp.Value.Initialize(this, kvp.Key);
@ -197,77 +129,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return resolver; return resolver;
} }
private static ValueTuple<IGraphType, IFieldResolver> ResolveAssets(IGraphType assetListType) public IFieldPartitioning ResolvePartition(Partitioning key)
{
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{
var context = (GraphQLExecutionContext)c.UserContext;
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedAssetsAsync(contentIds);
});
return (assetListType, resolver);
}
private ValueTuple<IGraphType, IFieldResolver> ResolveReferences(IField field)
{
var schemaId = ((ReferencesFieldProperties)field.RawProperties).SchemaId;
var contentType = GetContentType(schemaId);
if (contentType == null)
{
return (null, null);
}
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{ {
var context = (GraphQLExecutionContext)c.UserContext; return partitionResolver(key);
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedContentsAsync(schemaId, contentIds);
});
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType));
return (schemaFieldType, resolver);
} }
public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field)
{
Guard.NotNull(context, nameof(context));
var result = await new DocumentExecuter().ExecuteAsync(options =>
{ {
options.Query = query.Query; return field.Accept(schemaTypes);
options.Schema = graphQLSchema;
options.Inputs = query.Variables?.ToInputs() ?? new Inputs();
options.UserContext = context;
options.OperationName = query.OperationName;
}).ConfigureAwait(false);
return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray());
} }
public IFieldPartitioning ResolvePartition(Partitioning key) public IGraphType GetInputGraphType(IField field)
{ {
return partitionResolver(key); return field.GetInputGraphType();
} }
public IComplexGraphType GetAssetType() public IGraphType GetAssetType()
{ {
return assetType; return assetType;
} }
public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field) public IGraphType GetContentDataType(Guid schemaId)
{ {
return fieldInfos[field.RawProperties.GetType()](field); var schema = schemasById.GetOrDefault(schemaId);
}
public IComplexGraphType GetContentDataType(Guid schemaId)
{
var schema = schemas.GetOrDefault(schemaId);
if (schema == null) if (schema == null)
{ {
@ -277,9 +161,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null; return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null;
} }
public IComplexGraphType GetContentType(Guid schemaId) public IGraphType GetContentType(Guid schemaId)
{ {
var schema = schemas.GetOrDefault(schemaId); var schema = schemasById.GetOrDefault(schemaId);
if (schema == null) if (schema == null)
{ {
@ -289,9 +173,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return contentTypes.GetOrAdd(schema, s => new ContentGraphType()); return contentTypes.GetOrAdd(schema, s => new ContentGraphType());
} }
public IGraphType GetInputGraphType(IField field) public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query)
{ {
return inputFieldInfos.GetOrAddDefault(field.RawProperties.GetType()); Guard.NotNull(context, nameof(context));
var result = await new DocumentExecuter().ExecuteAsync(options =>
{
options.Inputs = query.Variables?.ToInputs() ?? new Inputs();
options.Query = query.Query;
options.OperationName = query.OperationName;
options.Schema = graphQLSchema;
options.UserContext = context;
}).ConfigureAwait(false);
return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray());
} }
} }
} }

12
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs

@ -20,12 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
IFieldPartitioning ResolvePartition(Partitioning key); IFieldPartitioning ResolvePartition(Partitioning key);
IComplexGraphType GetAssetType();
IComplexGraphType GetContentType(Guid schemaId);
IComplexGraphType GetContentDataType(Guid schemaId);
IFieldResolver ResolveAssetUrl(); IFieldResolver ResolveAssetUrl();
IFieldResolver ResolveAssetSourceUrl(); IFieldResolver ResolveAssetSourceUrl();
@ -34,6 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
IFieldResolver ResolveContentUrl(ISchemaEntity schema); IFieldResolver ResolveContentUrl(ISchemaEntity schema);
IGraphType GetAssetType();
IGraphType GetContentType(Guid schemaId);
IGraphType GetContentDataType(Guid schemaId);
IGraphType GetInputGraphType(IField field); IGraphType GetInputGraphType(IField field);
(IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field); (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field);

1
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs

@ -8,6 +8,7 @@
using System; using System;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {

6
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = "The app mutations."; Description = "The app mutations.";
} }
private void AddContentCreate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType, IComplexGraphType contentType) private void AddContentCreate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType contentDataType, IGraphType contentType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType)
{ {
AddField(new FieldType AddField(new FieldType
{ {

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs

@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddAssetsQueries(IComplexGraphType assetType) private void AddAssetsQueries(IGraphType assetType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentQueries(Guid schemaId, string schemaType, string schemaName, IComplexGraphType contentType) private void AddContentQueries(Guid schemaId, string schemaType, string schemaName, IGraphType contentType)
{ {
AddField(new FieldType AddField(new FieldType
{ {

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class AssetsResultGraphType : ObjectGraphType<IResultList<IAssetEntity>> public sealed class AssetsResultGraphType : ObjectGraphType<IResultList<IAssetEntity>>
{ {
public AssetsResultGraphType(IComplexGraphType assetType) public AssetsResultGraphType(IGraphType assetType)
{ {
Name = $"AssetResultDto"; Name = $"AssetResultDto";

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs

@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class ContentDataChangedResultGraphType : ObjectGraphType<ContentDataChangedResult> public sealed class ContentDataChangedResultGraphType : ObjectGraphType<ContentDataChangedResult>
{ {
public ContentDataChangedResultGraphType(string schemaType, string schemaName, IComplexGraphType contentDataType) public ContentDataChangedResultGraphType(string schemaType, string schemaName, IGraphType contentDataType)
{ {
Name = $"{schemaName}DataChangedResultDto"; Name = $"{schemaName}DataChangedResultDto";

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class ContentsResultGraphType : ObjectGraphType<IResultList<IContentEntity>> public sealed class ContentsResultGraphType : ObjectGraphType<IResultList<IContentEntity>>
{ {
public ContentsResultGraphType(string schemaType, string schemaName, IComplexGraphType contentType) public ContentsResultGraphType(string schemaType, string schemaName, IGraphType contentType)
{ {
Name = $"{schemaType}ResultDto"; Name = $"{schemaType}ResultDto";

20
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public static class InputFieldExtensions
{
public static IGraphType GetInputGraphType(this IField field)
{
return field.Accept(InputFieldVisitor.Default);
}
}
}

66
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs

@ -0,0 +1,66 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class InputFieldVisitor : IFieldVisitor<IGraphType>
{
public static readonly InputFieldVisitor Default = new InputFieldVisitor();
private InputFieldVisitor()
{
}
public IGraphType Visit(IField<AssetsFieldProperties> field)
{
return AllTypes.ListOfNonNullGuid;
}
public IGraphType Visit(IField<BooleanFieldProperties> field)
{
return AllTypes.Boolean;
}
public IGraphType Visit(IField<DateTimeFieldProperties> field)
{
return AllTypes.Date;
}
public IGraphType Visit(IField<GeolocationFieldProperties> field)
{
return AllTypes.GeolocationInput;
}
public IGraphType Visit(IField<JsonFieldProperties> field)
{
return AllTypes.NoopJson;
}
public IGraphType Visit(IField<NumberFieldProperties> field)
{
return AllTypes.Float;
}
public IGraphType Visit(IField<ReferencesFieldProperties> field)
{
return AllTypes.ListOfNonNullGuid;
}
public IGraphType Visit(IField<StringFieldProperties> field)
{
return AllTypes.String;
}
public IGraphType Visit(IField<TagsFieldProperties> field)
{
return AllTypes.ListOfNonNullString;
}
}
}

115
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs

@ -0,0 +1,115 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class QueryGraphTypeVisitor : IFieldVisitor<(IGraphType ResolveType, IFieldResolver Resolver)>
{
private readonly Func<Guid, IGraphType> schemaResolver;
private readonly IGraphType assetListType;
public QueryGraphTypeVisitor(Func<Guid, IGraphType> schemaResolver, IGraphType assetListType)
{
this.assetListType = assetListType;
this.schemaResolver = schemaResolver;
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<AssetsFieldProperties> field)
{
return ResolveAssets(assetListType);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<BooleanFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopBoolean);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<DateTimeFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopDate);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<GeolocationFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopGeolocation);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<JsonFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopJson);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<NumberFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopFloat);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<ReferencesFieldProperties> field)
{
return ResolveReferences(field);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<StringFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopString);
}
public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField<TagsFieldProperties> field)
{
return ResolveDefault(AllTypes.NoopTags);
}
private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(IGraphType type)
{
return (type, new FuncFieldResolver<ContentFieldData, object>(c => c.Source.GetOrDefault(c.FieldName)));
}
private static ValueTuple<IGraphType, IFieldResolver> ResolveAssets(IGraphType assetListType)
{
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{
var context = (GraphQLExecutionContext)c.UserContext;
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedAssetsAsync(contentIds);
});
return (assetListType, resolver);
}
private ValueTuple<IGraphType, IFieldResolver> ResolveReferences(IField field)
{
var schemaId = ((ReferencesFieldProperties)field.RawProperties).SchemaId;
var contentType = schemaResolver(schemaId);
if (contentType == null)
{
return (null, null);
}
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{
var context = (GraphQLExecutionContext)c.UserContext;
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedContentsAsync(schemaId, contentIds);
});
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType));
return (schemaFieldType, resolver);
}
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs → src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/NoopGraphType.cs

@ -9,7 +9,7 @@ using System;
using GraphQL.Language.AST; using GraphQL.Language.AST;
using GraphQL.Types; using GraphQL.Types;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{ {
public sealed class NoopGraphType : ScalarGraphType public sealed class NoopGraphType : ScalarGraphType
{ {
Loading…
Cancel
Save