From 5dc244bf64afb2056939b617885fceb7a0da3607 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Fri, 22 Nov 2024 14:13:38 -0800 Subject: [PATCH] Upgrade GraphQL to 8.2 (#16736) --- Directory.Packages.props | 17 ++++-- mkdocs.yml | 3 +- .../GraphQLMiddleware.cs | 14 ++--- .../Services/SchemaService.cs | 35 ++++++----- .../MaxNumberOfResultsValidationRule.cs | 8 ++- .../RequiresPermissionValidationRule.cs | 8 ++- .../GraphQL/Fields/ContentFieldsProvider.cs | 32 +++++++--- .../ContentPickerFieldQueryObjectType.cs | 4 +- .../GraphQL/FlowMetadataContentTypeBuilder.cs | 4 ++ .../GraphQL/LayerQueryObjectType.cs | 13 ++-- .../GraphQL/SiteLayersQuery.cs | 18 +++--- .../GraphQL/SiteCulturesQuery.cs | 10 +-- .../GraphQL/MediaAssetQuery.cs | 5 +- .../GraphQL/MenuItemContentTypeBuilder.cs | 14 ++--- .../GraphQL/MenuItemsListQueryObjectType.cs | 1 - .../Sql/GraphQL/SqlQueryFieldTypeProvider.cs | 6 +- .../GraphQL/ElasticQueryFieldTypeProvider.cs | 8 ++- .../GraphQL/LuceneQueryFieldTypeProvider.cs | 4 +- .../GraphQL/CurrentUserQuery.cs | 4 +- .../GraphQLFieldNameAttribute.cs | 1 + .../GraphQLSettings.cs | 8 ++- .../GraphQLUserContext.cs | 1 + .../Queries/IFilterInputObjectGraphType.cs | 10 +++ .../Queries/WhereInputObjectGraphType.cs | 61 ++++++++++++++----- .../ServiceCollectionExtensions.cs | 5 +- .../Options/GraphQLContentOptions.cs | 2 +- .../Options/GraphQLContentPartOption.cs | 1 + .../Queries/ContentItemFilters.cs | 5 +- .../Queries/ContentItemQuery.cs | 11 ++-- .../Queries/ContentItemsFieldType.cs | 37 +++++++++-- .../Queries/ContentTypeQuery.cs | 10 +-- .../Queries/PublicationStatusGraphType.cs | 3 +- .../Queries/Types/ContentItemInterface.cs | 2 +- .../Queries/Types/ContentItemOrderByInput.cs | 2 +- .../Queries/Types/ContentItemType.cs | 48 +++++++++++---- .../Queries/Types/ContentItemWhereInput.cs | 2 +- .../DynamicContentFieldsIndexAliasProvider.cs | 3 +- .../Types/DynamicContentTypeBuilder.cs | 12 ++-- .../Types/DynamicContentTypeQueryBuilder.cs | 6 +- .../DynamicContentTypeWhereInputBuilder.cs | 5 +- .../Queries/Types/DynamicPartGraphType.cs | 34 +++-------- .../Queries/Types/FieldTypeIndexDescriptor.cs | 8 +++ .../Queries/Types/IContentFieldProvider.cs | 6 -- .../Queries/Types/IContentTypeBuilder.cs | 3 +- .../Queries/Types/TypedContentTypeBuilder.cs | 37 +++++------ .../ServiceCollectionExtensions.cs | 1 - .../GraphQL/UserType.cs | 24 +++++--- src/docs/releases/3.0.0.md | 25 ++++++++ .../GraphQL/ContentItemsFieldTypeTests.cs | 38 ++++++++---- 49 files changed, 417 insertions(+), 202 deletions(-) create mode 100644 src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/IFilterInputObjectGraphType.cs create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/FieldTypeIndexDescriptor.cs create mode 100644 src/docs/releases/3.0.0.md diff --git a/Directory.Packages.props b/Directory.Packages.props index f807e47d456..869058edc6f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,12 +1,15 @@ + true true + 2.3.0 + @@ -22,10 +25,10 @@ - - - - + + + + @@ -88,6 +91,7 @@ + @@ -96,6 +100,7 @@ + + + + diff --git a/mkdocs.yml b/mkdocs.yml index b4568757aa9..43ff7083b28 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,7 +55,8 @@ validation: # Add files here that are intentionally not in the navigation and thus omitted_files shouldn't warn about them. not_in_nav: | samples/ - + releases/3.0.0.md + # Extensions markdown_extensions: - markdown.extensions.admonition diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index 60bdace4489..3f4ab760043 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -151,13 +151,13 @@ private async Task ExecuteAsync(HttpContext context) options.Variables = request.Variables; options.UserContext = _settings.BuildUserContext?.Invoke(context); options.ValidationRules = DocumentValidator.CoreRules - .Concat(context.RequestServices.GetServices()) - .Append(new ComplexityValidationRule(new ComplexityConfiguration - { - MaxDepth = _settings.MaxDepth, - MaxComplexity = _settings.MaxComplexity, - FieldImpact = _settings.FieldImpact - })); + .Concat(context.RequestServices.GetServices()) + .Append(new ComplexityValidationRule(new ComplexityOptions + { + MaxDepth = _settings.MaxDepth, + MaxComplexity = _settings.MaxComplexity, + DefaultObjectImpact = _settings.FieldImpact ?? 0, + })); options.Listeners.Add(dataLoaderDocumentListener); options.RequestServices = context.RequestServices; }); diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs index 3b8f0e9c8b0..bc13d9055f5 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using GraphQL; using GraphQL.MicrosoftDI; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; @@ -6,7 +7,7 @@ namespace OrchardCore.Apis.GraphQL.Services; -public class SchemaService : ISchemaFactory +public sealed class SchemaService : ISchemaFactory { private readonly IEnumerable _schemaBuilders; private readonly IServiceProvider _serviceProvider; @@ -15,7 +16,9 @@ public class SchemaService : ISchemaFactory private ISchema _schema; - public SchemaService(IEnumerable schemaBuilders, IServiceProvider serviceProvider) + public SchemaService( + IEnumerable schemaBuilders, + IServiceProvider serviceProvider) { _schemaBuilders = schemaBuilders; _serviceProvider = serviceProvider; @@ -61,21 +64,23 @@ public async Task GetSchemaAsync() var schema = new Schema(new SelfActivatingServiceProvider(_serviceProvider)) { - Query = new ObjectGraphType { Name = "Query" }, - Mutation = new ObjectGraphType { Name = "Mutation" }, - Subscription = new ObjectGraphType { Name = "Subscription" }, + Query = new ObjectGraphType + { + Name = "Query", + }, + Mutation = new ObjectGraphType + { + Name = "Mutation", + }, + Subscription = new ObjectGraphType + { + Name = "Subscription", + }, NameConverter = new OrchardFieldNameConverter(), }; - foreach (var type in serviceProvider.GetServices()) - { - schema.RegisterType(type); - } - - foreach (var type in serviceProvider.GetServices()) - { - schema.RegisterType(type); - } + schema.RegisterTypes(serviceProvider.GetServices().ToArray()); + schema.RegisterTypes(serviceProvider.GetServices().ToArray()); foreach (var builder in _schemaBuilders) { @@ -90,7 +95,6 @@ public async Task GetSchemaAsync() await builder.BuildAsync(schema); } - // Clean Query, Mutation and Subscription if they have no fields // to prevent GraphQL configuration errors. @@ -110,6 +114,7 @@ public async Task GetSchemaAsync() } schema.Initialize(); + return _schema = schema; } finally diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs index c4df8595dfc..622a252bf38 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs @@ -25,7 +25,7 @@ public MaxNumberOfResultsValidationRule( _logger = logger; } - public ValueTask ValidateAsync(ValidationContext validationContext) + public ValueTask GetPreNodeVisitorAsync(ValidationContext validationContext) { return ValueTask.FromResult((INodeVisitor)new NodeVisitors( new MatchingNodeVisitor((arg, visitorContext) => @@ -68,4 +68,10 @@ public ValueTask ValidateAsync(ValidationContext validationContext } }))); } + + public ValueTask GetVariableVisitorAsync(ValidationContext context) + => ValueTask.FromResult(null); + + public ValueTask GetPostNodeVisitorAsync(ValidationContext context) + => ValueTask.FromResult(null); } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index ba3183d2447..d1c061f9e70 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -23,7 +23,7 @@ public RequiresPermissionValidationRule( S = localizer; } - public async ValueTask ValidateAsync(ValidationContext validationContext) + public async ValueTask GetPreNodeVisitorAsync(ValidationContext validationContext) { // shouldn't we access UserContext from validation-context inside MatchingNodeVisitor actions? var userContext = (GraphQLUserContext)validationContext.UserContext; @@ -121,4 +121,10 @@ private void AddPermissionValidationError(ValidationContext validationContext, A S["Authorization is required to access the node. {0}", nodeName], node)); } + + public ValueTask GetVariableVisitorAsync(ValidationContext context) + => ValueTask.FromResult(null); + + public ValueTask GetPostNodeVisitorAsync(ValidationContext context) + => ValueTask.FromResult(null); } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 57da8bc0e8b..6954860e83b 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -20,10 +20,11 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Boolean field", FieldType = typeof(BooleanGraphType), + ResolvedType = new BooleanGraphType(), UnderlyingType = typeof(BooleanField), FieldAccessor = field => ((BooleanField)field).Value, IndexType = typeof(BooleanFieldIndex), - Index = nameof(BooleanFieldIndex.Boolean) + Index = nameof(BooleanFieldIndex.Boolean), } }, { @@ -32,10 +33,11 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Date field", FieldType = typeof(DateGraphType), + ResolvedType = new DateGraphType(), UnderlyingType = typeof(DateField), FieldAccessor = field => ((DateField)field).Value, IndexType = typeof(DateFieldIndex), - Index = nameof(DateFieldIndex.Date) + Index = nameof(DateFieldIndex.Date), } }, { @@ -44,10 +46,11 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Date & time field", FieldType = typeof(DateTimeGraphType), + ResolvedType = new DateTimeGraphType(), UnderlyingType = typeof(DateTimeField), FieldAccessor = field => ((DateTimeField)field).Value, IndexType = typeof(DateTimeFieldIndex), - Index = nameof(DateTimeFieldIndex.DateTime) + Index = nameof(DateTimeFieldIndex.DateTime), } }, { @@ -56,6 +59,7 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Numeric field", FieldType = typeof(DecimalGraphType), + ResolvedType = new DecimalGraphType(), UnderlyingType = typeof(NumericField), FieldAccessor = field => ((NumericField)field).Value, IndexType = typeof(NumericFieldIndex), @@ -68,6 +72,7 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Text field", FieldType = typeof(StringGraphType), + ResolvedType = new StringGraphType(), UnderlyingType = typeof(TextField), FieldAccessor = field => ((TextField)field).Text, IndexType = typeof(TextFieldIndex), @@ -80,6 +85,7 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Time field", FieldType = typeof(TimeSpanGraphType), + ResolvedType = new TimeSpanGraphType(), UnderlyingType = typeof(TimeField), FieldAccessor = field => ((TimeField)field).Value, IndexType = typeof(TimeFieldIndex), @@ -92,6 +98,7 @@ public class ContentFieldsProvider : IContentFieldProvider { Description = "Multi text field", FieldType = typeof(ListGraphType), + ResolvedType = new ListGraphType(new StringGraphType()), UnderlyingType = typeof(MultiTextField), FieldAccessor = field => ((MultiTextField)field).Values, } @@ -107,9 +114,10 @@ public FieldType GetField(ISchema schema, ContentPartFieldDefinition field, stri return new FieldType { - Name = customFieldName ?? field.Name, + Name = customFieldName ?? schema.NameConverter.NameForField(field.Name, null), Description = fieldDescriptor.Description, Type = fieldDescriptor.FieldType, + ResolvedType = fieldDescriptor.ResolvedType, Resolver = new FuncFieldResolver(context => { // Check if part has been collapsed by trying to get the parent part. @@ -127,7 +135,8 @@ public FieldType GetField(ISchema schema, ContentPartFieldDefinition field, stri }; } - public bool HasField(ISchema schema, ContentPartFieldDefinition field) => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); + public bool HasField(ISchema schema, ContentPartFieldDefinition field) + => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); public FieldTypeIndexDescriptor GetFieldIndex(ContentPartFieldDefinition field) { @@ -141,22 +150,29 @@ public FieldTypeIndexDescriptor GetFieldIndex(ContentPartFieldDefinition field) return new FieldTypeIndexDescriptor { Index = fieldDescriptor.Index, - IndexType = fieldDescriptor.IndexType + IndexType = fieldDescriptor.IndexType, }; } - public bool HasFieldIndex(ContentPartFieldDefinition field) => - _contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldTypeDescriptor) && + public bool HasFieldIndex(ContentPartFieldDefinition field) + => _contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldTypeDescriptor) && fieldTypeDescriptor.IndexType != null && !string.IsNullOrWhiteSpace(fieldTypeDescriptor.Index); private sealed class FieldTypeDescriptor { public string Description { get; set; } + public Type FieldType { get; set; } + public Type UnderlyingType { get; set; } + + public required IGraphType ResolvedType { get; set; } + public Func FieldAccessor { get; set; } + public string Index { get; set; } + public Type IndexType { get; set; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs index af2371a288a..df80bd7e94f 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs @@ -1,4 +1,4 @@ -using GraphQL.DataLoader; +using GraphQL; using GraphQL.Types; using OrchardCore.Apis.GraphQL; using OrchardCore.ContentFields.Fields; @@ -29,7 +29,7 @@ public ContentPickerFieldQueryObjectType() { var contentItemLoader = x.GetOrAddPublishedContentItemByIdDataLoader(); - return (contentItemLoader.LoadAsync(x.Page(x.Source.ContentItemIds))).Then(itemResultSet => + return contentItemLoader.LoadAsync(x.Page(x.Source.ContentItemIds)).Then(itemResultSet => { return itemResultSet.SelectMany(x => x); }); diff --git a/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs b/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs index 25119a26ccc..f3414b3d8a5 100644 --- a/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs +++ b/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs @@ -18,4 +18,8 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentItemType.Field("metadata") .Resolve(context => context.Source.As()); } + + public void Clear() + { + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs index a12f83eecda..58593fcf4e4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs @@ -18,11 +18,16 @@ public LayerQueryObjectType() { Name = "Layer"; - Field(layer => layer.Name).Description("The name of the layer."); + Field(layer => layer.Name) + .Description("The name of the layer."); + Field, IEnumerable>("layerrule") .Description("The rule that activates the layer.") .Resolve(ctx => ctx.Source.LayerRule.Conditions); - Field(layer => layer.Description).Description("The description of the layer."); + + Field(layer => layer.Description) + .Description("The description of the layer."); + Field, IEnumerable>("widgets") .Description("The widgets for this layer.") .Argument("status", "publication status of the widgets") @@ -50,8 +55,8 @@ async ValueTask> GetWidgetsForLayerAsync(IResolveFieldC } } - private static Expression> GetVersionFilter(PublicationStatusEnum status) => - status switch + private static Expression> GetVersionFilter(PublicationStatusEnum status) + => status switch { PublicationStatusEnum.Published => x => x.Published, PublicationStatusEnum.Draft => x => x.Latest && !x.Published, diff --git a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs index 1421bb17dc2..1e4e60cdd45 100644 --- a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs @@ -11,20 +11,22 @@ namespace OrchardCore.Layers.GraphQL; -public class SiteLayersQuery : ISchemaBuilder +public sealed class SiteLayersQuery : ISchemaBuilder { - protected readonly IStringLocalizer S; private readonly GraphQLContentOptions _graphQLContentOptions; + internal readonly IStringLocalizer S; + public SiteLayersQuery( - IStringLocalizer localizer, - IOptions graphQLContentOptions) + IOptions graphQLContentOptions, + IStringLocalizer localizer) { - S = localizer; _graphQLContentOptions = graphQLContentOptions.Value; + S = localizer; } - public Task GetIdentifierAsync() => Task.FromResult(string.Empty); + public Task GetIdentifierAsync() + => Task.FromResult(string.Empty); public Task BuildAsync(ISchema schema) { @@ -38,7 +40,7 @@ public Task BuildAsync(ISchema schema) Name = "SiteLayers", Description = S["Site layers define the rules and zone placement for widgets."], Type = typeof(ListGraphType), - Resolver = new LockedAsyncFieldResolver>(ResolveAsync) + Resolver = new LockedAsyncFieldResolver>(ResolveAsync), }; schema.Query.AddField(field); @@ -49,7 +51,9 @@ public Task BuildAsync(ISchema schema) private async ValueTask> ResolveAsync(IResolveFieldContext resolveContext) { var layerService = resolveContext.RequestServices.GetService(); + var allLayers = await layerService.GetLayersAsync(); + return allLayers.Layers; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs b/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs index 1fb5ba5d14a..347eee587d7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs @@ -12,11 +12,12 @@ namespace OrchardCore.Localization.GraphQL; /// /// Represents a site cultures for Graph QL. /// -public class SiteCulturesQuery : ISchemaBuilder +public sealed class SiteCulturesQuery : ISchemaBuilder { - protected readonly IStringLocalizer S; private readonly GraphQLContentOptions _graphQLContentOptions; + internal readonly IStringLocalizer S; + /// /// Creates a new instance of the . /// @@ -31,7 +32,8 @@ public SiteCulturesQuery( _graphQLContentOptions = graphQLContentOptions.Value; } - public Task GetIdentifierAsync() => Task.FromResult(string.Empty); + public Task GetIdentifierAsync() + => Task.FromResult(string.Empty); /// public Task BuildAsync(ISchema schema) @@ -46,7 +48,7 @@ public Task BuildAsync(ISchema schema) Name = "SiteCultures", Description = S["The active cultures configured for the site."], Type = typeof(ListGraphType), - Resolver = new LockedAsyncFieldResolver>(ResolveAsync) + Resolver = new LockedAsyncFieldResolver>(ResolveAsync), }; schema.Query.AddField(field); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs index 71c98579da2..4bd5fdc9b7a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs @@ -10,11 +10,12 @@ namespace OrchardCore.Media.GraphQL; -public class MediaAssetQuery : ISchemaBuilder +public sealed class MediaAssetQuery : ISchemaBuilder { - protected readonly IStringLocalizer S; private readonly GraphQLContentOptions _graphQLContentOptions; + internal IStringLocalizer S; + public MediaAssetQuery( IStringLocalizer localizer, IOptions graphQLContentOptions) diff --git a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs index 6c2a72c5fee..98dac63c2d9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs +++ b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs @@ -1,4 +1,3 @@ -using GraphQL.Resolvers; using GraphQL.Types; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.GraphQL.Queries.Types; @@ -16,13 +15,14 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition return; } - contentItemType.AddField(new FieldType - { - Type = typeof(MenuItemsListQueryObjectType), - Name = nameof(MenuItemsListPart).ToFieldName(), - Resolver = new FuncFieldResolver(context => context.Source.As()) - }); + contentItemType + .Field(nameof(MenuItemsListPart).ToFieldName()) + .Resolve(context => context.Source.As()); contentItemType.Interface(); } + + public void Clear() + { + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemsListQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemsListQueryObjectType.cs index 15627321436..2030c2c94ce 100644 --- a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemsListQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemsListQueryObjectType.cs @@ -12,6 +12,5 @@ public MenuItemsListQueryObjectType() Field>("menuItems") .Description("The menu items.") .Resolve(context => context.Source.MenuItems); - } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs index f0f7572fa78..98d9893d656 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs @@ -18,7 +18,7 @@ namespace OrchardCore.Queries.Sql.GraphQL.Queries; /// This implementation of registers /// all SQL Queries as GraphQL queries. /// -public class SqlQueryFieldTypeProvider : ISchemaBuilder +public sealed class SqlQueryFieldTypeProvider : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; @@ -100,7 +100,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc var typeType = new ObjectGraphType { - Name = fieldTypeName + Name = fieldTypeName, }; foreach (var child in properties) @@ -131,8 +131,8 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc var field = new FieldType() { Name = nameLower, - Description = description, Type = typeof(IntGraphType), + Description = description, Resolver = new FuncFieldResolver(context => { var source = context.Source; diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs index 492dcccb0e6..11440892e85 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs @@ -16,12 +16,14 @@ namespace OrchardCore.Search.Elasticsearch.GraphQL.Queries; -public class ElasticQueryFieldTypeProvider : ISchemaBuilder +public sealed class ElasticQueryFieldTypeProvider : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; - public ElasticQueryFieldTypeProvider(IHttpContextAccessor httpContextAccessor, ILogger logger) + public ElasticQueryFieldTypeProvider( + IHttpContextAccessor httpContextAccessor, + ILogger logger) { _httpContextAccessor = httpContextAccessor; _logger = logger; @@ -112,6 +114,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc Name = nameLower, Description = description, Type = typeof(StringGraphType), + ResolvedType = new StringGraphType(), Resolver = new FuncFieldResolver(context => { var source = context.Source; @@ -128,6 +131,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc Name = nameLower, Description = description, Type = typeof(IntGraphType), + ResolvedType = new IntGraphType(), Resolver = new FuncFieldResolver(context => { var source = context.Source; diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs index 63b4e630c38..c7b5cb68591 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs @@ -15,7 +15,7 @@ namespace OrchardCore.Queries.Lucene.GraphQL.Queries; -public class LuceneQueryFieldTypeProvider : ISchemaBuilder +public sealed class LuceneQueryFieldTypeProvider : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; @@ -114,6 +114,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc Name = nameLower, Description = description, Type = typeof(StringGraphType), + ResolvedType = new StringGraphType(), Resolver = new FuncFieldResolver(context => { var source = context.Source; @@ -130,6 +131,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc Name = nameLower, Description = description, Type = typeof(IntGraphType), + ResolvedType = new IntGraphType(), Resolver = new FuncFieldResolver(context => { var source = context.Source; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs index 5f108547a0d..b1198136f22 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs @@ -16,7 +16,8 @@ namespace OrchardCore.Users.GraphQL; internal sealed class CurrentUserQuery : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IStringLocalizer S; + + internal readonly IStringLocalizer S; public CurrentUserQuery( IHttpContextAccessor httpContextAccessor, @@ -50,6 +51,7 @@ public Task BuildAsync(ISchema schema) public Task GetIdentifierAsync() { var contentDefinitionManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(); + return contentDefinitionManager.GetIdentifierAsync(); } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLFieldNameAttribute.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLFieldNameAttribute.cs index ce557684395..5c457989358 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLFieldNameAttribute.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLFieldNameAttribute.cs @@ -10,5 +10,6 @@ public GraphQLFieldNameAttribute(string field, string mapped) } public string Field { get; } + public string Mapped { get; } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs index 69313c2b434..2a48c5b0586 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs @@ -5,15 +5,21 @@ namespace OrchardCore.Apis.GraphQL; public class GraphQLSettings { public PathString Path { get; set; } = "/api/graphql"; + public Func> BuildUserContext { get; set; } public bool ExposeExceptions { get; set; } public int? MaxDepth { get; set; } + public int? MaxComplexity { get; set; } + public double? FieldImpact { get; set; } + public int DefaultNumberOfResults { get; set; } + public int MaxNumberOfResults { get; set; } + public MaxNumberOfResultsValidationMode MaxNumberOfResultsValidationMode { get; set; } } @@ -21,5 +27,5 @@ public enum MaxNumberOfResultsValidationMode { Default, Enabled, - Disabled + Disabled, } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs index 4816a009504..41e297c5e47 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs @@ -5,5 +5,6 @@ namespace OrchardCore.Apis.GraphQL; public class GraphQLUserContext : Dictionary { public ClaimsPrincipal User { get; set; } + public SemaphoreSlim ExecutionContextLock { get; } = new SemaphoreSlim(1, 1); } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/IFilterInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/IFilterInputObjectGraphType.cs new file mode 100644 index 00000000000..6e5b8a39b5d --- /dev/null +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/IFilterInputObjectGraphType.cs @@ -0,0 +1,10 @@ +using GraphQL.Types; + +namespace OrchardCore.Apis.GraphQL.Queries; + +public interface IFilterInputObjectGraphType : IInputObjectGraphType +{ + void AddScalarFilterFields(string fieldName, string description); + + void AddScalarFilterFields(Type graphType, string fieldName, string description); +} diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index 7d28ba1b445..c084f6af265 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -3,13 +3,6 @@ namespace OrchardCore.Apis.GraphQL.Queries; -public interface IFilterInputObjectGraphType : IInputObjectGraphType -{ - void AddScalarFilterFields(string fieldName, string description); - - void AddScalarFilterFields(Type graphType, string fieldName, string description); -} - public class WhereInputObjectGraphType : WhereInputObjectGraphType, IFilterInputObjectGraphType { } @@ -94,26 +87,29 @@ public virtual void AddScalarFilterFields(Type graphType, string fieldName, stri private void AddEqualityFilters(Type graphType, string fieldName, string description) { - AddFilterFields(graphType, EqualityOperators, fieldName, description); + AddFilterFields(CreateGraphType(graphType), EqualityOperators, fieldName, description); } private void AddStringFilters(Type graphType, string fieldName, string description) { - AddFilterFields(graphType, StringComparisonOperators, fieldName, description); + AddFilterFields(CreateGraphType(graphType), StringComparisonOperators, fieldName, description); } private void AddNonStringFilters(Type graphType, string fieldName, string description) { - AddFilterFields(graphType, NonStringValueComparisonOperators, fieldName, description); + AddFilterFields(CreateGraphType(graphType), NonStringValueComparisonOperators, fieldName, description); } private void AddMultiValueFilters(Type graphType, string fieldName, string description) { - var wrappedType = typeof(ListGraphType<>).MakeGenericType(graphType); - AddFilterFields(wrappedType, MultiValueComparisonOperators, fieldName, description); + AddFilterFields(CreateGraphType(graphType), MultiValueComparisonOperators, fieldName, description); } - private void AddFilterFields(Type graphType, IDictionary filters, string fieldName, string description) + private void AddFilterFields( + IGraphType resolvedType, + IDictionary filters, + string fieldName, + string description) { foreach (var filter in filters) { @@ -121,8 +117,45 @@ private void AddFilterFields(Type graphType, IDictionary filters { Name = fieldName + filter.Key, Description = $"{description} {filter.Value}", - Type = graphType + ResolvedType = resolvedType, }); } } + + private readonly Dictionary graphTypes = new(); + + private IGraphType CreateGraphType(Type type) + { + if (type.IsGenericType) + { + var genericDef = type.GetGenericTypeDefinition(); + if (genericDef == typeof(ListGraphType<>)) + { + var innerType = type.GetGenericArguments()[0]; + + return new ListGraphType(CreateGraphType(innerType)); + } + + if (genericDef == typeof(NonNullGraphType<>)) + { + var innerType = type.GetGenericArguments()[0]; + + return new NonNullGraphType(CreateGraphType(innerType)); + } + } + + if (typeof(ScalarGraphType).IsAssignableFrom(type)) + { + if (!graphTypes.TryGetValue(type, out var graphType)) + { + graphType = (IGraphType)Activator.CreateInstance(type); + + graphTypes[type] = graphType; + } + + return graphType; + } + + throw new InvalidOperationException($"{type.Name} is not a valid {nameof(ScalarGraphType)}."); + } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/ServiceCollectionExtensions.cs index ca5a2c72663..3c13ea5e2c4 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace OrchardCore.Apis; @@ -15,7 +16,7 @@ public static void AddInputObjectGraphType(this IServiceCo where TObject : class where TObjectType : InputObjectGraphType { - services.AddTransient(); + services.TryAddTransient(); services.AddTransient, TObjectType>(s => s.GetRequiredService()); services.AddTransient(s => s.GetRequiredService()); } @@ -30,7 +31,7 @@ public static void AddObjectGraphType(this IServiceCollectio where TInput : class where TInputType : ObjectGraphType { - services.AddTransient(); + services.TryAddTransient(); services.AddTransient, TInputType>(s => s.GetRequiredService()); services.AddTransient(s => s.GetRequiredService()); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs index 420676967fc..961836ad20f 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs @@ -157,7 +157,7 @@ public bool IsHiddenByDefault(string contentType) { ArgumentException.ThrowIfNullOrEmpty(contentType); - var contentTypeOption = ContentTypeOptions.FirstOrDefault(ctp => ctp.ContentType == contentType); + var contentTypeOption = ContentTypeOptions.FirstOrDefault(ctp => ctp.ContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase)); return contentTypeOption?.Hidden ?? false; } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentPartOption.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentPartOption.cs index ce1e54f80c8..d933310c67d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentPartOption.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentPartOption.cs @@ -19,5 +19,6 @@ public GraphQLContentPartOption(string contentPartName) public string Name { get; } public bool Collapse { get; set; } + public bool Hidden { get; set; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemFilters.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemFilters.cs index 7a5c3fef8a8..3308fb41b8f 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemFilters.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemFilters.cs @@ -11,13 +11,14 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries; -public class ContentItemFilters : GraphQLFilter +public sealed class ContentItemFilters : GraphQLFilter { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IAuthorizationService _authorizationService; - public ContentItemFilters(IHttpContextAccessor httpContextAccessor, + public ContentItemFilters( + IHttpContextAccessor httpContextAccessor, IContentDefinitionManager contentDefinitionManager, IAuthorizationService authorizationService) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs index 29408e222f8..19f8203cbd5 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs @@ -9,20 +9,21 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries; -public class ContentItemQuery : ISchemaBuilder +public sealed class ContentItemQuery : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; - protected readonly IStringLocalizer S; + + internal readonly IStringLocalizer S; public ContentItemQuery(IHttpContextAccessor httpContextAccessor, IStringLocalizer localizer) { _httpContextAccessor = httpContextAccessor; - S = localizer; } - public Task GetIdentifierAsync() => Task.FromResult(string.Empty); + public Task GetIdentifierAsync() + => Task.FromResult(string.Empty); public Task BuildAsync(ISchema schema) { @@ -35,7 +36,7 @@ public Task BuildAsync(ISchema schema) new QueryArgument> { Name = "contentItemId", - Description = S["Content item id"] + Description = S["Content item id"], } ), Resolver = new FuncFieldResolver(ResolveAsync) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 37b3346a80c..850c09bdd92 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -40,16 +40,41 @@ public ContentItemsFieldType(string contentItemName, ISchema schema, IOptions); - var whereInput = new ContentItemWhereInput(contentItemName, optionsAccessor); var orderByInput = new ContentItemOrderByInput(contentItemName); Arguments = new QueryArguments( - new QueryArgument { Name = "where", Description = "filters the content items", ResolvedType = whereInput }, - new QueryArgument { Name = "orderBy", Description = "sort order", ResolvedType = orderByInput }, - new QueryArgument { Name = "first", Description = "the first n content items", ResolvedType = new IntGraphType() }, - new QueryArgument { Name = "skip", Description = "the number of content items to skip", ResolvedType = new IntGraphType() }, - new QueryArgument { Name = "status", Description = "publication status of the content item", ResolvedType = new PublicationStatusGraphType(), DefaultValue = PublicationStatusEnum.Published } + new QueryArgument + { + Name = "where", + Description = "filters the content items", + ResolvedType = whereInput, + }, + new QueryArgument + { + Name = "orderBy", + Description = "sort order", + ResolvedType = orderByInput, + }, + new QueryArgument + { + Name = "first", + Description = "the first n content items", + ResolvedType = new IntGraphType(), + }, + new QueryArgument + { + Name = "skip", + Description = "the number of content items to skip", + ResolvedType = new IntGraphType(), + }, + new QueryArgument + { + Name = "status", + Description = "publication status of the content item", + ResolvedType = new PublicationStatusGraphType(), + DefaultValue = PublicationStatusEnum.Published, + } ); Resolver = new LockedAsyncFieldResolver>(ResolveAsync); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs index 874ff2e4ef0..f99d9125630 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs @@ -14,12 +14,13 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries; /// /// Registers all Content Types as queries. /// -public class ContentTypeQuery : ISchemaBuilder +public sealed class ContentTypeQuery : ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IOptions _contentOptionsAccessor; private readonly IOptions _settingsAccessor; - protected readonly IStringLocalizer S; + + internal readonly IStringLocalizer S; public ContentTypeQuery(IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, @@ -35,6 +36,7 @@ public ContentTypeQuery(IHttpContextAccessor httpContextAccessor, public Task GetIdentifierAsync() { var contentDefinitionManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); + return contentDefinitionManager.GetIdentifierAsync(); } @@ -55,14 +57,14 @@ public async Task BuildAsync(ISchema schema) var typeType = new ContentItemType(_contentOptionsAccessor) { Name = typeDefinition.Name, - Description = S["Represents a {0}.", typeDefinition.DisplayName] + Description = S["Represents a {0}.", typeDefinition.DisplayName], }; var query = new ContentItemsFieldType(typeDefinition.Name, schema, _contentOptionsAccessor, _settingsAccessor) { Name = typeDefinition.Name, Description = S["Represents a {0}.", typeDefinition.DisplayName], - ResolvedType = new ListGraphType(typeType) + ResolvedType = new ListGraphType(typeType), }; query.RequirePermission(CommonPermissions.ExecuteGraphQL); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/PublicationStatusGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/PublicationStatusGraphType.cs index 8dcefd2e92d..de831b8fa23 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/PublicationStatusGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/PublicationStatusGraphType.cs @@ -2,9 +2,8 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries; -public class PublicationStatusGraphType : EnumerationGraphType +public sealed class PublicationStatusGraphType : EnumerationGraphType { - public PublicationStatusGraphType() { Name = "Status"; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemInterface.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemInterface.cs index 1cbd102f448..f5fc4ccfd20 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemInterface.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemInterface.cs @@ -4,7 +4,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class ContentItemInterface : InterfaceGraphType +public sealed class ContentItemInterface : InterfaceGraphType { private readonly GraphQLContentOptions _options; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemOrderByInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemOrderByInput.cs index 79326ee323c..e61cd619e24 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemOrderByInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemOrderByInput.cs @@ -2,7 +2,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class ContentItemOrderByInput : InputObjectGraphType +public sealed class ContentItemOrderByInput : InputObjectGraphType { public ContentItemOrderByInput(string contentItemName) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs index a721ad1e18f..93e72619446 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs @@ -12,7 +12,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class ContentItemType : ObjectGraphType +public sealed class ContentItemType : ObjectGraphType { private readonly GraphQLContentOptions _options; @@ -22,17 +22,41 @@ public ContentItemType(IOptions optionsAccessor) Name = "ContentItemType"; - Field(ci => ci.ContentItemId).Description("Content item id"); - Field(ci => ci.ContentItemVersionId, nullable: true).Description("The content item version id"); - Field(ci => ci.ContentType).Description("Type of content"); - Field(ci => ci.DisplayText, nullable: true).Description("The display text of the content item"); - Field(ci => ci.Published).Description("Is the published version"); - Field(ci => ci.Latest).Description("Is the latest version"); - Field("modifiedUtc").Resolve(ci => ci.Source.ModifiedUtc).Description("The date and time of modification"); - Field("publishedUtc").Resolve(ci => ci.Source.PublishedUtc).Description("The date and time of publication"); - Field("createdUtc").Resolve(ci => ci.Source.CreatedUtc).Description("The date and time of creation"); - Field(ci => ci.Owner, nullable: true).Description("The owner of the content item"); - Field(ci => ci.Author).Description("The author of the content item"); + Field(ci => ci.ContentItemId) + .Description("Content item id"); + + Field(ci => ci.ContentItemVersionId, nullable: true) + .Description("The content item version id"); + + Field(ci => ci.ContentType) + .Description("Type of content"); + + Field(ci => ci.DisplayText, nullable: true) + .Description("The display text of the content item"); + + Field(ci => ci.Published) + .Description("Is the published version"); + + Field(ci => ci.Latest) + .Description("Is the latest version"); + + Field("modifiedUtc") + .Resolve(ci => ci.Source.ModifiedUtc) + .Description("The date and time of modification"); + + Field("publishedUtc") + .Resolve(ci => ci.Source.PublishedUtc) + .Description("The date and time of publication"); + + Field("createdUtc") + .Resolve(ci => ci.Source.CreatedUtc) + .Description("The date and time of creation"); + + Field(ci => ci.Owner, nullable: true) + .Description("The owner of the content item"); + + Field(ci => ci.Author) + .Description("The author of the content item"); Field("render") .ResolveLockedAsync(RenderShapeAsync); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index 8cb51e3026a..e3015d46d7d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -9,7 +9,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; [GraphQLFieldName("Or", "OR")] [GraphQLFieldName("And", "AND")] [GraphQLFieldName("Not", "NOT")] -public class ContentItemWhereInput : WhereInputObjectGraphType +public sealed class ContentItemWhereInput : WhereInputObjectGraphType { private readonly IOptions _optionsAccessor; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index e693a5595a0..9b109a4d622 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -7,7 +7,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, IContentDefinitionEventHandler +public sealed class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, IContentDefinitionEventHandler { private static readonly string _cacheKey = nameof(DynamicContentFieldsIndexAliasProvider); @@ -16,7 +16,6 @@ public class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, ICont private readonly IMemoryCache _memoryCache; private readonly GraphQLContentOptions _contentOptions; - public DynamicContentFieldsIndexAliasProvider( IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 53ba0a1476a..026b8fa76e3 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -14,10 +14,13 @@ public abstract class DynamicContentTypeBuilder : IContentTypeBuilder { protected readonly IHttpContextAccessor _httpContextAccessor; protected readonly GraphQLContentOptions _contentOptions; + protected readonly IStringLocalizer S; + private readonly Dictionary _dynamicPartFields; - protected DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, + protected DynamicContentTypeBuilder( + IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, IStringLocalizer localizer) { @@ -36,9 +39,8 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe { return; } - var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; - var contentFieldProviders = serviceProvider.GetServices().ToList(); + var contentFieldProviders = serviceProvider.GetServices().ToArray(); foreach (var part in contentTypeDefinition.Parts) { @@ -184,14 +186,14 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe Name = partFieldName, Description = S["Represents a {0}.", part.PartDefinition.Name], Type = typeof(DynamicPartGraphType), - ResolvedType = new DynamicPartGraphType(part), + ResolvedType = new DynamicPartGraphType(part, schema, contentFieldProviders), Resolver = new FuncFieldResolver(context => { var nameToResolve = partName; var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); return context.Source.Get(typeToResolve, nameToResolve); - }) + }), }; objectGraphType.AddField(field); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index 6a74496498b..6745d84e075 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -7,12 +7,14 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder +public sealed class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder { public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, IStringLocalizer localizer) - : base(httpContextAccessor, contentOptionsAccessor, localizer) { } + : base(httpContextAccessor, contentOptionsAccessor, localizer) + { + } public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs index ff1836b04ce..c2fa5081c66 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs @@ -7,9 +7,10 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class DynamicContentTypeWhereInputBuilder : DynamicContentTypeBuilder +public sealed class DynamicContentTypeWhereInputBuilder : DynamicContentTypeBuilder { - public DynamicContentTypeWhereInputBuilder(IHttpContextAccessor httpContextAccessor, + public DynamicContentTypeWhereInputBuilder( + IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, IStringLocalizer localizer) : base(httpContextAccessor, contentOptionsAccessor, localizer) { } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs index 123c5b861f2..3f8d21195fe 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs @@ -1,42 +1,28 @@ using GraphQL.Types; -using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement.Metadata.Models; namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; public sealed class DynamicPartGraphType : ObjectGraphType { - private ContentTypePartDefinition _part; - - public DynamicPartGraphType(ContentTypePartDefinition part) + public DynamicPartGraphType( + ContentTypePartDefinition part, + ISchema schema, + IEnumerable contentFieldProviders) { Name = part.Name; - _part = part; - } - public override void Initialize(ISchema schema) - { - if (schema is IServiceProvider serviceProvider) + foreach (var field in part.PartDefinition.Fields) { - var contentFieldProviders = serviceProvider.GetServices().ToList(); - - foreach (var field in _part.PartDefinition.Fields) + foreach (var fieldProvider in contentFieldProviders) { - foreach (var fieldProvider in contentFieldProviders) + var fieldType = fieldProvider.GetField(schema, field, part.Name); + if (fieldType != null) { - var fieldType = fieldProvider.GetField(schema, field, _part.Name); - if (fieldType != null) - { - AddField(fieldType); - break; - } + AddField(fieldType); + break; } } } - - // Part is not required here anymore, do not keep it alive. - _part = null; - - base.Initialize(schema); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/FieldTypeIndexDescriptor.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/FieldTypeIndexDescriptor.cs new file mode 100644 index 00000000000..fdca6d51baa --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/FieldTypeIndexDescriptor.cs @@ -0,0 +1,8 @@ +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public sealed class FieldTypeIndexDescriptor +{ + public required string Index { get; set; } + + public required Type IndexType { get; set; } +} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs index a1f0bc2fea1..1f691702c9e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs @@ -13,9 +13,3 @@ public interface IContentFieldProvider bool HasFieldIndex(ContentPartFieldDefinition field); } - -public sealed class FieldTypeIndexDescriptor -{ - public required string Index { get; set; } - public required Type IndexType { get; set; } -} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs index 4964df7604e..599ab80114a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs @@ -6,5 +6,6 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; public interface IContentTypeBuilder { void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); - void Clear() { } + + void Clear(); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs index 561939d0066..0ee612716ba 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs @@ -9,12 +9,13 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public class TypedContentTypeBuilder : IContentTypeBuilder +public sealed class TypedContentTypeBuilder : IContentTypeBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly GraphQLContentOptions _contentOptions; - public TypedContentTypeBuilder(IHttpContextAccessor httpContextAccessor, + public TypedContentTypeBuilder( + IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor) { _httpContextAccessor = httpContextAccessor; @@ -64,7 +65,8 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition continue; } - var partType = typeActivator.GetTypeActivator(part.PartDefinition.Name).Type; + var partActivator = typeActivator.GetTypeActivator(part.PartDefinition.Name); + var partType = partActivator.Type; var rolledUpField = new FieldType { Name = field.Name, @@ -93,21 +95,16 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition } else { - var field = new FieldType - { - Name = partFieldName, - Type = queryGraphType.GetType(), - Description = queryGraphType.Description, - }; - contentItemType.Field(partFieldName, queryGraphType.GetType()) - .Description(queryGraphType.Description) - .Resolve(context => - { - var nameToResolve = partName; - var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); - - return context.Source.Get(typeToResolve, nameToResolve); - }); + contentItemType + .Field(partFieldName, queryGraphType.GetType()) + .Description(queryGraphType.Description) + .Resolve(context => + { + var nameToResolve = partName; + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); + + return context.Source.Get(typeToResolve, nameToResolve); + }); } } @@ -143,4 +140,8 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition } } } + + public void Clear() + { + } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs index 95ea1de8388..ea870b8c4fa 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs @@ -22,7 +22,6 @@ public static IServiceCollection AddContentGraphQL(this IServiceCollection servi services.AddPermissionProvider(); - services.AddTransient(); services.AddScoped(); services.AddScoped(); diff --git a/src/OrchardCore/OrchardCore.Users.Core/GraphQL/UserType.cs b/src/OrchardCore/OrchardCore.Users.Core/GraphQL/UserType.cs index a1276989049..ab0e672d154 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/GraphQL/UserType.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/GraphQL/UserType.cs @@ -6,9 +6,9 @@ namespace OrchardCore.Users.GraphQL; -public class UserType : ObjectGraphType +public sealed class UserType : ObjectGraphType { - protected readonly IStringLocalizer S; + internal readonly IStringLocalizer S; public UserType(IStringLocalizer localizer) { @@ -17,17 +17,27 @@ public UserType(IStringLocalizer localizer) Name = "User"; Description = S["Represents a user."]; - Field(u => u.UserId).Description(S["The id of the user."]); - Field(u => u.UserName).Description(S["The name of the user."]); - Field(u => u.Email, nullable: true).Description(S["The email of the user."]); - Field(u => u.PhoneNumber, nullable: true).Description(S["The phone number of the user."]); + Field(u => u.UserId) + .Description(S["The id of the user."]); + + Field(u => u.UserName) + .Description(S["The name of the user."]); + + Field(u => u.Email, nullable: true) + .Description(S["The email of the user."]); + + Field(u => u.PhoneNumber, nullable: true) + .Description(S["The phone number of the user."]); } public override void Initialize(ISchema schema) { // Add custom user settings by reusing previously registered content types with the // stereotype "CustomUserSettings". - foreach (var contentItemType in schema.AdditionalTypeInstances.Where(t => t.Metadata.TryGetValue("Stereotype", out var stereotype) && stereotype as string == "CustomUserSettings")) + var contentItemTypes = schema.AdditionalTypeInstances + .Where(t => t.Metadata.TryGetValue("Stereotype", out var stereotype) && (stereotype as string) == "CustomUserSettings"); + + foreach (var contentItemType in contentItemTypes) { Field(contentItemType.Name, contentItemType) .Description(S["Custom user settings of {0}.", contentItemType.Name]) diff --git a/src/docs/releases/3.0.0.md b/src/docs/releases/3.0.0.md new file mode 100644 index 00000000000..0718c3c0d1e --- /dev/null +++ b/src/docs/releases/3.0.0.md @@ -0,0 +1,25 @@ +# Orchard Core 3.0.0 + +Release date: Not yet released + +## Breaking Changes + +### GraphQL Module + +#### GraphQL Library Upgrade + +The `GraphQL` libraries have been upgraded from version 7 to version 8. Below are important changes and considerations for your implementation: + +1. **Removal of Default Implementation**: + The `IContentTypeBuilder` interface previously included a default implementation for the `Clear()` method. This implementation has been removed. If you have a custom implementation of `IContentTypeBuilder`, you must now provide your own `Clear()` method. The method can remain empty if no specific actions are needed. + +2. **Sealed Classes**: + Several classes have been marked as sealed to prevent further inheritance. This change is intended to enhance stability and maintainability. The following classes are now sealed: + + - All implementations of `InputObjectGraphType` + - All implementations of `ObjectGraphType<>` + - All implementations of `WhereInputObjectGraphType` + - All implementations of `DynamicContentTypeBuilder` + - All implementations of `IContentTypeBuilder` + - All implementations of `GraphQLFilter` + - All implementations of `ISchemaBuilder` diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index cafc2c3cb14..904d0a88885 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -414,15 +414,19 @@ private static ResolveFieldContext CreateAnimalFieldContext(IServiceProvider ser FieldDefinition = new FieldType { Name = "Inputs", - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), - Arguments = [ - new QueryArgument - { - Name = "where", - Description = "filters the animals", - ResolvedType = where - } - ] + ResolvedType = new ListGraphType(new StringGraphType() + { + Name = "Animal", + }), + Arguments = + [ + new QueryArgument + { + Name = "where", + Description = "filters the animals", + ResolvedType = where + } + ] }, RequestServices = services }; @@ -435,7 +439,12 @@ public AnimalPartWhereInput(string fieldName) { Name = "Test"; Description = "Foo"; - var fieldType = new FieldType { Name = fieldName, Type = typeof(StringGraphType) }; + var fieldType = new FieldType + { + Name = fieldName, + Type = typeof(StringGraphType), + ResolvedType = new StringGraphType(), + }; fieldType.Metadata["PartName"] = "AnimalPart"; AddField(fieldType); } @@ -447,7 +456,12 @@ public AnimalPartCollapsedWhereInput() { Name = "Test"; Description = "Foo"; - var fieldType = new FieldType { Name = "Name", Type = typeof(StringGraphType) }; + var fieldType = new FieldType + { + Name = "Name", + Type = typeof(StringGraphType), + ResolvedType = new StringGraphType(), + }; fieldType.Metadata["PartName"] = "AnimalPart"; fieldType.Metadata["PartCollapsed"] = true; AddField(fieldType); @@ -457,7 +471,9 @@ public AnimalPartCollapsedWhereInput() public class Animal : ContentPart { public string Name { get; set; } + public bool IsHappy { get; set; } + public bool IsScary { get; set; } }